feat(config): configurable generated-section captions (P2)
TOC / links / tags section headings (and their levels) were hardcoded. Added per-wiki toc_header/toc_header_level, links_header/links_header_level, tags_header/tags_header_level — defaults Contents / Generated Links / Generated Tags at level 1, matching upstream (also fixes the tags index heading: Tags -> Generated Tags). A caption_line(name, level) helper emits the `=`-markers; the configured text + level thread through toc_edit / links_edit / tag_links_edit and the auto_toc save hook. find_section_range matches the configured text, so regeneration stays idempotent at any level. Tests: captions_honour_custom_header_and_level (link_health) + config round-trip + default assertions (index_and_config); existing tag/toc/links tests updated for the new arity and the Generated Tags default. fmt/clippy clean (8-arg ops get allow(too_many_arguments), matching the codebase precedent); 0 Rust test failures; config-parity green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -231,6 +231,9 @@ require('nuwiki').setup({
|
|||||||
css_name = 'style.css',
|
css_name = 'style.css',
|
||||||
auto_export = false, -- export on save
|
auto_export = false, -- export on save
|
||||||
auto_toc = false, -- auto-refresh TOC on save
|
auto_toc = false, -- auto-refresh TOC on save
|
||||||
|
toc_header = 'Contents', -- :VimwikiTOC caption (+ toc_header_level = 1)
|
||||||
|
links_header = 'Generated Links',
|
||||||
|
tags_header = 'Generated Tags',
|
||||||
exclude_files = {}, -- glob patterns
|
exclude_files = {}, -- glob patterns
|
||||||
-- Checkbox progression characters: " .oOX" by default.
|
-- Checkbox progression characters: " .oOX" by default.
|
||||||
listsyms = ' .oOX',
|
listsyms = ' .oOX',
|
||||||
@@ -333,6 +336,9 @@ Each is a boolean. All default to `true` except `mouse`.
|
|||||||
| `list_margin` | int | `-1` | `-1` (auto) or `≥ 0` |
|
| `list_margin` | int | `-1` | `-1` (auto) or `≥ 0` |
|
||||||
| `links_space_char` | string | `' '` | char that replaces spaces in synthesised link paths |
|
| `links_space_char` | string | `' '` | char that replaces spaces in synthesised link paths |
|
||||||
| `auto_toc` | bool | `false` | `true` \| `false` |
|
| `auto_toc` | bool | `false` | `true` \| `false` |
|
||||||
|
| `toc_header` / `toc_header_level` | string / int | `'Contents'` / `1` | `:VimwikiTOC` caption + heading level |
|
||||||
|
| `links_header` / `links_header_level` | string / int | `'Generated Links'` / `1` | `:VimwikiGenerateLinks` caption + level |
|
||||||
|
| `tags_header` / `tags_header_level` | string / int | `'Generated Tags'` / `1` | `:VimwikiGenerateTagLinks` index caption + level |
|
||||||
|
|
||||||
#### HTML export keys (per-wiki)
|
#### HTML export keys (per-wiki)
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,14 @@ fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceE
|
|||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||||
Ok(ops::toc_edit(&doc.text, &doc.ast, &p.uri, utf8))
|
let wiki = backend.wiki_for_uri(&p.uri);
|
||||||
|
let (header, level) = wiki
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| (w.config.toc_header.clone(), w.config.toc_header_level))
|
||||||
|
.unwrap_or_else(|| (ops::TOC_HEADING.to_string(), 1));
|
||||||
|
Ok(ops::toc_edit(
|
||||||
|
&doc.text, &doc.ast, &p.uri, utf8, &header, level,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
||||||
@@ -353,6 +360,8 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
|||||||
¤t_page,
|
¤t_page,
|
||||||
&pages,
|
&pages,
|
||||||
utf8,
|
utf8,
|
||||||
|
&wiki.config.links_header,
|
||||||
|
wiki.config.links_header_level,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +624,8 @@ fn tags_generate_links(
|
|||||||
parsed.tag.as_deref(),
|
parsed.tag.as_deref(),
|
||||||
&snapshot,
|
&snapshot,
|
||||||
utf8,
|
utf8,
|
||||||
|
&wiki.config.tags_header,
|
||||||
|
wiki.config.tags_header_level,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1780,14 +1791,18 @@ pub mod ops {
|
|||||||
anchor: String,
|
anchor: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `= Heading =` line at the given level (clamped 1..=6), with trailing
|
||||||
|
/// newline. Drives the configurable `*_header` / `*_header_level` captions.
|
||||||
|
pub fn caption_line(name: &str, level: u8) -> String {
|
||||||
|
let eq = "=".repeat((level as usize).clamp(1, 6));
|
||||||
|
format!("{eq} {name} {eq}\n")
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a heading + nested list TOC for the current document. The TOC
|
/// Build a heading + nested list TOC for the current document. The TOC
|
||||||
/// itself (any existing `= Contents =` heading on the page) is skipped
|
/// itself (any existing `= Contents =` heading on the page) is skipped
|
||||||
/// so re-generation is idempotent.
|
/// so re-generation is idempotent.
|
||||||
pub fn build_toc_text(items: &[(u8, String, String)], heading_name: &str) -> String {
|
pub fn build_toc_text(items: &[(u8, String, String)], heading_name: &str, level: u8) -> String {
|
||||||
let mut out = String::new();
|
let mut out = caption_line(heading_name, level);
|
||||||
out.push_str("= ");
|
|
||||||
out.push_str(heading_name);
|
|
||||||
out.push_str(" =\n");
|
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -1808,11 +1823,13 @@ pub mod ops {
|
|||||||
|
|
||||||
/// Build a flat list of wikilinks to every page (sorted), optionally
|
/// Build a flat list of wikilinks to every page (sorted), optionally
|
||||||
/// excluding `current_page`.
|
/// excluding `current_page`.
|
||||||
pub fn build_links_text(pages: &[String], heading_name: &str, exclude: Option<&str>) -> String {
|
pub fn build_links_text(
|
||||||
let mut out = String::new();
|
pages: &[String],
|
||||||
out.push_str("= ");
|
heading_name: &str,
|
||||||
out.push_str(heading_name);
|
level: u8,
|
||||||
out.push_str(" =\n");
|
exclude: Option<&str>,
|
||||||
|
) -> String {
|
||||||
|
let mut out = caption_line(heading_name, level);
|
||||||
for name in pages {
|
for name in pages {
|
||||||
if Some(name.as_str()) == exclude {
|
if Some(name.as_str()) == exclude {
|
||||||
continue;
|
continue;
|
||||||
@@ -1938,8 +1955,10 @@ pub mod ops {
|
|||||||
ast: &DocumentNode,
|
ast: &DocumentNode,
|
||||||
uri: &Url,
|
uri: &Url,
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
heading: &str,
|
||||||
|
level: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let items = collect_toc_items(ast, TOC_HEADING);
|
let items = collect_toc_items(ast, heading);
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1947,9 +1966,9 @@ pub mod ops {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|it| (it.level, it.title, it.anchor))
|
.map(|it| (it.level, it.title, it.anchor))
|
||||||
.collect();
|
.collect();
|
||||||
let new_text = build_toc_text(&triples, TOC_HEADING);
|
let new_text = build_toc_text(&triples, heading, level);
|
||||||
let mut b = WorkspaceEditBuilder::new();
|
let mut b = WorkspaceEditBuilder::new();
|
||||||
match find_section_range(ast, TOC_HEADING) {
|
match find_section_range(ast, heading) {
|
||||||
Some((start, end)) => {
|
Some((start, end)) => {
|
||||||
let span = Span::new(start, end);
|
let span = Span::new(start, end);
|
||||||
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
||||||
@@ -1980,13 +1999,16 @@ pub mod ops {
|
|||||||
ast: &DocumentNode,
|
ast: &DocumentNode,
|
||||||
uri: &Url,
|
uri: &Url,
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
heading: &str,
|
||||||
|
level: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
find_section_range(ast, TOC_HEADING)?;
|
find_section_range(ast, heading)?;
|
||||||
toc_edit(text, ast, uri, utf8)
|
toc_edit(text, ast, uri, utf8, heading, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a `WorkspaceEdit` that replaces or inserts the
|
/// Produce a `WorkspaceEdit` that replaces or inserts the
|
||||||
/// auto-generated links section for the given document.
|
/// auto-generated links section for the given document.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn links_edit(
|
pub fn links_edit(
|
||||||
text: &str,
|
text: &str,
|
||||||
ast: &DocumentNode,
|
ast: &DocumentNode,
|
||||||
@@ -1994,13 +2016,15 @@ pub mod ops {
|
|||||||
current_page: &str,
|
current_page: &str,
|
||||||
all_pages: &[String],
|
all_pages: &[String],
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
heading: &str,
|
||||||
|
level: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
if all_pages.is_empty() {
|
if all_pages.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let new_text = build_links_text(all_pages, LINKS_HEADING, Some(current_page));
|
let new_text = build_links_text(all_pages, heading, level, Some(current_page));
|
||||||
let mut b = WorkspaceEditBuilder::new();
|
let mut b = WorkspaceEditBuilder::new();
|
||||||
match find_section_range(ast, LINKS_HEADING) {
|
match find_section_range(ast, heading) {
|
||||||
Some((start, end)) => {
|
Some((start, end)) => {
|
||||||
let span = Span::new(start, end);
|
let span = Span::new(start, end);
|
||||||
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
||||||
@@ -3118,6 +3142,8 @@ pub mod ops {
|
|||||||
pub fn build_tag_links_text(
|
pub fn build_tag_links_text(
|
||||||
pages_by_tag: &std::collections::BTreeMap<String, Vec<String>>,
|
pages_by_tag: &std::collections::BTreeMap<String, Vec<String>>,
|
||||||
tag: Option<&str>,
|
tag: Option<&str>,
|
||||||
|
header: &str,
|
||||||
|
level: u8,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
match tag {
|
match tag {
|
||||||
Some(t) => {
|
Some(t) => {
|
||||||
@@ -3125,7 +3151,7 @@ pub mod ops {
|
|||||||
if pages.is_empty() {
|
if pages.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut out = format!("= Tag: {t} =\n");
|
let mut out = caption_line(&format!("Tag: {t}"), level);
|
||||||
for p in pages {
|
for p in pages {
|
||||||
out.push_str(&format!("- [[{p}]]\n"));
|
out.push_str(&format!("- [[{p}]]\n"));
|
||||||
}
|
}
|
||||||
@@ -3135,9 +3161,11 @@ pub mod ops {
|
|||||||
if pages_by_tag.is_empty() {
|
if pages_by_tag.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut out = String::from("= Tags =\n");
|
let mut out = caption_line(header, level);
|
||||||
|
let sub = level.saturating_add(1);
|
||||||
for (name, pages) in pages_by_tag {
|
for (name, pages) in pages_by_tag {
|
||||||
out.push_str(&format!("\n== {name} ==\n"));
|
out.push('\n');
|
||||||
|
out.push_str(&caption_line(name, sub));
|
||||||
for p in pages {
|
for p in pages {
|
||||||
out.push_str(&format!("- [[{p}]]\n"));
|
out.push_str(&format!("- [[{p}]]\n"));
|
||||||
}
|
}
|
||||||
@@ -3148,11 +3176,12 @@ pub mod ops {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Heading name used to locate an existing tag-links section so
|
/// Heading name used to locate an existing tag-links section so
|
||||||
/// re-generation is idempotent.
|
/// re-generation is idempotent. The no-arg index uses the configured
|
||||||
fn tag_section_heading(tag: Option<&str>) -> String {
|
/// `tags_header`; a single-tag section uses `Tag: <name>`.
|
||||||
|
fn tag_section_heading(tag: Option<&str>, header: &str) -> String {
|
||||||
match tag {
|
match tag {
|
||||||
Some(t) => format!("Tag: {t}"),
|
Some(t) => format!("Tag: {t}"),
|
||||||
None => "Tags".to_string(),
|
None => header.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3160,6 +3189,7 @@ pub mod ops {
|
|||||||
/// section (case-insensitive match on the h1 title) or inserts at the
|
/// section (case-insensitive match on the h1 title) or inserts at the
|
||||||
/// top of the document when no such section exists. Returns `None`
|
/// top of the document when no such section exists. Returns `None`
|
||||||
/// when there are no matching tagged pages.
|
/// when there are no matching tagged pages.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn tag_links_edit(
|
pub fn tag_links_edit(
|
||||||
text: &str,
|
text: &str,
|
||||||
ast: &DocumentNode,
|
ast: &DocumentNode,
|
||||||
@@ -3167,9 +3197,11 @@ pub mod ops {
|
|||||||
tag: Option<&str>,
|
tag: Option<&str>,
|
||||||
pages_by_tag: &std::collections::BTreeMap<String, Vec<String>>,
|
pages_by_tag: &std::collections::BTreeMap<String, Vec<String>>,
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
header: &str,
|
||||||
|
level: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let body = build_tag_links_text(pages_by_tag, tag)?;
|
let body = build_tag_links_text(pages_by_tag, tag, header, level)?;
|
||||||
let heading = tag_section_heading(tag);
|
let heading = tag_section_heading(tag, header);
|
||||||
let mut b = WorkspaceEditBuilder::new();
|
let mut b = WorkspaceEditBuilder::new();
|
||||||
match find_section_range(ast, &heading) {
|
match find_section_range(ast, &heading) {
|
||||||
Some((start, end)) => {
|
Some((start, end)) => {
|
||||||
|
|||||||
@@ -75,6 +75,18 @@ pub struct WikiConfig {
|
|||||||
pub links_space_char: String,
|
pub links_space_char: String,
|
||||||
/// Rebuild the page's TOC on save when set.
|
/// Rebuild the page's TOC on save when set.
|
||||||
pub auto_toc: bool,
|
pub auto_toc: bool,
|
||||||
|
/// Heading text + level for the generated `:VimwikiTOC` section
|
||||||
|
/// (vimwiki's `toc_header` / `toc_header_level`).
|
||||||
|
pub toc_header: String,
|
||||||
|
pub toc_header_level: u8,
|
||||||
|
/// Heading text + level for `:VimwikiGenerateLinks` (`links_header` /
|
||||||
|
/// `links_header_level`).
|
||||||
|
pub links_header: String,
|
||||||
|
pub links_header_level: u8,
|
||||||
|
/// Heading text + level for the no-arg `:VimwikiGenerateTagLinks` index
|
||||||
|
/// (`tags_header` / `tags_header_level`).
|
||||||
|
pub tags_header: String,
|
||||||
|
pub tags_header_level: u8,
|
||||||
/// HTML export options.
|
/// HTML export options.
|
||||||
pub html: HtmlConfig,
|
pub html: HtmlConfig,
|
||||||
}
|
}
|
||||||
@@ -196,6 +208,12 @@ impl WikiConfig {
|
|||||||
list_margin: d.list_margin,
|
list_margin: d.list_margin,
|
||||||
links_space_char: d.links_space_char,
|
links_space_char: d.links_space_char,
|
||||||
auto_toc: d.auto_toc,
|
auto_toc: d.auto_toc,
|
||||||
|
toc_header: d.toc_header,
|
||||||
|
toc_header_level: d.toc_header_level,
|
||||||
|
links_header: d.links_header,
|
||||||
|
links_header_level: d.links_header_level,
|
||||||
|
tags_header: d.tags_header,
|
||||||
|
tags_header_level: d.tags_header_level,
|
||||||
html: HtmlConfig::default(),
|
html: HtmlConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,6 +244,12 @@ impl WikiConfig {
|
|||||||
list_margin: d.list_margin,
|
list_margin: d.list_margin,
|
||||||
links_space_char: d.links_space_char,
|
links_space_char: d.links_space_char,
|
||||||
auto_toc: d.auto_toc,
|
auto_toc: d.auto_toc,
|
||||||
|
toc_header: d.toc_header,
|
||||||
|
toc_header_level: d.toc_header_level,
|
||||||
|
links_header: d.links_header,
|
||||||
|
links_header_level: d.links_header_level,
|
||||||
|
tags_header: d.tags_header,
|
||||||
|
tags_header_level: d.tags_header_level,
|
||||||
html,
|
html,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,6 +346,18 @@ fn default_listsym_rejected() -> String {
|
|||||||
"-".to_string()
|
"-".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_toc_header() -> String {
|
||||||
|
"Contents".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_links_header() -> String {
|
||||||
|
"Generated Links".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_tags_header() -> String {
|
||||||
|
"Generated Tags".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
fn default_links_space_char() -> String {
|
fn default_links_space_char() -> String {
|
||||||
" ".to_string()
|
" ".to_string()
|
||||||
}
|
}
|
||||||
@@ -348,6 +384,12 @@ fn wiki_defaults() -> WikiDefaults {
|
|||||||
list_margin: -1,
|
list_margin: -1,
|
||||||
links_space_char: default_links_space_char(),
|
links_space_char: default_links_space_char(),
|
||||||
auto_toc: false,
|
auto_toc: false,
|
||||||
|
toc_header: default_toc_header(),
|
||||||
|
toc_header_level: 1,
|
||||||
|
links_header: default_links_header(),
|
||||||
|
links_header_level: 1,
|
||||||
|
tags_header: default_tags_header(),
|
||||||
|
tags_header_level: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,6 +409,12 @@ struct WikiDefaults {
|
|||||||
list_margin: i32,
|
list_margin: i32,
|
||||||
links_space_char: String,
|
links_space_char: String,
|
||||||
auto_toc: bool,
|
auto_toc: bool,
|
||||||
|
toc_header: String,
|
||||||
|
toc_header_level: u8,
|
||||||
|
links_header: String,
|
||||||
|
links_header_level: u8,
|
||||||
|
tags_header: String,
|
||||||
|
tags_header_level: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -593,6 +641,18 @@ struct RawWiki {
|
|||||||
links_space_char: Option<String>,
|
links_space_char: Option<String>,
|
||||||
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
auto_toc: Option<bool>,
|
auto_toc: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
toc_header: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
toc_header_level: Option<u8>,
|
||||||
|
#[serde(default)]
|
||||||
|
links_header: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
links_header_level: Option<u8>,
|
||||||
|
#[serde(default)]
|
||||||
|
tags_header: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
tags_header_level: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RawWiki> for WikiConfig {
|
impl From<RawWiki> for WikiConfig {
|
||||||
@@ -654,6 +714,12 @@ impl From<RawWiki> for WikiConfig {
|
|||||||
list_margin: r.list_margin.unwrap_or(d.list_margin),
|
list_margin: r.list_margin.unwrap_or(d.list_margin),
|
||||||
links_space_char: r.links_space_char.unwrap_or(d.links_space_char),
|
links_space_char: r.links_space_char.unwrap_or(d.links_space_char),
|
||||||
auto_toc: r.auto_toc.unwrap_or(d.auto_toc),
|
auto_toc: r.auto_toc.unwrap_or(d.auto_toc),
|
||||||
|
toc_header: r.toc_header.unwrap_or(d.toc_header),
|
||||||
|
toc_header_level: r.toc_header_level.unwrap_or(d.toc_header_level),
|
||||||
|
links_header: r.links_header.unwrap_or(d.links_header),
|
||||||
|
links_header_level: r.links_header_level.unwrap_or(d.links_header_level),
|
||||||
|
tags_header: r.tags_header.unwrap_or(d.tags_header),
|
||||||
|
tags_header_level: r.tags_header_level.unwrap_or(d.tags_header_level),
|
||||||
html,
|
html,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,6 +625,8 @@ impl LanguageServer for Backend {
|
|||||||
&doc.ast,
|
&doc.ast,
|
||||||
&uri,
|
&uri,
|
||||||
self.use_utf8.load(Ordering::Relaxed),
|
self.use_utf8.load(Ordering::Relaxed),
|
||||||
|
&wiki.config.toc_header,
|
||||||
|
wiki.config.toc_header_level,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ fn nuwiki_toc_generates_nested_contents() {
|
|||||||
let src = "= Top =\n== Sub ==\ntext\n";
|
let src = "= Top =\n== Sub ==\ntext\n";
|
||||||
let doc = parse(src);
|
let doc = parse(src);
|
||||||
let uri = Url::from_file_path("/wiki/Page.wiki").unwrap();
|
let uri = Url::from_file_path("/wiki/Page.wiki").unwrap();
|
||||||
let edit = ops::toc_edit(src, &doc, &uri, true).expect("toc edit produced");
|
let edit = ops::toc_edit(src, &doc, &uri, true, "Contents", 1).expect("toc edit produced");
|
||||||
assert!(edit.changes.is_some() || edit.document_changes.is_some());
|
assert!(edit.changes.is_some() || edit.document_changes.is_some());
|
||||||
|
|
||||||
let toc = ops::build_toc_text(
|
let toc = ops::build_toc_text(
|
||||||
@@ -307,6 +307,7 @@ fn nuwiki_toc_generates_nested_contents() {
|
|||||||
(2, "Sub".into(), "sub".into()),
|
(2, "Sub".into(), "sub".into()),
|
||||||
],
|
],
|
||||||
"Contents",
|
"Contents",
|
||||||
|
1,
|
||||||
);
|
);
|
||||||
assert!(toc.contains("= Contents ="));
|
assert!(toc.contains("= Contents ="));
|
||||||
assert!(toc.contains("- [[#top|Top]]"));
|
assert!(toc.contains("- [[#top|Top]]"));
|
||||||
@@ -317,7 +318,7 @@ fn nuwiki_toc_generates_nested_contents() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn nuwiki_generate_links_lists_all_pages_excluding_current() {
|
fn nuwiki_generate_links_lists_all_pages_excluding_current() {
|
||||||
let pages = vec!["About".to_string(), "Home".to_string(), "Notes".to_string()];
|
let pages = vec!["About".to_string(), "Home".to_string(), "Notes".to_string()];
|
||||||
let text = ops::build_links_text(&pages, "Links", Some("Home"));
|
let text = ops::build_links_text(&pages, "Links", 1, Some("Home"));
|
||||||
assert!(text.contains("= Links ="));
|
assert!(text.contains("= Links ="));
|
||||||
assert!(text.contains("- [[About]]"));
|
assert!(text.contains("- [[About]]"));
|
||||||
assert!(text.contains("- [[Notes]]"));
|
assert!(text.contains("- [[Notes]]"));
|
||||||
@@ -326,7 +327,7 @@ fn nuwiki_generate_links_lists_all_pages_excluding_current() {
|
|||||||
let src = "= Existing =\n";
|
let src = "= Existing =\n";
|
||||||
let doc = parse(src);
|
let doc = parse(src);
|
||||||
let uri = Url::from_file_path("/wiki/Home.wiki").unwrap();
|
let uri = Url::from_file_path("/wiki/Home.wiki").unwrap();
|
||||||
assert!(ops::links_edit(src, &doc, &uri, "Home", &pages, true).is_some());
|
assert!(ops::links_edit(src, &doc, &uri, "Home", &pages, true, "Generated Links", 1).is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
// :NuwikiCheckLinks — surface broken links across the workspace.
|
// :NuwikiCheckLinks — surface broken links across the workspace.
|
||||||
@@ -383,21 +384,31 @@ fn nuwiki_generate_tag_links_builds_section() {
|
|||||||
);
|
);
|
||||||
let by_tag = ops::tag_pages_snapshot(&idx);
|
let by_tag = ops::tag_pages_snapshot(&idx);
|
||||||
|
|
||||||
let single = ops::build_tag_links_text(&by_tag, Some("alpha")).unwrap();
|
let single = ops::build_tag_links_text(&by_tag, Some("alpha"), "Generated Tags", 1).unwrap();
|
||||||
assert!(single.starts_with("= Tag: alpha ="));
|
assert!(single.starts_with("= Tag: alpha ="));
|
||||||
assert!(single.contains("- [[Home]]"));
|
assert!(single.contains("- [[Home]]"));
|
||||||
assert!(single.contains("- [[Work]]"));
|
assert!(single.contains("- [[Work]]"));
|
||||||
|
|
||||||
// No-arg variant emits a section per tag.
|
// No-arg variant emits a section per tag.
|
||||||
let all = ops::build_tag_links_text(&by_tag, None).unwrap();
|
let all = ops::build_tag_links_text(&by_tag, None, "Generated Tags", 1).unwrap();
|
||||||
assert!(all.starts_with("= Tags ="));
|
assert!(all.starts_with("= Generated Tags ="));
|
||||||
assert!(all.contains("== alpha =="));
|
assert!(all.contains("== alpha =="));
|
||||||
assert!(all.contains("== beta =="));
|
assert!(all.contains("== beta =="));
|
||||||
|
|
||||||
let src = "= Home =\n";
|
let src = "= Home =\n";
|
||||||
let doc = parse(src);
|
let doc = parse(src);
|
||||||
let uri = Url::from_file_path("/wiki/Home.wiki").unwrap();
|
let uri = Url::from_file_path("/wiki/Home.wiki").unwrap();
|
||||||
assert!(ops::tag_links_edit(src, &doc, &uri, Some("alpha"), &by_tag, true).is_some());
|
assert!(ops::tag_links_edit(
|
||||||
|
src,
|
||||||
|
&doc,
|
||||||
|
&uri,
|
||||||
|
Some("alpha"),
|
||||||
|
&by_tag,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
// :NuwikiRebuildTags — force a full workspace re-index. A rebuild must
|
// :NuwikiRebuildTags — force a full workspace re-index. A rebuild must
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ fn tag_pages_snapshot_sorts_pages_alphabetically() {
|
|||||||
fn build_tag_links_for_single_tag() {
|
fn build_tag_links_for_single_tag() {
|
||||||
let mut snap = BTreeMap::new();
|
let mut snap = BTreeMap::new();
|
||||||
snap.insert("release".to_string(), vec!["Alpha".into(), "Beta".into()]);
|
snap.insert("release".to_string(), vec!["Alpha".into(), "Beta".into()]);
|
||||||
let out = ops::build_tag_links_text(&snap, Some("release")).unwrap();
|
let out = ops::build_tag_links_text(&snap, Some("release"), "Generated Tags", 1).unwrap();
|
||||||
let lines: Vec<&str> = out.lines().collect();
|
let lines: Vec<&str> = out.lines().collect();
|
||||||
assert_eq!(lines[0], "= Tag: release =");
|
assert_eq!(lines[0], "= Tag: release =");
|
||||||
assert_eq!(lines[1], "- [[Alpha]]");
|
assert_eq!(lines[1], "- [[Alpha]]");
|
||||||
@@ -213,7 +213,7 @@ fn build_tag_links_for_single_tag() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn build_tag_links_for_missing_tag_returns_none() {
|
fn build_tag_links_for_missing_tag_returns_none() {
|
||||||
let snap = BTreeMap::new();
|
let snap = BTreeMap::new();
|
||||||
assert!(ops::build_tag_links_text(&snap, Some("ghost")).is_none());
|
assert!(ops::build_tag_links_text(&snap, Some("ghost"), "Generated Tags", 1).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -221,8 +221,8 @@ fn build_tag_links_full_index_groups_by_tag() {
|
|||||||
let mut snap = BTreeMap::new();
|
let mut snap = BTreeMap::new();
|
||||||
snap.insert("a".to_string(), vec!["P1".into()]);
|
snap.insert("a".to_string(), vec!["P1".into()]);
|
||||||
snap.insert("b".to_string(), vec!["P2".into(), "P3".into()]);
|
snap.insert("b".to_string(), vec!["P2".into(), "P3".into()]);
|
||||||
let out = ops::build_tag_links_text(&snap, None).unwrap();
|
let out = ops::build_tag_links_text(&snap, None, "Generated Tags", 1).unwrap();
|
||||||
assert!(out.starts_with("= Tags =\n"));
|
assert!(out.starts_with("= Generated Tags =\n"));
|
||||||
assert!(out.contains("== a =="));
|
assert!(out.contains("== a =="));
|
||||||
assert!(out.contains("== b =="));
|
assert!(out.contains("== b =="));
|
||||||
assert!(out.contains("- [[P1]]"));
|
assert!(out.contains("- [[P1]]"));
|
||||||
@@ -233,7 +233,7 @@ fn build_tag_links_full_index_groups_by_tag() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn build_tag_links_full_index_returns_none_when_no_tags() {
|
fn build_tag_links_full_index_returns_none_when_no_tags() {
|
||||||
let snap = BTreeMap::new();
|
let snap = BTreeMap::new();
|
||||||
assert!(ops::build_tag_links_text(&snap, None).is_none());
|
assert!(ops::build_tag_links_text(&snap, None, "Generated Tags", 1).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== `nuwiki.tags.generateLinks` — tag_links_edit (in-buffer rewrite) =====
|
// ===== `nuwiki.tags.generateLinks` — tag_links_edit (in-buffer rewrite) =====
|
||||||
@@ -245,8 +245,17 @@ fn tag_links_edit_inserts_when_section_missing() {
|
|||||||
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
||||||
let mut snap = BTreeMap::new();
|
let mut snap = BTreeMap::new();
|
||||||
snap.insert("release".to_string(), vec!["Alpha".into()]);
|
snap.insert("release".to_string(), vec!["Alpha".into()]);
|
||||||
let edit =
|
let edit = ops::tag_links_edit(
|
||||||
ops::tag_links_edit(src, &ast, &uri, Some("release"), &snap, true).expect("got an edit");
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
Some("release"),
|
||||||
|
&snap,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("got an edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
// Insertion at start.
|
// Insertion at start.
|
||||||
assert_eq!(te.range.start, te.range.end);
|
assert_eq!(te.range.start, te.range.end);
|
||||||
@@ -261,7 +270,17 @@ fn tag_links_edit_replaces_existing_section() {
|
|||||||
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
||||||
let mut snap = BTreeMap::new();
|
let mut snap = BTreeMap::new();
|
||||||
snap.insert("release".to_string(), vec!["Fresh".into()]);
|
snap.insert("release".to_string(), vec!["Fresh".into()]);
|
||||||
let edit = ops::tag_links_edit(src, &ast, &uri, Some("release"), &snap, true).expect("edit");
|
let edit = ops::tag_links_edit(
|
||||||
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
Some("release"),
|
||||||
|
&snap,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
assert_eq!(te.range.start.line, 0);
|
assert_eq!(te.range.start.line, 0);
|
||||||
assert!(te.new_text.contains("[[Fresh]]"));
|
assert!(te.new_text.contains("[[Fresh]]"));
|
||||||
@@ -270,12 +289,13 @@ fn tag_links_edit_replaces_existing_section() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag_links_edit_full_index_replaces_existing_tags_section() {
|
fn tag_links_edit_full_index_replaces_existing_tags_section() {
|
||||||
let src = "= Tags =\n- [[old]]\n";
|
let src = "= Generated Tags =\n- [[old]]\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
||||||
let mut snap = BTreeMap::new();
|
let mut snap = BTreeMap::new();
|
||||||
snap.insert("alpha".to_string(), vec!["P".into()]);
|
snap.insert("alpha".to_string(), vec!["P".into()]);
|
||||||
let edit = ops::tag_links_edit(src, &ast, &uri, None, &snap, true).expect("edit");
|
let edit =
|
||||||
|
ops::tag_links_edit(src, &ast, &uri, None, &snap, true, "Generated Tags", 1).expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
assert!(te.new_text.contains("== alpha =="));
|
assert!(te.new_text.contains("== alpha =="));
|
||||||
assert!(!te.new_text.contains("[[old]]"));
|
assert!(!te.new_text.contains("[[old]]"));
|
||||||
@@ -287,7 +307,17 @@ fn tag_links_edit_returns_none_for_unknown_tag() {
|
|||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
||||||
let snap = BTreeMap::new();
|
let snap = BTreeMap::new();
|
||||||
assert!(ops::tag_links_edit(src, &ast, &uri, Some("ghost"), &snap, true).is_none());
|
assert!(ops::tag_links_edit(
|
||||||
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
Some("ghost"),
|
||||||
|
&snap,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== smoke: COMMANDS list advertises tag commands =====
|
// ===== smoke: COMMANDS list advertises tag commands =====
|
||||||
|
|||||||
@@ -275,6 +275,12 @@ fn defaults_carry_vimwiki_per_wiki_keys() {
|
|||||||
assert_eq!(cfg.list_margin, -1);
|
assert_eq!(cfg.list_margin, -1);
|
||||||
assert_eq!(cfg.links_space_char, " ");
|
assert_eq!(cfg.links_space_char, " ");
|
||||||
assert!(!cfg.auto_toc);
|
assert!(!cfg.auto_toc);
|
||||||
|
// Generated-section captions match upstream defaults.
|
||||||
|
assert_eq!(cfg.toc_header, "Contents");
|
||||||
|
assert_eq!(cfg.toc_header_level, 1);
|
||||||
|
assert_eq!(cfg.links_header, "Generated Links");
|
||||||
|
assert_eq!(cfg.tags_header, "Generated Tags");
|
||||||
|
assert_eq!(cfg.listsym_rejected, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -295,6 +301,11 @@ fn raw_wiki_parses_every_new_key() {
|
|||||||
"list_margin": 2,
|
"list_margin": 2,
|
||||||
"links_space_char": "_",
|
"links_space_char": "_",
|
||||||
"auto_toc": true,
|
"auto_toc": true,
|
||||||
|
"toc_header": "Table of Contents",
|
||||||
|
"toc_header_level": 2,
|
||||||
|
"links_header": "All Pages",
|
||||||
|
"tags_header": "Tag Index",
|
||||||
|
"tags_header_level": 3,
|
||||||
}],
|
}],
|
||||||
}));
|
}));
|
||||||
let w = &cfg.wikis[0];
|
let w = &cfg.wikis[0];
|
||||||
@@ -312,6 +323,12 @@ fn raw_wiki_parses_every_new_key() {
|
|||||||
assert_eq!(w.list_margin, 2);
|
assert_eq!(w.list_margin, 2);
|
||||||
assert_eq!(w.links_space_char, "_");
|
assert_eq!(w.links_space_char, "_");
|
||||||
assert!(w.auto_toc);
|
assert!(w.auto_toc);
|
||||||
|
assert_eq!(w.toc_header, "Table of Contents");
|
||||||
|
assert_eq!(w.toc_header_level, 2);
|
||||||
|
assert_eq!(w.links_header, "All Pages");
|
||||||
|
assert_eq!(w.links_header_level, 1); // unspecified → default
|
||||||
|
assert_eq!(w.tags_header, "Tag Index");
|
||||||
|
assert_eq!(w.tags_header_level, 3);
|
||||||
|
|
||||||
// The parsed keys drive a date-mode, Sunday-start diary calendar:
|
// The parsed keys drive a date-mode, Sunday-start diary calendar:
|
||||||
// a weekly note is the week-start date (YYYY-MM-DD), stepping ±7 days.
|
// a weekly note is the week-start date (YYYY-MM-DD), stepping ±7 days.
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ fn build_toc_text_nests_by_level() {
|
|||||||
(2u8, "Sub".into(), "sub".into()),
|
(2u8, "Sub".into(), "sub".into()),
|
||||||
(1u8, "Other".into(), "other".into()),
|
(1u8, "Other".into(), "other".into()),
|
||||||
];
|
];
|
||||||
let out = ops::build_toc_text(&items, "Contents");
|
let out = ops::build_toc_text(&items, "Contents", 1);
|
||||||
assert!(out.starts_with("= Contents =\n"));
|
assert!(out.starts_with("= Contents =\n"));
|
||||||
let lines: Vec<&str> = out.lines().collect();
|
let lines: Vec<&str> = out.lines().collect();
|
||||||
assert_eq!(lines[0], "= Contents =");
|
assert_eq!(lines[0], "= Contents =");
|
||||||
@@ -301,16 +301,30 @@ fn build_toc_text_nests_by_level() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_toc_text_with_no_headings_is_just_the_heading() {
|
fn build_toc_text_with_no_headings_is_just_the_heading() {
|
||||||
let out = ops::build_toc_text(&[], "Contents");
|
let out = ops::build_toc_text(&[], "Contents", 1);
|
||||||
assert_eq!(out, "= Contents =\n");
|
assert_eq!(out, "= Contents =\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn captions_honour_custom_header_and_level() {
|
||||||
|
// toc_header="Table of Contents", level 2 → `== Table of Contents ==`.
|
||||||
|
let out = ops::build_toc_text(&[], "Table of Contents", 2);
|
||||||
|
assert_eq!(out, "== Table of Contents ==\n");
|
||||||
|
// links_header at level 3.
|
||||||
|
let pages = vec!["A".to_string(), "B".to_string()];
|
||||||
|
let links = ops::build_links_text(&pages, "All Pages", 3, None);
|
||||||
|
assert!(links.starts_with("=== All Pages ===\n"));
|
||||||
|
// level clamps to 1..=6.
|
||||||
|
assert!(ops::build_toc_text(&[], "X", 9).starts_with("====== X ======"));
|
||||||
|
assert!(ops::build_toc_text(&[], "X", 0).starts_with("= X ="));
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Links text generation =====
|
// ===== Links text generation =====
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_links_text_excludes_current_page() {
|
fn build_links_text_excludes_current_page() {
|
||||||
let pages = vec!["A".to_string(), "Home".to_string(), "B".to_string()];
|
let pages = vec!["A".to_string(), "Home".to_string(), "B".to_string()];
|
||||||
let out = ops::build_links_text(&pages, "Generated Links", Some("Home"));
|
let out = ops::build_links_text(&pages, "Generated Links", 1, Some("Home"));
|
||||||
let lines: Vec<&str> = out.lines().collect();
|
let lines: Vec<&str> = out.lines().collect();
|
||||||
assert_eq!(lines[0], "= Generated Links =");
|
assert_eq!(lines[0], "= Generated Links =");
|
||||||
assert!(lines.contains(&"- [[A]]"));
|
assert!(lines.contains(&"- [[A]]"));
|
||||||
@@ -321,7 +335,7 @@ fn build_links_text_excludes_current_page() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn build_links_text_no_excludes_includes_all() {
|
fn build_links_text_no_excludes_includes_all() {
|
||||||
let pages = vec!["A".to_string(), "B".to_string()];
|
let pages = vec!["A".to_string(), "B".to_string()];
|
||||||
let out = ops::build_links_text(&pages, "Generated Links", None);
|
let out = ops::build_links_text(&pages, "Generated Links", 1, None);
|
||||||
assert!(out.contains("[[A]]"));
|
assert!(out.contains("[[A]]"));
|
||||||
assert!(out.contains("[[B]]"));
|
assert!(out.contains("[[B]]"));
|
||||||
}
|
}
|
||||||
@@ -359,7 +373,7 @@ fn toc_edit_inserts_at_top_when_no_existing_toc() {
|
|||||||
let src = "= One =\n== Two ==\n";
|
let src = "= One =\n== Two ==\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
let edit = ops::toc_edit(src, &ast, &uri, true).expect("got an edit");
|
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1).expect("got an edit");
|
||||||
let changes = edit.changes.expect("changes map");
|
let changes = edit.changes.expect("changes map");
|
||||||
let edits = &changes[&uri];
|
let edits = &changes[&uri];
|
||||||
assert_eq!(edits.len(), 1);
|
assert_eq!(edits.len(), 1);
|
||||||
@@ -375,7 +389,7 @@ fn toc_edit_replaces_existing_toc() {
|
|||||||
let src = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
|
let src = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
let edit = ops::toc_edit(src, &ast, &uri, true).expect("got an edit");
|
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1).expect("got an edit");
|
||||||
let changes = edit.changes.expect("changes map");
|
let changes = edit.changes.expect("changes map");
|
||||||
let edits = &changes[&uri];
|
let edits = &changes[&uri];
|
||||||
let te = &edits[0];
|
let te = &edits[0];
|
||||||
@@ -390,7 +404,7 @@ fn toc_edit_returns_none_for_empty_doc() {
|
|||||||
let src = "";
|
let src = "";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/empty.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/empty.wiki").unwrap();
|
||||||
assert!(ops::toc_edit(src, &ast, &uri, true).is_none());
|
assert!(ops::toc_edit(src, &ast, &uri, true, "Contents", 1).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -401,14 +415,14 @@ fn toc_rebuild_edit_only_acts_when_toc_already_present() {
|
|||||||
let without = "= One =\n== Two ==\n";
|
let without = "= One =\n== Two ==\n";
|
||||||
let ast = parse(without);
|
let ast = parse(without);
|
||||||
assert!(
|
assert!(
|
||||||
ops::toc_rebuild_edit(without, &ast, &uri, true).is_none(),
|
ops::toc_rebuild_edit(without, &ast, &uri, true, "Contents", 1).is_none(),
|
||||||
"auto_toc should not insert a TOC where none existed"
|
"auto_toc should not insert a TOC where none existed"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Existing TOC → rebuild refreshes it.
|
// Existing TOC → rebuild refreshes it.
|
||||||
let with = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
|
let with = "= Contents =\n- [[#stale|Stale]]\n\n= Real =\n";
|
||||||
let ast = parse(with);
|
let ast = parse(with);
|
||||||
let edit = ops::toc_rebuild_edit(with, &ast, &uri, true).expect("rebuild edit");
|
let edit = ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1).expect("rebuild edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
assert!(te.new_text.contains("[[#real|Real]]"));
|
assert!(te.new_text.contains("[[#real|Real]]"));
|
||||||
assert!(!te.new_text.contains("Stale"));
|
assert!(!te.new_text.contains("Stale"));
|
||||||
@@ -422,7 +436,8 @@ fn links_edit_inserts_when_section_absent() {
|
|||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
let pages = vec!["A".into(), "B".into(), "Home".into()];
|
let pages = vec!["A".into(), "B".into(), "Home".into()];
|
||||||
let edit = ops::links_edit(src, &ast, &uri, "Home", &pages, true).expect("edit");
|
let edit =
|
||||||
|
ops::links_edit(src, &ast, &uri, "Home", &pages, true, "Generated Links", 1).expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
assert!(te.new_text.contains("[[A]]"));
|
assert!(te.new_text.contains("[[A]]"));
|
||||||
assert!(te.new_text.contains("[[B]]"));
|
assert!(te.new_text.contains("[[B]]"));
|
||||||
@@ -435,7 +450,8 @@ fn links_edit_replaces_when_section_present() {
|
|||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
let pages = vec!["Fresh".into()];
|
let pages = vec!["Fresh".into()];
|
||||||
let edit = ops::links_edit(src, &ast, &uri, "Home", &pages, true).expect("edit");
|
let edit =
|
||||||
|
ops::links_edit(src, &ast, &uri, "Home", &pages, true, "Generated Links", 1).expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
assert!(te.new_text.contains("[[Fresh]]"));
|
assert!(te.new_text.contains("[[Fresh]]"));
|
||||||
assert!(!te.new_text.contains("Stale"));
|
assert!(!te.new_text.contains("Stale"));
|
||||||
@@ -447,7 +463,7 @@ fn links_edit_returns_none_for_empty_page_list() {
|
|||||||
let src = "Hi\n";
|
let src = "Hi\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
assert!(ops::links_edit(src, &ast, &uri, "Home", &[], true).is_none());
|
assert!(ops::links_edit(src, &ast, &uri, "Home", &[], true, "Generated Links", 1).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== find_orphans =====
|
// ===== find_orphans =====
|
||||||
|
|||||||
@@ -87,9 +87,17 @@ fix site.
|
|||||||
`<Leader>w<Leader>m` stays tomorrow — exact upstream parity
|
`<Leader>w<Leader>m` stays tomorrow — exact upstream parity
|
||||||
(`lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`; docs + README updated).
|
(`lua/nuwiki/keymaps.lua`, `ftplugin/vimwiki.vim`; docs + README updated).
|
||||||
Covered by `diary.leader_t_opens_today_in_tab` / `diary.leader_m_opens_tomorrow`.
|
Covered by `diary.leader_t_opens_today_in_tab` / `diary.leader_m_opens_tomorrow`.
|
||||||
- [ ] **Generated-section captions hardcoded** — `toc_header` /
|
- [x] **Generated-section captions hardcoded** — TOC/links/tags headings and
|
||||||
`toc_header_level`, `links_header`, `tags_header` (+levels).
|
their levels were hardcoded (`= Contents =`, `= Generated Links =`, `= Tags =`,
|
||||||
_Fix:_ `crates/nuwiki-lsp/src/config.rs` + server TOC/generate renderers.
|
all level 1). _Fix:_ added per-wiki `toc_header`/`toc_header_level`,
|
||||||
|
`links_header`/`links_header_level`, `tags_header`/`tags_header_level`
|
||||||
|
(defaults `Contents`/`Generated Links`/`Generated Tags`, level 1 — matching
|
||||||
|
upstream; this also corrects the tags index heading from `Tags` →
|
||||||
|
`Generated Tags`). A `caption_line(name, level)` helper emits the markers; the
|
||||||
|
text + level thread through `toc_edit`/`links_edit`/`tag_links_edit` (+ the
|
||||||
|
auto_toc save hook) and `find_section_range` matches the configured text so
|
||||||
|
regen stays idempotent. Tests: `captions_honour_custom_header_and_level`
|
||||||
|
(`link_health.rs`) + config round-trip in `index_and_config.rs`.
|
||||||
- [ ] **On-save autoregen family** — `auto_diary_index`, `auto_generate_links`,
|
- [ ] **On-save autoregen family** — `auto_diary_index`, `auto_generate_links`,
|
||||||
`auto_generate_tags`, `auto_tags` (only `auto_toc` / `auto_export` exist).
|
`auto_generate_tags`, `auto_tags` (only `auto_toc` / `auto_export` exist).
|
||||||
_Fix:_ `config.rs` `RawWiki`/`WikiConfig` + the `didSave` path.
|
_Fix:_ `config.rs` `RawWiki`/`WikiConfig` + the `didSave` path.
|
||||||
|
|||||||
@@ -196,6 +196,9 @@ match upstream vimwiki.
|
|||||||
`css_name` `'style.css'`
|
`css_name` `'style.css'`
|
||||||
`auto_export` `false` — export to HTML on save.
|
`auto_export` `false` — export to HTML on save.
|
||||||
`auto_toc` `false` — refresh TOC on save.
|
`auto_toc` `false` — refresh TOC on save.
|
||||||
|
`toc_header` `'Contents'` (+ `toc_header_level` `1`) — :VimwikiTOC.
|
||||||
|
`links_header` `'Generated Links'` (+ `links_header_level` `1`).
|
||||||
|
`tags_header` `'Generated Tags'` (+ `tags_header_level` `1`).
|
||||||
`exclude_files` List of glob patterns excluded from export.
|
`exclude_files` List of glob patterns excluded from export.
|
||||||
`listsyms` `' .oOX'` — checkbox progression characters.
|
`listsyms` `' .oOX'` — checkbox progression characters.
|
||||||
`listsym_rejected` `'-'` — glyph for a cancelled checkbox `[-]`.
|
`listsym_rejected` `'-'` — glyph for a cancelled checkbox `[-]`.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ M.defaults = {
|
|||||||
-- html_path, template_path, template_default, template_ext,
|
-- html_path, template_path, template_default, template_ext,
|
||||||
-- template_date_format, css_name, auto_export, auto_toc,
|
-- template_date_format, css_name, auto_export, auto_toc,
|
||||||
-- html_filename_parameterization, exclude_files, color_dic
|
-- html_filename_parameterization, exclude_files, color_dic
|
||||||
|
-- toc_header, toc_header_level, links_header, links_header_level,
|
||||||
|
-- tags_header, tags_header_level,
|
||||||
-- listsyms, listsym_rejected, listsyms_propagate, list_margin,
|
-- listsyms, listsym_rejected, listsyms_propagate, list_margin,
|
||||||
-- links_space_char
|
-- links_space_char
|
||||||
wikis = nil,
|
wikis = nil,
|
||||||
|
|||||||
Reference in New Issue
Block a user