feat(generate): insert links/tags sections at the cursor line too
Extend the cursor-line insertion from :NuwikiTOC to the other buffer list generators: :NuwikiGenerateLinks and :NuwikiGenerateTagLinks. They appended at end-of-file; a fresh section now goes at the cursor line (with a leading blank), matching :NuwikiTOC. An existing section is still refreshed in place, and the auto_generate_*-on-save hooks are unaffected (they only ever replace). Shared section_insert() helper computes the insert position/block for all three (cursor line clamped to the document, else the per-command fallback: top of file for TOC, EOF for links/tags). Clients send the 0-based cursor line; handlers parse it via parse_uri_line_arg and thread it through links_edit / tag_links_edit. Rebuild variants pass None. Tests: links_edit_inserts_at_cursor_line + tag_links_edit_inserts_at_cursor_line. 577 passed, clippy clean, keymap harnesses green. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -734,11 +734,14 @@ endfunction
|
|||||||
" `generateForPath`). Matches upstream's optional path argument.
|
" `generateForPath`). Matches upstream's optional path argument.
|
||||||
function! nuwiki#commands#links_generate(...) abort
|
function! nuwiki#commands#links_generate(...) abort
|
||||||
let l:path = a:0 >= 1 ? a:1 : ''
|
let l:path = a:0 >= 1 ? a:1 : ''
|
||||||
|
" Cursor line (0-based) so a fresh section is inserted there.
|
||||||
|
let l:line = line('.') - 1
|
||||||
if l:path !=# ''
|
if l:path !=# ''
|
||||||
call s:exec('nuwiki.links.generateForPath',
|
call s:exec('nuwiki.links.generateForPath',
|
||||||
\ [{'uri': s:buf_uri(), 'path': l:path}])
|
\ [{'uri': s:buf_uri(), 'path': l:path, 'line': l:line}])
|
||||||
else
|
else
|
||||||
call s:exec('nuwiki.links.generate', [{'uri': s:buf_uri()}])
|
call s:exec('nuwiki.links.generate',
|
||||||
|
\ [{'uri': s:buf_uri(), 'line': l:line}])
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
function! nuwiki#commands#check_links(...) abort
|
function! nuwiki#commands#check_links(...) abort
|
||||||
@@ -800,7 +803,8 @@ function! nuwiki#commands#tags_search(query) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! nuwiki#commands#tags_generate_links(tag) abort
|
function! nuwiki#commands#tags_generate_links(tag) abort
|
||||||
let l:args = { 'uri': s:buf_uri() }
|
" Cursor line (0-based) so a fresh section is inserted there.
|
||||||
|
let l:args = { 'uri': s:buf_uri(), 'line': line('.') - 1 }
|
||||||
if a:tag !=# ''
|
if a:tag !=# ''
|
||||||
let l:args['tag'] = a:tag
|
let l:args['tag'] = a:tag
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -304,6 +304,24 @@ fn parse_uri_arg(args: Vec<Value>) -> Result<UriArg, String> {
|
|||||||
serde_json::from_value(raw).map_err(|e| format!("invalid args: {e}"))
|
serde_json::from_value(raw).map_err(|e| format!("invalid args: {e}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse `{ uri, line? }` for the section generators (TOC / links / tags). The
|
||||||
|
/// optional 0-based `line` is the cursor line the client sends, where a fresh
|
||||||
|
/// section is inserted.
|
||||||
|
fn parse_uri_line_arg(args: Vec<Value>) -> Result<(Url, Option<u32>), String> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct A {
|
||||||
|
uri: Url,
|
||||||
|
#[serde(default)]
|
||||||
|
line: Option<u32>,
|
||||||
|
}
|
||||||
|
let raw = args
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| "missing { uri } argument".to_string())?;
|
||||||
|
let a: A = serde_json::from_value(raw).map_err(|e| format!("invalid args: {e}"))?;
|
||||||
|
Ok((a.uri, a.line))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
struct OptionalUriArg {
|
struct OptionalUriArg {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -323,23 +341,13 @@ fn parse_optional_uri_arg(args: Vec<Value>) -> Result<OptionalUriArg, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
||||||
#[derive(Deserialize)]
|
let (uri, cursor_line) = parse_uri_line_arg(args)?;
|
||||||
struct TocArg {
|
let doc = match backend.documents.get(&uri) {
|
||||||
uri: Url,
|
|
||||||
/// 0-based cursor line; a fresh TOC is inserted here.
|
|
||||||
#[serde(default)]
|
|
||||||
line: Option<u32>,
|
|
||||||
}
|
|
||||||
let p: TocArg = match args.into_iter().next() {
|
|
||||||
Some(v) => serde_json::from_value(v).map_err(|e| format!("invalid args: {e}"))?,
|
|
||||||
None => return Err("missing { uri } argument".to_string()),
|
|
||||||
};
|
|
||||||
let doc = match backend.documents.get(&p.uri) {
|
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||||
let wiki = backend.wiki_for_uri(&p.uri);
|
let wiki = backend.wiki_for_uri(&uri);
|
||||||
let (header, level) = wiki
|
let (header, level) = wiki
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|w| (w.config.toc_header.clone(), w.config.toc_header_level))
|
.map(|w| (w.config.toc_header.clone(), w.config.toc_header_level))
|
||||||
@@ -352,28 +360,28 @@ fn toc_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceE
|
|||||||
Ok(ops::toc_edit(
|
Ok(ops::toc_edit(
|
||||||
&doc.text,
|
&doc.text,
|
||||||
&doc.ast,
|
&doc.ast,
|
||||||
&p.uri,
|
&uri,
|
||||||
utf8,
|
utf8,
|
||||||
&header,
|
&header,
|
||||||
level,
|
level,
|
||||||
margin,
|
margin,
|
||||||
link_format,
|
link_format,
|
||||||
p.line,
|
cursor_line,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
||||||
let p = parse_uri_arg(args)?;
|
let (uri, cursor_line) = parse_uri_line_arg(args)?;
|
||||||
let doc = match backend.documents.get(&p.uri) {
|
let doc = match backend.documents.get(&uri) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
};
|
};
|
||||||
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
let utf8 = backend.use_utf8.load(Ordering::Relaxed);
|
||||||
let wiki = match backend.wiki_for_uri(&p.uri) {
|
let wiki = match backend.wiki_for_uri(&uri) {
|
||||||
Some(w) => w,
|
Some(w) => w,
|
||||||
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(&uri, Some(&wiki.config.root));
|
||||||
let (pages, captions) = {
|
let (pages, captions) = {
|
||||||
let idx = wiki
|
let idx = wiki
|
||||||
.index
|
.index
|
||||||
@@ -388,7 +396,7 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
|||||||
Ok(ops::links_edit(
|
Ok(ops::links_edit(
|
||||||
&doc.text,
|
&doc.text,
|
||||||
&doc.ast,
|
&doc.ast,
|
||||||
&p.uri,
|
&uri,
|
||||||
¤t_page,
|
¤t_page,
|
||||||
&pages,
|
&pages,
|
||||||
utf8,
|
utf8,
|
||||||
@@ -396,6 +404,7 @@ fn links_generate(backend: &Backend, args: Vec<Value>) -> Result<Option<Workspac
|
|||||||
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(),
|
captions.as_ref(),
|
||||||
|
cursor_line,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,6 +641,9 @@ fn tags_generate_links(
|
|||||||
uri: Url,
|
uri: Url,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
|
/// 0-based cursor line; a fresh tags section is inserted here.
|
||||||
|
#[serde(default)]
|
||||||
|
line: Option<u32>,
|
||||||
}
|
}
|
||||||
let raw = args
|
let raw = args
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -664,6 +676,7 @@ fn tags_generate_links(
|
|||||||
&wiki.config.tags_header,
|
&wiki.config.tags_header,
|
||||||
wiki.config.tags_header_level,
|
wiki.config.tags_header_level,
|
||||||
wiki.config.list_margin.max(0) as usize,
|
wiki.config.list_margin.max(0) as usize,
|
||||||
|
parsed.line,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,6 +1383,32 @@ fn eof_position(text: &str) -> LspPosition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decide where to insert a generated section (TOC / generated links / tags
|
||||||
|
/// index) and the text block to write. When the client passed a cursor
|
||||||
|
/// `insert_line`, insert at that line (clamped to the document; a leading blank
|
||||||
|
/// keeps the heading off the preceding line, except at the top of the file).
|
||||||
|
/// Otherwise fall back to `fallback` — top of file for the TOC, end of file for
|
||||||
|
/// the links/tags sections.
|
||||||
|
fn section_insert(
|
||||||
|
text: &str,
|
||||||
|
insert_line: Option<u32>,
|
||||||
|
fallback: LspPosition,
|
||||||
|
new_text: &str,
|
||||||
|
) -> (LspPosition, String) {
|
||||||
|
match insert_line {
|
||||||
|
Some(l) => {
|
||||||
|
let line = l.min(text.matches('\n').count() as u32);
|
||||||
|
let block = if line == 0 {
|
||||||
|
format!("{new_text}\n")
|
||||||
|
} else {
|
||||||
|
format!("\n{new_text}\n")
|
||||||
|
};
|
||||||
|
(LspPosition { line, character: 0 }, block)
|
||||||
|
}
|
||||||
|
None => (fallback, format!("{new_text}\n")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Pure operations =====
|
// ===== Pure operations =====
|
||||||
|
|
||||||
pub mod ops {
|
pub mod ops {
|
||||||
@@ -2104,22 +2143,13 @@ pub mod ops {
|
|||||||
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Insert at the cursor line (clamped to the document), or the
|
// Fresh TOC: at the cursor line, else the top of the file.
|
||||||
// top of the file when the client didn't send one.
|
let top = LspPosition {
|
||||||
let line = insert_line
|
line: 0,
|
||||||
.unwrap_or(0)
|
character: 0,
|
||||||
.min(text.matches('\n').count() as u32);
|
|
||||||
// A blank line keeps the TOC heading from abutting preceding
|
|
||||||
// text — not needed at the very top of the file.
|
|
||||||
let block = if line == 0 {
|
|
||||||
format!("{new_text}\n")
|
|
||||||
} else {
|
|
||||||
format!("\n{new_text}\n")
|
|
||||||
};
|
};
|
||||||
b.edit(
|
let (pos, block) = section_insert(text, insert_line, top, &new_text);
|
||||||
uri.clone(),
|
b.edit(uri.clone(), text_edit_insert(pos, block));
|
||||||
text_edit_insert(LspPosition { line, character: 0 }, block),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(b.build())
|
Some(b.build())
|
||||||
@@ -2170,6 +2200,7 @@ pub mod ops {
|
|||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
captions: Option<&std::collections::BTreeMap<String, String>>,
|
captions: Option<&std::collections::BTreeMap<String, String>>,
|
||||||
|
insert_line: Option<u32>,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
if all_pages.is_empty() {
|
if all_pages.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
@@ -2189,9 +2220,9 @@ pub mod ops {
|
|||||||
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, new_text, text, utf8));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let with_sep = format!("{new_text}\n");
|
// Fresh section: at the cursor line, else the end of the file.
|
||||||
let insert_pos = eof_position(text);
|
let (pos, block) = section_insert(text, insert_line, eof_position(text), &new_text);
|
||||||
b.edit(uri.clone(), text_edit_insert(insert_pos, with_sep));
|
b.edit(uri.clone(), text_edit_insert(pos, block));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(b.build())
|
Some(b.build())
|
||||||
@@ -2214,6 +2245,7 @@ pub mod ops {
|
|||||||
captions: Option<&std::collections::BTreeMap<String, String>>,
|
captions: Option<&std::collections::BTreeMap<String, String>>,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
find_section_range(ast, heading)?;
|
find_section_range(ast, heading)?;
|
||||||
|
// Rebuild-on-save only ever replaces an existing section in place.
|
||||||
links_edit(
|
links_edit(
|
||||||
text,
|
text,
|
||||||
ast,
|
ast,
|
||||||
@@ -2225,6 +2257,7 @@ pub mod ops {
|
|||||||
level,
|
level,
|
||||||
margin,
|
margin,
|
||||||
captions,
|
captions,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3447,6 +3480,7 @@ pub mod ops {
|
|||||||
header: &str,
|
header: &str,
|
||||||
level: u8,
|
level: u8,
|
||||||
margin: usize,
|
margin: usize,
|
||||||
|
insert_line: Option<u32>,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
let body = build_tag_links_text(pages_by_tag, tag, header, level, margin)?;
|
let body = build_tag_links_text(pages_by_tag, tag, header, level, margin)?;
|
||||||
let heading = tag_section_heading(tag, header);
|
let heading = tag_section_heading(tag, header);
|
||||||
@@ -3457,9 +3491,9 @@ pub mod ops {
|
|||||||
b.edit(uri.clone(), text_edit_replace(span, body, text, utf8));
|
b.edit(uri.clone(), text_edit_replace(span, body, text, utf8));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let with_sep = format!("{body}\n");
|
// Fresh section: at the cursor line, else the end of the file.
|
||||||
let insert_pos = eof_position(text);
|
let (pos, block) = section_insert(text, insert_line, eof_position(text), &body);
|
||||||
b.edit(uri.clone(), text_edit_insert(insert_pos, with_sep));
|
b.edit(uri.clone(), text_edit_insert(pos, block));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(b.build())
|
Some(b.build())
|
||||||
@@ -3480,6 +3514,7 @@ pub mod ops {
|
|||||||
margin: usize,
|
margin: usize,
|
||||||
) -> Option<WorkspaceEdit> {
|
) -> Option<WorkspaceEdit> {
|
||||||
find_section_range(ast, header)?;
|
find_section_range(ast, header)?;
|
||||||
|
// Rebuild-on-save only ever replaces an existing section in place.
|
||||||
tag_links_edit(
|
tag_links_edit(
|
||||||
text,
|
text,
|
||||||
ast,
|
ast,
|
||||||
@@ -3490,6 +3525,7 @@ pub mod ops {
|
|||||||
header,
|
header,
|
||||||
level,
|
level,
|
||||||
margin,
|
margin,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ fn nuwiki_generate_links_lists_all_pages_excluding_current() {
|
|||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
.is_some());
|
.is_some());
|
||||||
@@ -430,7 +431,8 @@ fn nuwiki_generate_tag_links_builds_section() {
|
|||||||
true,
|
true,
|
||||||
"Generated Tags",
|
"Generated Tags",
|
||||||
1,
|
1,
|
||||||
0
|
0,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.is_some());
|
.is_some());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ fn tag_links_edit_inserts_when_section_missing() {
|
|||||||
"Generated Tags",
|
"Generated Tags",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("got an edit");
|
.expect("got an edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -264,6 +265,37 @@ fn tag_links_edit_inserts_when_section_missing() {
|
|||||||
assert!(te.new_text.contains("[[Alpha]]"));
|
assert!(te.new_text.contains("[[Alpha]]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tag_links_edit_inserts_at_cursor_line() {
|
||||||
|
// A fresh tags section goes at the cursor line, not the EOF.
|
||||||
|
let src = "= Notes =\nbody\nmore\n";
|
||||||
|
let ast = parse(src);
|
||||||
|
let uri = Url::parse("file:///tmp/p.wiki").unwrap();
|
||||||
|
let mut snap = BTreeMap::new();
|
||||||
|
snap.insert("release".to_string(), vec!["Alpha".into()]);
|
||||||
|
let edit = ops::tag_links_edit(
|
||||||
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
Some("release"),
|
||||||
|
&snap,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
Some(1),
|
||||||
|
)
|
||||||
|
.expect("edit");
|
||||||
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
|
assert_eq!(te.range.start.line, 1);
|
||||||
|
assert_eq!(te.range.start.character, 0);
|
||||||
|
assert!(
|
||||||
|
te.new_text.starts_with("\n= Tag: release ="),
|
||||||
|
"got: {:?}",
|
||||||
|
te.new_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tag_links_edit_replaces_existing_section() {
|
fn tag_links_edit_replaces_existing_section() {
|
||||||
let src = "= Tag: release =\n- [[Stale]]\n\n= Other =\n";
|
let src = "= Tag: release =\n- [[Stale]]\n\n= Other =\n";
|
||||||
@@ -281,6 +313,7 @@ fn tag_links_edit_replaces_existing_section() {
|
|||||||
"Generated Tags",
|
"Generated Tags",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("edit");
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -296,8 +329,19 @@ fn tag_links_edit_full_index_replaces_existing_tags_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("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, "Generated Tags", 1, 0)
|
let edit = ops::tag_links_edit(
|
||||||
.expect("edit");
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
None,
|
||||||
|
&snap,
|
||||||
|
true,
|
||||||
|
"Generated Tags",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.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]]"));
|
||||||
@@ -352,7 +396,8 @@ fn tag_links_edit_returns_none_for_unknown_tag() {
|
|||||||
true,
|
true,
|
||||||
"Generated Tags",
|
"Generated Tags",
|
||||||
1,
|
1,
|
||||||
0
|
0,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.is_none());
|
.is_none());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -597,6 +597,7 @@ fn links_edit_inserts_when_section_absent() {
|
|||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("edit");
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -605,6 +606,37 @@ fn links_edit_inserts_when_section_absent() {
|
|||||||
assert!(!te.new_text.contains("[[Home]]"));
|
assert!(!te.new_text.contains("[[Home]]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn links_edit_inserts_at_cursor_line() {
|
||||||
|
// A fresh Generated Links section goes at the cursor line, not the EOF.
|
||||||
|
let src = "= Home =\nbody\nmore\n";
|
||||||
|
let ast = parse(src);
|
||||||
|
let uri = Url::parse("file:///tmp/page.wiki").unwrap();
|
||||||
|
let pages = vec!["A".into()];
|
||||||
|
let edit = ops::links_edit(
|
||||||
|
src,
|
||||||
|
&ast,
|
||||||
|
&uri,
|
||||||
|
"Home",
|
||||||
|
&pages,
|
||||||
|
true,
|
||||||
|
"Generated Links",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
Some(1),
|
||||||
|
)
|
||||||
|
.expect("edit");
|
||||||
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
|
assert_eq!(te.range.start.line, 1);
|
||||||
|
assert_eq!(te.range.start.character, 0);
|
||||||
|
assert!(
|
||||||
|
te.new_text.starts_with("\n= Generated Links ="),
|
||||||
|
"got: {:?}",
|
||||||
|
te.new_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn links_edit_replaces_when_section_present() {
|
fn links_edit_replaces_when_section_present() {
|
||||||
let src = "= Generated Links =\n- [[Stale]]\n\n= Real =\n";
|
let src = "= Generated Links =\n- [[Stale]]\n\n= Real =\n";
|
||||||
@@ -622,6 +654,7 @@ fn links_edit_replaces_when_section_present() {
|
|||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.expect("edit");
|
.expect("edit");
|
||||||
let te = &edit.changes.unwrap()[&uri][0];
|
let te = &edit.changes.unwrap()[&uri][0];
|
||||||
@@ -645,6 +678,7 @@ fn links_edit_returns_none_for_empty_page_list() {
|
|||||||
"Generated Links",
|
"Generated Links",
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
.is_none());
|
.is_none());
|
||||||
|
|||||||
+5
-2
@@ -530,7 +530,8 @@ Page generation ~
|
|||||||
|
|
||||||
*:NuwikiGenerateLinks*
|
*:NuwikiGenerateLinks*
|
||||||
:NuwikiGenerateLinks
|
:NuwikiGenerateLinks
|
||||||
Insert a flat list of every page in the wiki under the cursor.
|
Insert a flat list of every page in the wiki. A new section is inserted at
|
||||||
|
the cursor line; an existing one is refreshed in place.
|
||||||
|
|
||||||
*:NuwikiCheckLinks*
|
*:NuwikiCheckLinks*
|
||||||
:[range]NuwikiCheckLinks
|
:[range]NuwikiCheckLinks
|
||||||
@@ -556,7 +557,9 @@ Tags ~
|
|||||||
*:NuwikiGenerateTagLinks*
|
*:NuwikiGenerateTagLinks*
|
||||||
:NuwikiGenerateTagLinks [tag]
|
:NuwikiGenerateTagLinks [tag]
|
||||||
Insert a section linking every page that has `<tag>`. With no
|
Insert a section linking every page that has `<tag>`. With no
|
||||||
argument, generates a section per tag found in the workspace.
|
argument, generates a section per tag found in the workspace. A new
|
||||||
|
section is inserted at the cursor line; an existing one is refreshed in
|
||||||
|
place.
|
||||||
|
|
||||||
*:NuwikiRebuildTags*
|
*:NuwikiRebuildTags*
|
||||||
:NuwikiRebuildTags[!]
|
:NuwikiRebuildTags[!]
|
||||||
|
|||||||
@@ -521,12 +521,12 @@ end
|
|||||||
-- `:VimwikiGenerateLinks [rel_path]` — optional path scopes the links to a
|
-- `:VimwikiGenerateLinks [rel_path]` — optional path scopes the links to a
|
||||||
-- subtree via the server's generateForPath; no arg = every page.
|
-- subtree via the server's generateForPath; no arg = every page.
|
||||||
M.links_generate = function(path)
|
M.links_generate = function(path)
|
||||||
|
-- Cursor line (0-based) so a fresh section is inserted there.
|
||||||
|
local line = vim.api.nvim_win_get_cursor(0)[1] - 1
|
||||||
if path and path ~= '' then
|
if path and path ~= '' then
|
||||||
local args = uri_args()[1]
|
exec('nuwiki.links.generateForPath', { { uri = buf_uri(), path = path, line = line } })
|
||||||
args.path = path
|
|
||||||
exec('nuwiki.links.generateForPath', { args })
|
|
||||||
else
|
else
|
||||||
exec('nuwiki.links.generate', uri_args())
|
exec('nuwiki.links.generate', { { uri = buf_uri(), line = line } })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -606,7 +606,8 @@ function M.tags_search(query)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function M.tags_generate_links(tag)
|
function M.tags_generate_links(tag)
|
||||||
local args = { uri = buf_uri() }
|
-- Cursor line (0-based) so a fresh section is inserted there.
|
||||||
|
local args = { uri = buf_uri(), line = vim.api.nvim_win_get_cursor(0)[1] - 1 }
|
||||||
if tag and tag ~= '' then
|
if tag and tag ~= '' then
|
||||||
args.tag = tag
|
args.tag = tag
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user