Major clean up and improvements to vimwiki compatibility and documentation.
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
//! `workspace/executeCommand` dispatcher.
|
||||
//!
|
||||
//! Phase 13 introduced the router + `nuwiki.file.delete`. Phase 14 added
|
||||
//! the cheap surgical edits — checkbox toggle/cycle/reject, heading
|
||||
//! promote/demote, "next task" navigation. Phase 15 layers on the
|
||||
//! generation + workspace-query commands: `toc.generate`,
|
||||
//! `links.generate`, `workspace.checkLinks`, `workspace.findOrphans`.
|
||||
//! The router handles `nuwiki.file.delete`, the cheap surgical edits —
|
||||
//! checkbox toggle/cycle/reject, heading promote/demote, "next task"
|
||||
//! navigation — and the generation + workspace-query commands:
|
||||
//! `toc.generate`, `links.generate`, `workspace.checkLinks`,
|
||||
//! `workspace.findOrphans`.
|
||||
//!
|
||||
//! Every command resolves to either a `WorkspaceEdit` (the server then
|
||||
//! calls `apply_edit`) or a JSON `Value` (returned to the client) — see
|
||||
@@ -87,7 +87,17 @@ pub(crate) async fn execute(
|
||||
Ok(list_checkbox(backend, args, ops::toggle_state)?.map(CommandOutcome::Edit))
|
||||
}
|
||||
"nuwiki.list.cycleCheckbox" => {
|
||||
Ok(list_checkbox(backend, args, ops::cycle_state)?.map(CommandOutcome::Edit))
|
||||
let reverse = args
|
||||
.first()
|
||||
.and_then(|v| v.get("reverse"))
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let mutate = if reverse {
|
||||
ops::cycle_state_back
|
||||
} else {
|
||||
ops::cycle_state
|
||||
};
|
||||
Ok(list_checkbox(backend, args, mutate)?.map(CommandOutcome::Edit))
|
||||
}
|
||||
"nuwiki.list.rejectCheckbox" => {
|
||||
Ok(list_checkbox(backend, args, ops::reject_state)?.map(CommandOutcome::Edit))
|
||||
@@ -653,7 +663,7 @@ fn tags_rebuild(backend: &Backend, args: Vec<Value>) -> Result<Option<Value>, St
|
||||
})))
|
||||
}
|
||||
|
||||
// ===== Phase 18: multi-wiki picker commands =====
|
||||
// ===== multi-wiki picker commands =====
|
||||
|
||||
fn wiki_list_all(backend: &Backend) -> Result<Option<Value>, String> {
|
||||
let wikis = backend.wikis_snapshot();
|
||||
@@ -811,7 +821,7 @@ fn wiki_goto_page(backend: &Backend, args: Vec<Value>) -> Result<Option<Value>,
|
||||
})))
|
||||
}
|
||||
|
||||
// ===== §13.1 Cluster A: list rewriters =====
|
||||
// ===== Cluster A: list rewriters =====
|
||||
|
||||
fn list_remove_done(backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
||||
let p = parse_optional_uri_arg(args.clone())?;
|
||||
@@ -944,7 +954,7 @@ fn list_change_level(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
|
||||
))
|
||||
}
|
||||
|
||||
// ===== §13.1 Cluster B: table rewriters =====
|
||||
// ===== Cluster B: table rewriters =====
|
||||
|
||||
fn table_insert(_backend: &Backend, args: Vec<Value>) -> Result<Option<WorkspaceEdit>, String> {
|
||||
#[derive(Deserialize)]
|
||||
@@ -1020,7 +1030,7 @@ fn table_move_column(backend: &Backend, args: Vec<Value>) -> Result<Option<Works
|
||||
))
|
||||
}
|
||||
|
||||
// ===== Phase 17: HTML export commands =====
|
||||
// ===== HTML export commands =====
|
||||
|
||||
fn export_current(
|
||||
backend: &Backend,
|
||||
@@ -1233,6 +1243,20 @@ pub mod ops {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact inverse of [`cycle_state`] — used by the `glp` mapping
|
||||
/// ("cycle the checkbox state backward").
|
||||
pub fn cycle_state_back(current: &str) -> Option<&'static str> {
|
||||
match current {
|
||||
"[ ]" => Some("[X]"),
|
||||
"[X]" => Some("[O]"),
|
||||
"[O]" => Some("[o]"),
|
||||
"[o]" => Some("[.]"),
|
||||
"[.]" => Some("[ ]"),
|
||||
"[-]" => Some("[ ]"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle the `[-]` "rejected" state.
|
||||
pub fn reject_state(current: &str) -> Option<&'static str> {
|
||||
match current {
|
||||
@@ -1647,7 +1671,7 @@ pub mod ops {
|
||||
None
|
||||
}
|
||||
|
||||
// ===== Phase 15: TOC + Links generation, workspace queries =====
|
||||
// ===== TOC + Links generation, workspace queries =====
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1973,7 +1997,7 @@ pub mod ops {
|
||||
/// on the diagnostics module's internals.
|
||||
pub use crate::diagnostics::collect_wiki_links;
|
||||
|
||||
// ===== §13.1 Cluster A: list rewriters =====
|
||||
// ===== Cluster A: list rewriters =====
|
||||
|
||||
use crate::edits::text_edit_delete;
|
||||
use nuwiki_core::ast::ListSymbol;
|
||||
@@ -2095,7 +2119,9 @@ pub mod ops {
|
||||
|
||||
/// Delete every checkbox item whose state is `Done` or `Rejected`,
|
||||
/// cascading into their sublists. When `pos` is `Some`, restrict to
|
||||
/// the list containing `pos`'s line; otherwise sweep the whole doc.
|
||||
/// the contiguous list block containing `pos`'s line (the "current
|
||||
/// list" — including its nested sublists); otherwise sweep the whole
|
||||
/// doc.
|
||||
pub fn remove_done_edit(
|
||||
text: &str,
|
||||
ast: &DocumentNode,
|
||||
@@ -2103,16 +2129,8 @@ pub mod ops {
|
||||
pos: Option<LspPosition>,
|
||||
utf8: bool,
|
||||
) -> Option<WorkspaceEdit> {
|
||||
let restrict_line = pos.map(|p| p.line);
|
||||
let mut victims: Vec<Span> = Vec::new();
|
||||
walk_list_items(&ast.children, &mut |item| {
|
||||
let matches_scope = match restrict_line {
|
||||
None => true,
|
||||
Some(line) => (item.span.start.line..=item.span.end.line).contains(&line),
|
||||
};
|
||||
if !matches_scope {
|
||||
return;
|
||||
}
|
||||
let mut collect = |item: &ListItemNode| {
|
||||
if matches!(
|
||||
item.checkbox,
|
||||
Some(nuwiki_core::ast::CheckboxState::Done)
|
||||
@@ -2120,7 +2138,19 @@ pub mod ops {
|
||||
) {
|
||||
victims.push(extend_to_line_end(text, item.span));
|
||||
}
|
||||
});
|
||||
};
|
||||
match pos {
|
||||
None => walk_list_items(&ast.children, &mut collect),
|
||||
Some(p) => {
|
||||
// "Current list" = the top-level contiguous list block the
|
||||
// cursor sits in. Walk just that block (and its sublists),
|
||||
// leaving done items in sibling lists untouched.
|
||||
let list = find_top_list_at_line(ast, p.line)?;
|
||||
for item in &list.items {
|
||||
walk_list_items_in_item(item, &mut collect);
|
||||
}
|
||||
}
|
||||
}
|
||||
if victims.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -2131,6 +2161,40 @@ pub mod ops {
|
||||
Some(b.build())
|
||||
}
|
||||
|
||||
/// Find the top-level (contiguous) list block whose span contains
|
||||
/// `line`. Unlike [`find_list_at_line`], this does not descend into
|
||||
/// sublists — it returns the outermost list so callers operate on the
|
||||
/// whole list block the cursor belongs to.
|
||||
fn find_top_list_at_line(ast: &DocumentNode, line: u32) -> Option<&ListNode> {
|
||||
for block in &ast.children {
|
||||
if let Some(list) = find_top_list_in_block(block, line) {
|
||||
return Some(list);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_top_list_in_block(block: &BlockNode, line: u32) -> Option<&ListNode> {
|
||||
match block {
|
||||
BlockNode::List(list) => {
|
||||
if (list.span.start.line..=list.span.end.line).contains(&line) {
|
||||
Some(list)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
BlockNode::Blockquote(BlockquoteNode { children, .. }) => {
|
||||
for c in children {
|
||||
if let Some(l) = find_top_list_in_block(c, line) {
|
||||
return Some(l);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a span to include the trailing newline (so deletion removes
|
||||
/// the empty line that would otherwise be left behind).
|
||||
fn extend_to_line_end(text: &str, span: Span) -> Span {
|
||||
@@ -2480,7 +2544,7 @@ pub mod ops {
|
||||
Some((span, marker_str, i))
|
||||
}
|
||||
|
||||
// ===== §13.1 Cluster B: table rewriters =====
|
||||
// ===== Cluster B: table rewriters =====
|
||||
|
||||
use nuwiki_core::ast::{TableNode, TableRowNode};
|
||||
|
||||
@@ -2738,7 +2802,7 @@ pub mod ops {
|
||||
Some(out)
|
||||
}
|
||||
|
||||
// ===== Phase 16: diary helpers =====
|
||||
// ===== diary helpers =====
|
||||
|
||||
use crate::edits::op_create;
|
||||
|
||||
@@ -2792,10 +2856,10 @@ pub mod ops {
|
||||
b.build()
|
||||
}
|
||||
|
||||
// ===== Tag ops (Phase 12 commands) =====
|
||||
// ===== Tag ops =====
|
||||
//
|
||||
// SPEC §12.3 calls for three tag-driven commands beyond the indexing
|
||||
// that Phase 12 already shipped. `tag_hits` powers `nuwiki.tags.search`;
|
||||
// Three tag-driven commands beyond the indexing itself.
|
||||
// `tag_hits` powers `nuwiki.tags.search`;
|
||||
// `tag_links_edit` powers `nuwiki.tags.generateLinks`; the rebuild
|
||||
// command is a direct re-walk of the wiki root in the dispatcher.
|
||||
|
||||
@@ -2984,7 +3048,7 @@ pub mod ops {
|
||||
}
|
||||
}
|
||||
|
||||
/// Disk-touching helpers for the Phase 17 `nuwiki.export.*` commands.
|
||||
/// Disk-touching helpers for the `nuwiki.export.*` commands.
|
||||
/// Kept out of `ops` because everything here is side-effecting — pure
|
||||
/// rendering / templating lives in [`crate::export`].
|
||||
pub mod export_ops {
|
||||
|
||||
Reference in New Issue
Block a user