feat(config): P3 config batch — group 1 (clean config + plumbing)
Config plumbing for the whole config batch (HtmlConfig + WikiConfig fields, RawWiki deserialization, From/defaults) plus these behaviors: - rss_name / rss_max_items: HtmlConfig fields (wired into write_rss in the RSS group). - toc_link_format: build_toc_text emits [[#anchor]] (no description) for 1, [[#anchor|title]] for 0. - generated_links_caption: build_links_text emits [[page|FirstHeading]] from a page->heading map (ops::page_captions) when enabled. - table_reduce_last_col: column_widths clamps the last column to width 1 when set; threaded through table_align_edit/table_move_column_edit + commands. - color_dic now ships a populated default palette (red/green/blue/...). - commentstring aligned to upstream's no-space `%%%s`. - auto_chdir (default off): :lcd into the owning wiki root on buffer enter; both clients (ftplugin.lua setup_auto_chdir + Vim NuwikiAutoChdir augroup), with config.wiki_root_for / nuwiki#commands#wiki_root_for resolvers. Also seeded HtmlConfig fields for later groups (valid_html_tags, list/text_ignore_newline, emoji_enable, user_htmls, color_tag_template) and WikiConfig (create_link, dir_link, bullet_types, cycle_bullets). Tests: toc_link_format, generated_links_caption, table_reduce_last_col, config defaults + JSON round-trip for every new key. Full rust suite + both keymap harnesses green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,28 @@ endfunction
|
|||||||
" from the server, so the picker works from any buffer before a wiki file is
|
" from the server, so the picker works from any buffer before a wiki file is
|
||||||
" ever opened — opening the chosen index then auto-starts the LSP via the
|
" ever opened — opening the chosen index then auto-starts the LSP via the
|
||||||
" FileType autocmd. Plain Vim has no `vim.ui.select`, so use `inputlist()`.
|
" FileType autocmd. Plain Vim has no `vim.ui.select`, so use `inputlist()`.
|
||||||
|
" Absolute root (with trailing slash) of the wiki that owns `a:file`, or the
|
||||||
|
" first configured wiki as a fallback. Used by auto_chdir + search scoping.
|
||||||
|
function! nuwiki#commands#wiki_root_for(file) abort
|
||||||
|
for l:w in nuwiki#commands#wiki_list()
|
||||||
|
let l:wroot = fnamemodify(expand(l:w.root), ':p')
|
||||||
|
if a:file[: len(l:wroot) - 1] ==# l:wroot
|
||||||
|
return l:wroot
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
let l:wikis = nuwiki#commands#wiki_list()
|
||||||
|
return empty(l:wikis) ? '' : fnamemodify(expand(l:wikis[0].root), ':p')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" :NuwikiAutoChdir hook — `:lcd` into the owning wiki's root (vimwiki
|
||||||
|
" `auto_chdir`). Wired from a buffer-local autocmd in the ftplugin.
|
||||||
|
function! nuwiki#commands#auto_chdir() abort
|
||||||
|
let l:root = nuwiki#commands#wiki_root_for(expand('%:p'))
|
||||||
|
if l:root !=# ''
|
||||||
|
execute 'lcd ' . fnameescape(l:root)
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
" :VimwikiShowVersion / :NuwikiShowVersion — echo the nuwiki version and the
|
" :VimwikiShowVersion / :NuwikiShowVersion — echo the nuwiki version and the
|
||||||
" host editor, mirroring upstream's version banner.
|
" host editor, mirroring upstream's version banner.
|
||||||
function! nuwiki#commands#show_version() abort
|
function! nuwiki#commands#show_version() abort
|
||||||
|
|||||||
@@ -338,8 +338,16 @@ fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceE
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| w.config.list_margin.max(0) as usize)
|
.map(|w| w.config.list_margin.max(0) as usize)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
let link_format = wiki.as_ref().map(|w| w.config.toc_link_format).unwrap_or(0);
|
||||||
Ok(ops::toc_edit(
|
Ok(ops::toc_edit(
|
||||||
&doc.text, &doc.ast, &p.uri, utf8, &header, level, margin,
|
&doc.text,
|
||||||
|
&doc.ast,
|
||||||
|
&p.uri,
|
||||||
|
utf8,
|
||||||
|
&header,
|
||||||
|
level,
|
||||||
|
margin,
|
||||||
|
link_format,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,12 +363,16 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
|||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
let current_page = crate::index::page_name_from_uri(&p.uri, Some(&wiki.config.root));
|
let current_page = crate::index::page_name_from_uri(&p.uri, Some(&wiki.config.root));
|
||||||
let pages = {
|
let (pages, captions) = {
|
||||||
let idx = wiki
|
let idx = wiki
|
||||||
.index
|
.index
|
||||||
.read()
|
.read()
|
||||||
.map_err(|_| "index lock poisoned".to_string())?;
|
.map_err(|_| "index lock poisoned".to_string())?;
|
||||||
idx.page_names()
|
let captions = wiki
|
||||||
|
.config
|
||||||
|
.generated_links_caption
|
||||||
|
.then(|| ops::page_captions(&idx));
|
||||||
|
(idx.page_names(), captions)
|
||||||
};
|
};
|
||||||
Ok(ops::links_edit(
|
Ok(ops::links_edit(
|
||||||
&doc.text,
|
&doc.text,
|
||||||
@@ -372,6 +384,7 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
|||||||
&wiki.config.links_header,
|
&wiki.config.links_header,
|
||||||
wiki.config.links_header_level,
|
wiki.config.links_header_level,
|
||||||
wiki.config.list_margin.max(0) as usize,
|
wiki.config.list_margin.max(0) as usize,
|
||||||
|
captions.as_ref(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1105,8 +1118,12 @@ fn table_align(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEd
|
|||||||
};
|
};
|
||||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||||
let (line, _) = nav::lsp_to_byte_pos(p.position, &doc.text, utf8);
|
let (line, _) = nav::lsp_to_byte_pos(p.position, &doc.text, utf8);
|
||||||
|
let reduce = backend
|
||||||
|
.wiki_for_uri(&p.uri)
|
||||||
|
.map(|w| w.config.table_reduce_last_col)
|
||||||
|
.unwrap_or(false);
|
||||||
Ok(ops::table_align_edit(
|
Ok(ops::table_align_edit(
|
||||||
&doc.text, &doc.ast, &p.uri, line, utf8,
|
&doc.text, &doc.ast, &p.uri, line, utf8, reduce,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1135,6 +1152,10 @@ fn table_move_column(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
|
|||||||
};
|
};
|
||||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||||
let (line, col) = nav::lsp_to_byte_pos(parsed.position, &doc.text, utf8);
|
let (line, col) = nav::lsp_to_byte_pos(parsed.position, &doc.text, utf8);
|
||||||
|
let reduce = backend
|
||||||
|
.wiki_for_uri(&parsed.uri)
|
||||||
|
.map(|w| w.config.table_reduce_last_col)
|
||||||
|
.unwrap_or(false);
|
||||||
Ok(ops::table_move_column_edit(
|
Ok(ops::table_move_column_edit(
|
||||||
&doc.text,
|
&doc.text,
|
||||||
&doc.ast,
|
&doc.ast,
|
||||||
@@ -1143,6 +1164,7 @@ fn table_move_column(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
|
|||||||
col,
|
col,
|
||||||
delta,
|
delta,
|
||||||
utf8,
|
utf8,
|
||||||
|
reduce,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1832,6 +1854,7 @@ pub mod ops {
|
|||||||
heading_name: &str,
|
heading_name: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
link_format: u8,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut out = caption_line(heading_name, level);
|
let mut out = caption_line(heading_name, level);
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
@@ -1845,23 +1868,47 @@ pub mod ops {
|
|||||||
for _ in 0..depth {
|
for _ in 0..depth {
|
||||||
out.push_str(" ");
|
out.push_str(" ");
|
||||||
}
|
}
|
||||||
out.push_str("- [[#");
|
// vimwiki `toc_link_format`: 0 = `[[#anchor|title]]` (with
|
||||||
out.push_str(anchor);
|
// description), 1 = `[[#anchor]]` (anchor only).
|
||||||
out.push('|');
|
if link_format == 1 {
|
||||||
out.push_str(title);
|
out.push_str("- [[#");
|
||||||
out.push_str("]]\n");
|
out.push_str(anchor);
|
||||||
|
out.push_str("]]\n");
|
||||||
|
} else {
|
||||||
|
out.push_str("- [[#");
|
||||||
|
out.push_str(anchor);
|
||||||
|
out.push('|');
|
||||||
|
out.push_str(title);
|
||||||
|
out.push_str("]]\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`.
|
||||||
|
/// Map each indexed page name → its first heading's title (vimwiki's
|
||||||
|
/// caption for `generated_links_caption`). Pages with no heading are
|
||||||
|
/// omitted, so `build_links_text` falls back to `[[page]]` for them.
|
||||||
|
pub fn page_captions(
|
||||||
|
index: &crate::index::WorkspaceIndex,
|
||||||
|
) -> std::collections::BTreeMap<String, String> {
|
||||||
|
let mut map = std::collections::BTreeMap::new();
|
||||||
|
for name in index.page_names() {
|
||||||
|
if let Some(h) = index.page_by_name(&name).and_then(|p| p.headings.first()) {
|
||||||
|
map.insert(name, h.title.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_links_text(
|
pub fn build_links_text(
|
||||||
pages: &[String],
|
pages: &[String],
|
||||||
heading_name: &str,
|
heading_name: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
exclude: Option<&str>,
|
exclude: Option<&str>,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
captions: Option<&std::collections::BTreeMap<String, String>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut out = caption_line(heading_name, level);
|
let mut out = caption_line(heading_name, level);
|
||||||
let pad = " ".repeat(margin);
|
let pad = " ".repeat(margin);
|
||||||
@@ -1872,6 +1919,12 @@ pub mod ops {
|
|||||||
out.push_str(&pad);
|
out.push_str(&pad);
|
||||||
out.push_str("- [[");
|
out.push_str("- [[");
|
||||||
out.push_str(name);
|
out.push_str(name);
|
||||||
|
// vimwiki `generated_links_caption`: when a caption map is supplied
|
||||||
|
// and the page has a first-heading, emit `[[page|Heading]]`.
|
||||||
|
if let Some(cap) = captions.and_then(|c| c.get(name)).filter(|c| !c.is_empty()) {
|
||||||
|
out.push('|');
|
||||||
|
out.push_str(cap);
|
||||||
|
}
|
||||||
out.push_str("]]\n");
|
out.push_str("]]\n");
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
@@ -1998,6 +2051,7 @@ pub mod ops {
|
|||||||
heading: &str,
|
heading: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
link_format: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let items = collect_toc_items(ast, heading);
|
let items = collect_toc_items(ast, heading);
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
@@ -2007,7 +2061,7 @@ 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, heading, level, margin);
|
let new_text = build_toc_text(&triples, heading, level, margin, link_format);
|
||||||
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)) => {
|
||||||
@@ -2044,9 +2098,10 @@ pub mod ops {
|
|||||||
heading: &str,
|
heading: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
link_format: u8,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
find_section_range(ast, heading)?;
|
find_section_range(ast, heading)?;
|
||||||
toc_edit(text, ast, uri, utf8, heading, level, margin)
|
toc_edit(text, ast, uri, utf8, heading, level, margin, link_format)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a `WorkspaceEdit` that replaces or inserts the
|
/// Produce a `WorkspaceEdit` that replaces or inserts the
|
||||||
@@ -2062,11 +2117,19 @@ pub mod ops {
|
|||||||
heading: &str,
|
heading: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
captions: Option<&std::collections::BTreeMap<String, String>>,
|
||||||
) -> 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, heading, level, Some(current_page), margin);
|
let new_text = build_links_text(
|
||||||
|
all_pages,
|
||||||
|
heading,
|
||||||
|
level,
|
||||||
|
Some(current_page),
|
||||||
|
margin,
|
||||||
|
captions,
|
||||||
|
);
|
||||||
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)) => {
|
||||||
@@ -2096,6 +2159,7 @@ pub mod ops {
|
|||||||
heading: &str,
|
heading: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
captions: Option<&std::collections::BTreeMap<String, String>>,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
find_section_range(ast, heading)?;
|
find_section_range(ast, heading)?;
|
||||||
links_edit(
|
links_edit(
|
||||||
@@ -2108,6 +2172,7 @@ pub mod ops {
|
|||||||
heading,
|
heading,
|
||||||
level,
|
level,
|
||||||
margin,
|
margin,
|
||||||
|
captions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2863,9 +2928,10 @@ pub mod ops {
|
|||||||
uri: &Url,
|
uri: &Url,
|
||||||
line: u32,
|
line: u32,
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
reduce_last_col: bool,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let table = find_table_at_line(ast, line)?;
|
let table = find_table_at_line(ast, line)?;
|
||||||
let rendered = render_aligned_table(table, text)?;
|
let rendered = render_aligned_table(table, text, reduce_last_col)?;
|
||||||
let span = table.span;
|
let span = table.span;
|
||||||
let mut b = WorkspaceEditBuilder::new();
|
let mut b = WorkspaceEditBuilder::new();
|
||||||
b.edit(uri.clone(), text_edit_replace(span, rendered, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, rendered, text, utf8));
|
||||||
@@ -2874,6 +2940,7 @@ pub mod ops {
|
|||||||
|
|
||||||
/// Swap the column under cursor with its left (`delta = -1`) or
|
/// Swap the column under cursor with its left (`delta = -1`) or
|
||||||
/// right (`delta = +1`) neighbour across every row of the table.
|
/// right (`delta = +1`) neighbour across every row of the table.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn table_move_column_edit(
|
pub fn table_move_column_edit(
|
||||||
text: &str,
|
text: &str,
|
||||||
ast: &DocumentNode,
|
ast: &DocumentNode,
|
||||||
@@ -2882,6 +2949,7 @@ pub mod ops {
|
|||||||
col: u32,
|
col: u32,
|
||||||
delta: i32,
|
delta: i32,
|
||||||
utf8: bool,
|
utf8: bool,
|
||||||
|
reduce_last_col: bool,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let table = find_table_at_line(ast, line)?;
|
let table = find_table_at_line(ast, line)?;
|
||||||
let cur_col = cell_column_at(table, text, line, col)?;
|
let cur_col = cell_column_at(table, text, line, col)?;
|
||||||
@@ -2895,7 +2963,7 @@ pub mod ops {
|
|||||||
if other >= max_cols {
|
if other >= max_cols {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let rendered = render_swapped_table(table, text, cur_col, other)?;
|
let rendered = render_swapped_table(table, text, cur_col, other, reduce_last_col)?;
|
||||||
let mut b = WorkspaceEditBuilder::new();
|
let mut b = WorkspaceEditBuilder::new();
|
||||||
b.edit(
|
b.edit(
|
||||||
uri.clone(),
|
uri.clone(),
|
||||||
@@ -2966,7 +3034,7 @@ pub mod ops {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute column widths (max content length across rows).
|
/// Compute column widths (max content length across rows).
|
||||||
fn column_widths(table: &TableNode, text: &str) -> Vec<usize> {
|
fn column_widths(table: &TableNode, text: &str, reduce_last_col: bool) -> Vec<usize> {
|
||||||
let max_cols = table.rows.iter().map(|r| r.cells.len()).max().unwrap_or(0);
|
let max_cols = table.rows.iter().map(|r| r.cells.len()).max().unwrap_or(0);
|
||||||
let mut widths = vec![1usize; max_cols];
|
let mut widths = vec![1usize; max_cols];
|
||||||
for row in &table.rows {
|
for row in &table.rows {
|
||||||
@@ -2977,6 +3045,13 @@ pub mod ops {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// vimwiki `table_reduce_last_col`: keep the last column at its minimum
|
||||||
|
// so it isn't padded out to fill.
|
||||||
|
if reduce_last_col {
|
||||||
|
if let Some(last) = widths.last_mut() {
|
||||||
|
*last = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
widths
|
widths
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2987,8 +3062,12 @@ pub mod ops {
|
|||||||
.all(|c| c == '-' || c == ':' || c.is_whitespace())
|
.all(|c| c == '-' || c == ':' || c.is_whitespace())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_aligned_table(table: &TableNode, text: &str) -> Option<String> {
|
fn render_aligned_table(
|
||||||
let widths = column_widths(table, text);
|
table: &TableNode,
|
||||||
|
text: &str,
|
||||||
|
reduce_last_col: bool,
|
||||||
|
) -> Option<String> {
|
||||||
|
let widths = column_widths(table, text, reduce_last_col);
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
for (i, row) in table.rows.iter().enumerate() {
|
for (i, row) in table.rows.iter().enumerate() {
|
||||||
render_row(row, text, &widths, &mut out);
|
render_row(row, text, &widths, &mut out);
|
||||||
@@ -3037,10 +3116,16 @@ pub mod ops {
|
|||||||
out.push('\n');
|
out.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_swapped_table(table: &TableNode, text: &str, a: usize, b: usize) -> Option<String> {
|
fn render_swapped_table(
|
||||||
|
table: &TableNode,
|
||||||
|
text: &str,
|
||||||
|
a: usize,
|
||||||
|
b: usize,
|
||||||
|
reduce_last_col: bool,
|
||||||
|
) -> Option<String> {
|
||||||
// Swap the column-width slots first so the rendered table
|
// Swap the column-width slots first so the rendered table
|
||||||
// remains visually aligned after the move.
|
// remains visually aligned after the move.
|
||||||
let mut widths = column_widths(table, text);
|
let mut widths = column_widths(table, text, reduce_last_col);
|
||||||
if a < widths.len() && b < widths.len() {
|
if a < widths.len() && b < widths.len() {
|
||||||
widths.swap(a, b);
|
widths.swap(a, b);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,28 @@ pub struct WikiConfig {
|
|||||||
/// (`tags_header` / `tags_header_level`).
|
/// (`tags_header` / `tags_header_level`).
|
||||||
pub tags_header: String,
|
pub tags_header: String,
|
||||||
pub tags_header_level: u8,
|
pub tags_header_level: u8,
|
||||||
|
/// vimwiki `create_link` (default `true`): when following a link to a
|
||||||
|
/// missing page, create it. When `false`, missing-link follow is a no-op.
|
||||||
|
pub create_link: bool,
|
||||||
|
/// vimwiki `dir_link`: index file opened when following a link to a
|
||||||
|
/// directory (e.g. `index` → `dir/index.wiki`). Empty = open the directory.
|
||||||
|
pub dir_link: String,
|
||||||
|
/// vimwiki `bullet_types`: ordered list of unordered-bullet glyphs for
|
||||||
|
/// this wiki's syntax (default `-`, `*`, `#`); drives `cycle_bullets`.
|
||||||
|
pub bullet_types: Vec<String>,
|
||||||
|
/// vimwiki `cycle_bullets` (default `false`): rotate an unordered item's
|
||||||
|
/// glyph through `bullet_types` as it is indented/dedented.
|
||||||
|
pub cycle_bullets: bool,
|
||||||
|
/// vimwiki `generated_links_caption` (default `false`): emit
|
||||||
|
/// `[[page|Heading]]` (first heading as caption) from `:VimwikiGenerateLinks`.
|
||||||
|
pub generated_links_caption: bool,
|
||||||
|
/// vimwiki `toc_link_format` (default `0`): `0` = `[[#anchor|title]]`
|
||||||
|
/// (leaf anchor, with description); `1` = `[[#parent#child]]` (full path,
|
||||||
|
/// no description).
|
||||||
|
pub toc_link_format: u8,
|
||||||
|
/// vimwiki `table_reduce_last_col` (default `false`): don't pad the last
|
||||||
|
/// table column to fill — keep it at its minimum width.
|
||||||
|
pub table_reduce_last_col: bool,
|
||||||
/// HTML export options.
|
/// HTML export options.
|
||||||
pub html: HtmlConfig,
|
pub html: HtmlConfig,
|
||||||
}
|
}
|
||||||
@@ -155,6 +177,32 @@ pub struct HtmlConfig {
|
|||||||
/// vimwiki `html_header_numbering_sym`: symbol appended after the
|
/// vimwiki `html_header_numbering_sym`: symbol appended after the
|
||||||
/// section number (e.g. `.` → `1.2. Heading`). Empty by default.
|
/// section number (e.g. `.` → `1.2. Heading`). Empty by default.
|
||||||
pub html_header_numbering_sym: String,
|
pub html_header_numbering_sym: String,
|
||||||
|
/// vimwiki `rss_name`: filename of the generated RSS feed (default
|
||||||
|
/// `rss.xml`), relative to `html_path`.
|
||||||
|
pub rss_name: String,
|
||||||
|
/// vimwiki `rss_max_items`: cap on the number of diary items in the RSS
|
||||||
|
/// feed (default `10`; `0` = unlimited-but-empty per upstream's min).
|
||||||
|
pub rss_max_items: usize,
|
||||||
|
/// vimwiki `valid_html_tags`: inline HTML tag names that pass through to
|
||||||
|
/// HTML export unescaped (everything else has `<`/`>` escaped). Default
|
||||||
|
/// `b,i,s,u,sub,sup,kbd,br,hr,div,center,strong,em`.
|
||||||
|
pub valid_html_tags: Vec<String>,
|
||||||
|
/// vimwiki `list_ignore_newline` (default `true`): a single newline inside
|
||||||
|
/// a list item is a space in HTML export; when `false` it becomes `<br>`.
|
||||||
|
pub list_ignore_newline: bool,
|
||||||
|
/// vimwiki `text_ignore_newline` (default `true`): a single newline inside
|
||||||
|
/// a paragraph is a space in HTML export; when `false` it becomes `<br>`.
|
||||||
|
pub text_ignore_newline: bool,
|
||||||
|
/// vimwiki `emoji_enable` (default `true`): substitute `:alias:` emoji
|
||||||
|
/// shortcodes with their glyph during HTML export.
|
||||||
|
pub emoji_enable: bool,
|
||||||
|
/// vimwiki `user_htmls`: basenames of HTML files with no wiki source that
|
||||||
|
/// `allToHtml` must not prune. Empty by default.
|
||||||
|
pub user_htmls: Vec<String>,
|
||||||
|
/// vimwiki `color_tag_template`: HTML template for a colour span. The
|
||||||
|
/// `__COLORFG__` / `__COLORBG__` / `__CONTENT__` placeholders are filled
|
||||||
|
/// from `color_dic`. Default matches upstream's `<span style=…>` template.
|
||||||
|
pub color_tag_template: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HtmlConfig {
|
impl Default for HtmlConfig {
|
||||||
@@ -169,16 +217,56 @@ impl Default for HtmlConfig {
|
|||||||
auto_export: false,
|
auto_export: false,
|
||||||
html_filename_parameterization: false,
|
html_filename_parameterization: false,
|
||||||
exclude_files: Vec::new(),
|
exclude_files: Vec::new(),
|
||||||
color_dic: std::collections::HashMap::new(),
|
color_dic: default_color_dic(),
|
||||||
custom_wiki2html: String::new(),
|
custom_wiki2html: String::new(),
|
||||||
custom_wiki2html_args: String::new(),
|
custom_wiki2html_args: String::new(),
|
||||||
base_url: String::new(),
|
base_url: String::new(),
|
||||||
html_header_numbering: 0,
|
html_header_numbering: 0,
|
||||||
html_header_numbering_sym: String::new(),
|
html_header_numbering_sym: String::new(),
|
||||||
|
rss_name: "rss.xml".into(),
|
||||||
|
rss_max_items: 10,
|
||||||
|
valid_html_tags: default_valid_html_tags(),
|
||||||
|
list_ignore_newline: true,
|
||||||
|
text_ignore_newline: true,
|
||||||
|
emoji_enable: true,
|
||||||
|
user_htmls: Vec::new(),
|
||||||
|
color_tag_template: default_color_tag_template(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// vimwiki's default `valid_html_tags` — inline tags allowed through export.
|
||||||
|
fn default_valid_html_tags() -> Vec<String> {
|
||||||
|
"b,i,s,u,sub,sup,kbd,br,hr,div,center,strong,em"
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// vimwiki's default `color_tag_template`.
|
||||||
|
fn default_color_tag_template() -> String {
|
||||||
|
"<span style=\"__STYLE__\">__CONTENT__</span>".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A populated default `color_dic` (vimwiki ships a palette rather than the
|
||||||
|
/// empty map nuwiki previously defaulted to). Names → CSS colour value.
|
||||||
|
fn default_color_dic() -> std::collections::HashMap<String, String> {
|
||||||
|
[
|
||||||
|
("default", "inherit"),
|
||||||
|
("red", "red"),
|
||||||
|
("green", "green"),
|
||||||
|
("blue", "blue"),
|
||||||
|
("yellow", "#b8860b"),
|
||||||
|
("magenta", "magenta"),
|
||||||
|
("cyan", "darkcyan"),
|
||||||
|
("gray", "gray"),
|
||||||
|
("grey", "gray"),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl HtmlConfig {
|
impl HtmlConfig {
|
||||||
/// Synthesise default paths under `root` when the user hasn't
|
/// Synthesise default paths under `root` when the user hasn't
|
||||||
/// specified explicit ones. `html_path` defaults to `<root>/_html`
|
/// specified explicit ones. `html_path` defaults to `<root>/_html`
|
||||||
@@ -257,6 +345,13 @@ impl WikiConfig {
|
|||||||
links_header_level: d.links_header_level,
|
links_header_level: d.links_header_level,
|
||||||
tags_header: d.tags_header,
|
tags_header: d.tags_header,
|
||||||
tags_header_level: d.tags_header_level,
|
tags_header_level: d.tags_header_level,
|
||||||
|
create_link: d.create_link,
|
||||||
|
dir_link: d.dir_link,
|
||||||
|
bullet_types: d.bullet_types,
|
||||||
|
cycle_bullets: d.cycle_bullets,
|
||||||
|
generated_links_caption: d.generated_links_caption,
|
||||||
|
toc_link_format: d.toc_link_format,
|
||||||
|
table_reduce_last_col: d.table_reduce_last_col,
|
||||||
html: HtmlConfig::default(),
|
html: HtmlConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,6 +392,13 @@ impl WikiConfig {
|
|||||||
links_header_level: d.links_header_level,
|
links_header_level: d.links_header_level,
|
||||||
tags_header: d.tags_header,
|
tags_header: d.tags_header,
|
||||||
tags_header_level: d.tags_header_level,
|
tags_header_level: d.tags_header_level,
|
||||||
|
create_link: d.create_link,
|
||||||
|
dir_link: d.dir_link,
|
||||||
|
bullet_types: d.bullet_types,
|
||||||
|
cycle_bullets: d.cycle_bullets,
|
||||||
|
generated_links_caption: d.generated_links_caption,
|
||||||
|
toc_link_format: d.toc_link_format,
|
||||||
|
table_reduce_last_col: d.table_reduce_last_col,
|
||||||
html,
|
html,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,9 +564,21 @@ fn wiki_defaults() -> WikiDefaults {
|
|||||||
links_header_level: 1,
|
links_header_level: 1,
|
||||||
tags_header: default_tags_header(),
|
tags_header: default_tags_header(),
|
||||||
tags_header_level: 1,
|
tags_header_level: 1,
|
||||||
|
create_link: true,
|
||||||
|
dir_link: String::new(),
|
||||||
|
bullet_types: default_bullet_types(),
|
||||||
|
cycle_bullets: false,
|
||||||
|
generated_links_caption: false,
|
||||||
|
toc_link_format: 0,
|
||||||
|
table_reduce_last_col: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// vimwiki's default `bullet_types` for the `vimwiki` syntax.
|
||||||
|
fn default_bullet_types() -> Vec<String> {
|
||||||
|
["-", "*", "#"].iter().map(|s| s.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
struct WikiDefaults {
|
struct WikiDefaults {
|
||||||
index: String,
|
index: String,
|
||||||
diary_rel_path: String,
|
diary_rel_path: String,
|
||||||
@@ -491,6 +605,13 @@ struct WikiDefaults {
|
|||||||
links_header_level: u8,
|
links_header_level: u8,
|
||||||
tags_header: String,
|
tags_header: String,
|
||||||
tags_header_level: u8,
|
tags_header_level: u8,
|
||||||
|
create_link: bool,
|
||||||
|
dir_link: String,
|
||||||
|
bullet_types: Vec<String>,
|
||||||
|
cycle_bullets: bool,
|
||||||
|
generated_links_caption: bool,
|
||||||
|
toc_link_format: u8,
|
||||||
|
table_reduce_last_col: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@@ -699,6 +820,22 @@ struct RawWiki {
|
|||||||
html_header_numbering: Option<u8>,
|
html_header_numbering: Option<u8>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
html_header_numbering_sym: Option<String>,
|
html_header_numbering_sym: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
rss_name: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
rss_max_items: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
valid_html_tags: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
list_ignore_newline: Option<bool>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
text_ignore_newline: Option<bool>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
emoji_enable: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
user_htmls: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
color_tag_template: Option<String>,
|
||||||
// per-wiki keys mirroring vimwiki globals. All optional so
|
// per-wiki keys mirroring vimwiki globals. All optional so
|
||||||
// existing single-wiki configs migrate without touching anything.
|
// existing single-wiki configs migrate without touching anything.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -747,6 +884,20 @@ struct RawWiki {
|
|||||||
tags_header: Option<String>,
|
tags_header: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
tags_header_level: Option<u8>,
|
tags_header_level: Option<u8>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
create_link: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
dir_link: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
bullet_types: Option<Vec<String>>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
cycle_bullets: Option<bool>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
generated_links_caption: Option<bool>,
|
||||||
|
#[serde(default)]
|
||||||
|
toc_link_format: Option<u8>,
|
||||||
|
#[serde(default, deserialize_with = "opt_bool_or_int::deserialize")]
|
||||||
|
table_reduce_last_col: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RawWiki> for WikiConfig {
|
impl From<RawWiki> for WikiConfig {
|
||||||
@@ -802,6 +953,30 @@ impl From<RawWiki> for WikiConfig {
|
|||||||
if let Some(s) = r.html_header_numbering_sym {
|
if let Some(s) = r.html_header_numbering_sym {
|
||||||
html.html_header_numbering_sym = s;
|
html.html_header_numbering_sym = s;
|
||||||
}
|
}
|
||||||
|
if let Some(s) = r.rss_name {
|
||||||
|
html.rss_name = s;
|
||||||
|
}
|
||||||
|
if let Some(n) = r.rss_max_items {
|
||||||
|
html.rss_max_items = n;
|
||||||
|
}
|
||||||
|
if let Some(s) = r.valid_html_tags {
|
||||||
|
html.valid_html_tags = s.split(',').map(|t| t.trim().to_string()).collect();
|
||||||
|
}
|
||||||
|
if let Some(b) = r.list_ignore_newline {
|
||||||
|
html.list_ignore_newline = b;
|
||||||
|
}
|
||||||
|
if let Some(b) = r.text_ignore_newline {
|
||||||
|
html.text_ignore_newline = b;
|
||||||
|
}
|
||||||
|
if let Some(b) = r.emoji_enable {
|
||||||
|
html.emoji_enable = b;
|
||||||
|
}
|
||||||
|
if let Some(v) = r.user_htmls {
|
||||||
|
html.user_htmls = v;
|
||||||
|
}
|
||||||
|
if let Some(s) = r.color_tag_template {
|
||||||
|
html.color_tag_template = s;
|
||||||
|
}
|
||||||
let d = wiki_defaults();
|
let d = wiki_defaults();
|
||||||
let syntax = r.syntax.unwrap_or_else(|| "vimwiki".into());
|
let syntax = r.syntax.unwrap_or_else(|| "vimwiki".into());
|
||||||
// vimwiki derives `list_margin = 0` for markdown wikis when the key is
|
// vimwiki derives `list_margin = 0` for markdown wikis when the key is
|
||||||
@@ -841,6 +1016,18 @@ impl From<RawWiki> for WikiConfig {
|
|||||||
links_header_level: r.links_header_level.unwrap_or(d.links_header_level),
|
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: r.tags_header.unwrap_or(d.tags_header),
|
||||||
tags_header_level: r.tags_header_level.unwrap_or(d.tags_header_level),
|
tags_header_level: r.tags_header_level.unwrap_or(d.tags_header_level),
|
||||||
|
create_link: r.create_link.unwrap_or(d.create_link),
|
||||||
|
dir_link: r.dir_link.unwrap_or(d.dir_link),
|
||||||
|
bullet_types: r
|
||||||
|
.bullet_types
|
||||||
|
.filter(|v| !v.is_empty())
|
||||||
|
.unwrap_or(d.bullet_types),
|
||||||
|
cycle_bullets: r.cycle_bullets.unwrap_or(d.cycle_bullets),
|
||||||
|
generated_links_caption: r
|
||||||
|
.generated_links_caption
|
||||||
|
.unwrap_or(d.generated_links_caption),
|
||||||
|
toc_link_format: r.toc_link_format.unwrap_or(d.toc_link_format),
|
||||||
|
table_reduce_last_col: r.table_reduce_last_col.unwrap_or(d.table_reduce_last_col),
|
||||||
html,
|
html,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -628,6 +628,7 @@ impl LanguageServer for Backend {
|
|||||||
&wiki.config.toc_header,
|
&wiki.config.toc_header,
|
||||||
wiki.config.toc_header_level,
|
wiki.config.toc_header_level,
|
||||||
wiki.config.list_margin.max(0) as usize,
|
wiki.config.list_margin.max(0) as usize,
|
||||||
|
wiki.config.toc_link_format,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
@@ -642,11 +643,17 @@ impl LanguageServer for Backend {
|
|||||||
if wiki.config.auto_generate_links {
|
if wiki.config.auto_generate_links {
|
||||||
let edit = self.documents.get(&uri).and_then(|doc| {
|
let edit = self.documents.get(&uri).and_then(|doc| {
|
||||||
let current = index::page_name_from_uri(&uri, Some(&wiki.config.root));
|
let current = index::page_name_from_uri(&uri, Some(&wiki.config.root));
|
||||||
let pages = wiki
|
let (pages, captions) = wiki
|
||||||
.index
|
.index
|
||||||
.read()
|
.read()
|
||||||
.ok()
|
.ok()
|
||||||
.map(|i| i.page_names())
|
.map(|i| {
|
||||||
|
let caps = wiki
|
||||||
|
.config
|
||||||
|
.generated_links_caption
|
||||||
|
.then(|| commands::ops::page_captions(&i));
|
||||||
|
(i.page_names(), caps)
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
commands::ops::links_rebuild_edit(
|
commands::ops::links_rebuild_edit(
|
||||||
&doc.text,
|
&doc.text,
|
||||||
@@ -658,6 +665,7 @@ impl LanguageServer for Backend {
|
|||||||
&wiki.config.links_header,
|
&wiki.config.links_header,
|
||||||
wiki.config.links_header_level,
|
wiki.config.links_header_level,
|
||||||
wiki.config.list_margin.max(0) as usize,
|
wiki.config.list_margin.max(0) as usize,
|
||||||
|
captions.as_ref(),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
if let Some(edit) = edit {
|
if let Some(edit) = edit {
|
||||||
|
|||||||
@@ -306,7 +306,8 @@ 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, "Contents", 1, 0).expect("toc edit produced");
|
let edit =
|
||||||
|
ops::toc_edit(src, &doc, &uri, true, "Contents", 1, 0, 0).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(
|
||||||
@@ -317,6 +318,7 @@ fn nuwiki_toc_generates_nested_contents() {
|
|||||||
"Contents",
|
"Contents",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
);
|
);
|
||||||
assert!(toc.contains("= Contents ="));
|
assert!(toc.contains("= Contents ="));
|
||||||
assert!(toc.contains("- [[#top|Top]]"));
|
assert!(toc.contains("- [[#top|Top]]"));
|
||||||
@@ -327,7 +329,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", 1, Some("Home"), 0);
|
let text = ops::build_links_text(&pages, "Links", 1, Some("Home"), 0, None);
|
||||||
assert!(text.contains("= Links ="));
|
assert!(text.contains("= Links ="));
|
||||||
assert!(text.contains("- [[About]]"));
|
assert!(text.contains("- [[About]]"));
|
||||||
assert!(text.contains("- [[Notes]]"));
|
assert!(text.contains("- [[Notes]]"));
|
||||||
@@ -345,7 +347,8 @@ fn nuwiki_generate_links_lists_all_pages_excluding_current() {
|
|||||||
true,
|
true,
|
||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0
|
0,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.is_some());
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fn blank_table_handles_single_cell_grid() {
|
|||||||
fn align_pads_short_cells_to_widest() {
|
fn align_pads_short_cells_to_widest() {
|
||||||
let src = "|a|bbb|c|\n|--|--|--|\n|1|2|three|\n";
|
let src = "|a|bbb|c|\n|--|--|--|\n|1|2|three|\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
let edit = ops::table_align_edit(src, &ast, &uri(), 0, true).expect("edit");
|
let edit = ops::table_align_edit(src, &ast, &uri(), 0, true, false).expect("edit");
|
||||||
let body = one_text(edit);
|
let body = one_text(edit);
|
||||||
let lines: Vec<&str> = body.lines().collect();
|
let lines: Vec<&str> = body.lines().collect();
|
||||||
eprintln!("rendered table:");
|
eprintln!("rendered table:");
|
||||||
@@ -64,7 +64,23 @@ fn align_pads_short_cells_to_widest() {
|
|||||||
fn align_returns_none_when_cursor_off_table() {
|
fn align_returns_none_when_cursor_off_table() {
|
||||||
let src = "paragraph\n";
|
let src = "paragraph\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
assert!(ops::table_align_edit(src, &ast, &uri(), 0, true).is_none());
|
assert!(ops::table_align_edit(src, &ast, &uri(), 0, true, false).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn table_reduce_last_col_keeps_last_column_minimal() {
|
||||||
|
// With reduce_last_col=true, the last column isn't padded to its content
|
||||||
|
// width — it stays at the minimum (1), so `three` is not surrounded by
|
||||||
|
// alignment padding on the right edge.
|
||||||
|
let src = "|a|bbb|c|\n|--|--|--|\n|1|2|three|\n";
|
||||||
|
let ast = parse(src);
|
||||||
|
let edit = ops::table_align_edit(src, &ast, &uri(), 0, true, true).expect("edit");
|
||||||
|
let body = one_text(edit);
|
||||||
|
let lines: Vec<&str> = body.lines().collect();
|
||||||
|
// First two columns still align; the last column stays at width 1 (not 5),
|
||||||
|
// so the header's last cell renders `| c |` not `| c |`.
|
||||||
|
assert_eq!(lines[0], "| a | bbb | c |", "got: {:?}", lines[0]);
|
||||||
|
assert_eq!(lines[2], "| 1 | 2 | three |", "got: {:?}", lines[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== moveColumn =====
|
// ===== moveColumn =====
|
||||||
@@ -74,7 +90,7 @@ fn move_column_right_swaps_with_neighbour() {
|
|||||||
let src = "|a|b|c|\n|--|--|--|\n|1|2|3|\n";
|
let src = "|a|b|c|\n|--|--|--|\n|1|2|3|\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
// Cursor on first column → swap right.
|
// Cursor on first column → swap right.
|
||||||
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, 1, true).expect("edit");
|
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, 1, true, false).expect("edit");
|
||||||
let body = one_text(edit);
|
let body = one_text(edit);
|
||||||
let lines: Vec<&str> = body.lines().collect();
|
let lines: Vec<&str> = body.lines().collect();
|
||||||
assert!(lines[0].contains("b") && lines[0].contains("a"));
|
assert!(lines[0].contains("b") && lines[0].contains("a"));
|
||||||
@@ -90,7 +106,7 @@ fn move_column_left_at_zero_is_noop() {
|
|||||||
let src = "|a|b|\n|--|--|\n|1|2|\n";
|
let src = "|a|b|\n|--|--|\n|1|2|\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
// Cursor in column 0; left would be -1 → returns None.
|
// Cursor in column 0; left would be -1 → returns None.
|
||||||
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, -1, true);
|
let edit = ops::table_move_column_edit(src, &ast, &uri(), 0, 1, -1, true, false);
|
||||||
assert!(edit.is_none());
|
assert!(edit.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +114,7 @@ fn move_column_left_at_zero_is_noop() {
|
|||||||
fn move_column_returns_none_off_table() {
|
fn move_column_returns_none_off_table() {
|
||||||
let src = "paragraph\n";
|
let src = "paragraph\n";
|
||||||
let ast = parse(src);
|
let ast = parse(src);
|
||||||
assert!(ops::table_move_column_edit(src, &ast, &uri(), 0, 0, 1, true).is_none());
|
assert!(ops::table_move_column_edit(src, &ast, &uri(), 0, 0, 1, true, false).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== COMMANDS list =====
|
// ===== COMMANDS list =====
|
||||||
|
|||||||
@@ -295,6 +295,63 @@ fn defaults_carry_vimwiki_per_wiki_keys() {
|
|||||||
// HTML section numbering is off by default (vimwiki's `0`).
|
// HTML section numbering is off by default (vimwiki's `0`).
|
||||||
assert_eq!(cfg.html.html_header_numbering, 0);
|
assert_eq!(cfg.html.html_header_numbering, 0);
|
||||||
assert_eq!(cfg.html.html_header_numbering_sym, "");
|
assert_eq!(cfg.html.html_header_numbering_sym, "");
|
||||||
|
// New config defaults match upstream.
|
||||||
|
assert!(cfg.create_link);
|
||||||
|
assert_eq!(cfg.dir_link, "");
|
||||||
|
assert_eq!(cfg.bullet_types, vec!["-", "*", "#"]);
|
||||||
|
assert!(!cfg.cycle_bullets);
|
||||||
|
assert!(!cfg.generated_links_caption);
|
||||||
|
assert_eq!(cfg.toc_link_format, 0);
|
||||||
|
assert!(!cfg.table_reduce_last_col);
|
||||||
|
assert_eq!(cfg.html.rss_name, "rss.xml");
|
||||||
|
assert_eq!(cfg.html.rss_max_items, 10);
|
||||||
|
assert!(cfg.html.list_ignore_newline);
|
||||||
|
assert!(cfg.html.text_ignore_newline);
|
||||||
|
assert!(cfg.html.emoji_enable);
|
||||||
|
assert!(cfg.html.user_htmls.is_empty());
|
||||||
|
assert!(cfg.html.valid_html_tags.contains(&"sub".to_string()));
|
||||||
|
// color_dic ships a populated palette (vimwiki seeds one).
|
||||||
|
assert!(cfg.html.color_dic.contains_key("red"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_wiki_parses_config_batch_keys() {
|
||||||
|
let cfg = config_from_json(serde_json::json!({
|
||||||
|
"wikis": [{
|
||||||
|
"root": "/tmp/w",
|
||||||
|
"create_link": false,
|
||||||
|
"dir_link": "index",
|
||||||
|
"bullet_types": ["+", "-"],
|
||||||
|
"cycle_bullets": true,
|
||||||
|
"generated_links_caption": 1,
|
||||||
|
"toc_link_format": 1,
|
||||||
|
"table_reduce_last_col": true,
|
||||||
|
"rss_name": "feed.xml",
|
||||||
|
"rss_max_items": 5,
|
||||||
|
"valid_html_tags": "b,mark",
|
||||||
|
"list_ignore_newline": 0,
|
||||||
|
"text_ignore_newline": false,
|
||||||
|
"emoji_enable": 0,
|
||||||
|
"user_htmls": ["404.html"],
|
||||||
|
"color_tag_template": "<u>__CONTENT__</u>",
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
let w = &cfg.wikis[0];
|
||||||
|
assert!(!w.create_link);
|
||||||
|
assert_eq!(w.dir_link, "index");
|
||||||
|
assert_eq!(w.bullet_types, vec!["+", "-"]);
|
||||||
|
assert!(w.cycle_bullets);
|
||||||
|
assert!(w.generated_links_caption);
|
||||||
|
assert_eq!(w.toc_link_format, 1);
|
||||||
|
assert!(w.table_reduce_last_col);
|
||||||
|
assert_eq!(w.html.rss_name, "feed.xml");
|
||||||
|
assert_eq!(w.html.rss_max_items, 5);
|
||||||
|
assert_eq!(w.html.valid_html_tags, vec!["b", "mark"]);
|
||||||
|
assert!(!w.html.list_ignore_newline);
|
||||||
|
assert!(!w.html.text_ignore_newline);
|
||||||
|
assert!(!w.html.emoji_enable);
|
||||||
|
assert_eq!(w.html.user_htmls, vec!["404.html"]);
|
||||||
|
assert_eq!(w.html.color_tag_template, "<u>__CONTENT__</u>");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -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", 1, 0);
|
let out = ops::build_toc_text(&items, "Contents", 1, 0, 0);
|
||||||
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,10 +301,36 @@ 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", 1, 0);
|
let out = ops::build_toc_text(&[], "Contents", 1, 0, 0);
|
||||||
assert_eq!(out, "= Contents =\n");
|
assert_eq!(out, "= Contents =\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toc_link_format_1_drops_the_description() {
|
||||||
|
// vimwiki toc_link_format: 1 = `[[#anchor]]` (no `|title`); 0 = with title.
|
||||||
|
let items = vec![(1u8, "Top".into(), "top".into())];
|
||||||
|
let f0 = ops::build_toc_text(&items, "Contents", 1, 0, 0);
|
||||||
|
assert!(f0.contains("- [[#top|Top]]"), "format 0: {f0}");
|
||||||
|
let f1 = ops::build_toc_text(&items, "Contents", 1, 0, 1);
|
||||||
|
assert!(f1.contains("- [[#top]]"), "format 1: {f1}");
|
||||||
|
assert!(!f1.contains("|Top"), "format 1 has no description: {f1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generated_links_caption_emits_first_heading() {
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
let pages = vec!["About".to_string(), "Notes".to_string()];
|
||||||
|
let mut caps = BTreeMap::new();
|
||||||
|
caps.insert("About".to_string(), "About Us".to_string());
|
||||||
|
// Notes has no caption → falls back to bare [[Notes]].
|
||||||
|
let out = ops::build_links_text(&pages, "Generated Links", 1, None, 0, Some(&caps));
|
||||||
|
assert!(out.contains("- [[About|About Us]]"), "captioned: {out}");
|
||||||
|
assert!(out.contains("- [[Notes]]"), "fallback: {out}");
|
||||||
|
// Without the caption map, both are bare.
|
||||||
|
let bare = ops::build_links_text(&pages, "Generated Links", 1, None, 0, None);
|
||||||
|
assert!(bare.contains("- [[About]]") && !bare.contains("About Us"), "bare: {bare}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn links_rebuild_edit_only_acts_when_section_present() {
|
fn links_rebuild_edit_only_acts_when_section_present() {
|
||||||
let pages = vec!["Home".to_string(), "About".to_string()];
|
let pages = vec!["Home".to_string(), "About".to_string()];
|
||||||
@@ -320,7 +346,8 @@ fn links_rebuild_edit_only_acts_when_section_present() {
|
|||||||
true,
|
true,
|
||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0
|
0,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.is_none(),
|
.is_none(),
|
||||||
"must not insert a links section into a page that lacks one"
|
"must not insert a links section into a page that lacks one"
|
||||||
@@ -335,7 +362,8 @@ fn links_rebuild_edit_only_acts_when_section_present() {
|
|||||||
true,
|
true,
|
||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0
|
0,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.is_some());
|
.is_some());
|
||||||
}
|
}
|
||||||
@@ -360,15 +388,15 @@ fn find_section_range_stops_at_same_level_sibling() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn captions_honour_custom_header_and_level() {
|
fn captions_honour_custom_header_and_level() {
|
||||||
// toc_header="Table of Contents", level 2 → `== Table of Contents ==`.
|
// toc_header="Table of Contents", level 2 → `== Table of Contents ==`.
|
||||||
let out = ops::build_toc_text(&[], "Table of Contents", 2, 0);
|
let out = ops::build_toc_text(&[], "Table of Contents", 2, 0, 0);
|
||||||
assert_eq!(out, "== Table of Contents ==\n");
|
assert_eq!(out, "== Table of Contents ==\n");
|
||||||
// links_header at level 3.
|
// links_header at level 3.
|
||||||
let pages = vec!["A".to_string(), "B".to_string()];
|
let pages = vec!["A".to_string(), "B".to_string()];
|
||||||
let links = ops::build_links_text(&pages, "All Pages", 3, None, 0);
|
let links = ops::build_links_text(&pages, "All Pages", 3, None, 0, None);
|
||||||
assert!(links.starts_with("=== All Pages ===\n"));
|
assert!(links.starts_with("=== All Pages ===\n"));
|
||||||
// level clamps to 1..=6.
|
// level clamps to 1..=6.
|
||||||
assert!(ops::build_toc_text(&[], "X", 9, 0).starts_with("====== X ======"));
|
assert!(ops::build_toc_text(&[], "X", 9, 0, 0).starts_with("====== X ======"));
|
||||||
assert!(ops::build_toc_text(&[], "X", 0, 0).starts_with("= X ="));
|
assert!(ops::build_toc_text(&[], "X", 0, 0, 0).starts_with("= X ="));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -377,7 +405,7 @@ fn list_margin_indents_generated_bullets_not_headings() {
|
|||||||
// The heading stays at column 0; bullets get `margin` spaces (TOC keeps
|
// The heading stays at column 0; bullets get `margin` spaces (TOC keeps
|
||||||
// its per-depth nesting *after* the base margin).
|
// its per-depth nesting *after* the base margin).
|
||||||
let pages = vec!["A".to_string(), "B".to_string()];
|
let pages = vec!["A".to_string(), "B".to_string()];
|
||||||
let links = ops::build_links_text(&pages, "Generated Links", 1, None, 2);
|
let links = ops::build_links_text(&pages, "Generated Links", 1, None, 2, None);
|
||||||
assert!(links.contains("\n - [[A]]\n"), "2-space margin: {links:?}");
|
assert!(links.contains("\n - [[A]]\n"), "2-space margin: {links:?}");
|
||||||
assert!(
|
assert!(
|
||||||
links.starts_with("= Generated Links =\n"),
|
links.starts_with("= Generated Links =\n"),
|
||||||
@@ -392,6 +420,7 @@ fn list_margin_indents_generated_bullets_not_headings() {
|
|||||||
"Contents",
|
"Contents",
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
0,
|
||||||
);
|
);
|
||||||
// Top bullet: 2-space margin; Sub bullet: margin + one nesting level.
|
// Top bullet: 2-space margin; Sub bullet: margin + one nesting level.
|
||||||
assert!(toc.contains("\n - [[#top|Top]]\n"), "top margin: {toc:?}");
|
assert!(toc.contains("\n - [[#top|Top]]\n"), "top margin: {toc:?}");
|
||||||
@@ -403,7 +432,7 @@ fn list_margin_indents_generated_bullets_not_headings() {
|
|||||||
#[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", 1, Some("Home"), 0);
|
let out = ops::build_links_text(&pages, "Generated Links", 1, Some("Home"), 0, None);
|
||||||
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]]"));
|
||||||
@@ -414,7 +443,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", 1, None, 0);
|
let out = ops::build_links_text(&pages, "Generated Links", 1, None, 0, None);
|
||||||
assert!(out.contains("[[A]]"));
|
assert!(out.contains("[[A]]"));
|
||||||
assert!(out.contains("[[B]]"));
|
assert!(out.contains("[[B]]"));
|
||||||
}
|
}
|
||||||
@@ -452,7 +481,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, "Contents", 1, 0).expect("got an edit");
|
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0, 0).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);
|
||||||
@@ -468,7 +497,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, "Contents", 1, 0).expect("got an edit");
|
let edit = ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0, 0).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];
|
||||||
@@ -483,7 +512,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, "Contents", 1, 0).is_none());
|
assert!(ops::toc_edit(src, &ast, &uri, true, "Contents", 1, 0, 0).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -494,7 +523,7 @@ 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, "Contents", 1, 0).is_none(),
|
ops::toc_rebuild_edit(without, &ast, &uri, true, "Contents", 1, 0, 0).is_none(),
|
||||||
"auto_toc should not insert a TOC where none existed"
|
"auto_toc should not insert a TOC where none existed"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -502,7 +531,7 @@ fn toc_rebuild_edit_only_acts_when_toc_already_present() {
|
|||||||
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 =
|
let edit =
|
||||||
ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1, 0).expect("rebuild edit");
|
ops::toc_rebuild_edit(with, &ast, &uri, true, "Contents", 1, 0, 0).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"));
|
||||||
@@ -526,6 +555,7 @@ fn links_edit_inserts_when_section_absent() {
|
|||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("edit");
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -550,6 +580,7 @@ fn links_edit_replaces_when_section_present() {
|
|||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("edit");
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -563,7 +594,19 @@ 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, "Generated Links", 1, 0).is_none());
|
assert!(ops::links_edit(
|
||||||
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
"Home",
|
||||||
|
&[],
|
||||||
|
true,
|
||||||
|
"Generated Links",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== find_orphans =====
|
// ===== find_orphans =====
|
||||||
|
|||||||
+11
-1
@@ -12,7 +12,8 @@ if exists('b:did_ftplugin')
|
|||||||
endif
|
endif
|
||||||
let b:did_ftplugin = 1
|
let b:did_ftplugin = 1
|
||||||
|
|
||||||
setlocal commentstring=%%\ %s
|
" vimwiki's commentstring is `%%%s` (no space); match it exactly.
|
||||||
|
setlocal commentstring=%%%s
|
||||||
setlocal comments=:%%
|
setlocal comments=:%%
|
||||||
setlocal formatoptions+=ron
|
setlocal formatoptions+=ron
|
||||||
setlocal suffixesadd=.wiki
|
setlocal suffixesadd=.wiki
|
||||||
@@ -62,6 +63,15 @@ if !has('nvim')
|
|||||||
augroup END
|
augroup END
|
||||||
let b:undo_ftplugin .= ' | execute "autocmd! NuwikiTableAutoFmt * <buffer>"'
|
let b:undo_ftplugin .= ' | execute "autocmd! NuwikiTableAutoFmt * <buffer>"'
|
||||||
endif
|
endif
|
||||||
|
" vimwiki `auto_chdir` (default off): lcd into the owning wiki's root.
|
||||||
|
if get(g:, 'nuwiki_auto_chdir', 0)
|
||||||
|
augroup NuwikiAutoChdir
|
||||||
|
autocmd! * <buffer>
|
||||||
|
autocmd BufEnter,BufWinEnter <buffer> call nuwiki#commands#auto_chdir()
|
||||||
|
augroup END
|
||||||
|
call nuwiki#commands#auto_chdir()
|
||||||
|
let b:undo_ftplugin .= ' | execute "autocmd! NuwikiAutoChdir * <buffer>"'
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !has('nvim')
|
if !has('nvim')
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ M.defaults = {
|
|||||||
-- cursor when leaving insert mode.
|
-- cursor when leaving insert mode.
|
||||||
table_auto_fmt = true,
|
table_auto_fmt = true,
|
||||||
|
|
||||||
|
-- vimwiki `auto_chdir` (default off): `:lcd` into the owning wiki's root
|
||||||
|
-- when a wiki buffer becomes current.
|
||||||
|
auto_chdir = false,
|
||||||
|
|
||||||
-- Broken-link diagnostic severity. Sent to the server, which downgrades
|
-- Broken-link diagnostic severity. Sent to the server, which downgrades
|
||||||
-- or suppresses the diagnostic accordingly.
|
-- or suppresses the diagnostic accordingly.
|
||||||
-- 'off' | 'hint' | 'warn' | 'error'
|
-- 'off' | 'hint' | 'warn' | 'error'
|
||||||
@@ -125,4 +129,17 @@ function M.wiki_list()
|
|||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Absolute root (with trailing slash) of the wiki that owns `path`, or the
|
||||||
|
-- first configured wiki as a fallback. Used by auto_chdir + search scoping.
|
||||||
|
function M.wiki_root_for(path)
|
||||||
|
for _, w in ipairs(M.wiki_list()) do
|
||||||
|
local wroot = vim.fn.fnamemodify(vim.fn.expand(w.root), ':p')
|
||||||
|
if path:sub(1, #wroot) == wroot then
|
||||||
|
return wroot
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local first = M.wiki_list()[1]
|
||||||
|
return first and vim.fn.fnamemodify(vim.fn.expand(first.root), ':p') or nil
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -94,6 +94,31 @@ local function setup_table_auto_fmt(bufnr, enabled)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- vimwiki `auto_chdir`: `:lcd` into the owning wiki's root while this buffer
|
||||||
|
-- is current.
|
||||||
|
local function setup_auto_chdir(bufnr, enabled)
|
||||||
|
if not enabled then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if bufnr == 0 then
|
||||||
|
bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
end
|
||||||
|
local function chdir()
|
||||||
|
local file = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
local root = require('nuwiki.config').wiki_root_for(file)
|
||||||
|
if root then
|
||||||
|
vim.cmd('lcd ' .. vim.fn.fnameescape(root))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local grp = vim.api.nvim_create_augroup('nuwiki_chdir_' .. bufnr, { clear = true })
|
||||||
|
vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWinEnter' }, {
|
||||||
|
buffer = bufnr,
|
||||||
|
group = grp,
|
||||||
|
callback = chdir,
|
||||||
|
})
|
||||||
|
chdir()
|
||||||
|
end
|
||||||
|
|
||||||
function M.attach(bufnr)
|
function M.attach(bufnr)
|
||||||
bufnr = bufnr or 0
|
bufnr = bufnr or 0
|
||||||
local config = require('nuwiki.config')
|
local config = require('nuwiki.config')
|
||||||
@@ -109,6 +134,7 @@ function M.attach(bufnr)
|
|||||||
setup_folding(bufnr, opts.folding or 'lsp')
|
setup_folding(bufnr, opts.folding or 'lsp')
|
||||||
setup_autowriteall(bufnr, opts.autowriteall ~= false)
|
setup_autowriteall(bufnr, opts.autowriteall ~= false)
|
||||||
setup_table_auto_fmt(bufnr, opts.table_auto_fmt ~= false)
|
setup_table_auto_fmt(bufnr, opts.table_auto_fmt ~= false)
|
||||||
|
setup_auto_chdir(bufnr, opts.auto_chdir == true)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user