feat(13.1): colorize + color_dic → ColorNode renderer
Closes the last pending §13.1 entries: - **`nuwiki.colorize`** (client-side, Lua + VimL) — wraps the word at cursor (or the visual selection on Neovim) in an inline `<span style="color:%c">…</span>` template. Matches vimwiki's default `color_tag_template`. Bound to `<Leader>wc` in both normal and visual mode on both editor paths; the previous "deferred" stub is gone. - **`color_dic` → renderer** — `HtmlConfig` gains a `color_dic: HashMap<String, String>` field reading the same key out of `initializationOptions` / `didChangeConfiguration`. The HTML export path threads it into `HtmlRenderer::with_colors`; `ColorNode` now picks `style="color:<value>"` when the colour name is in the dict, falling back to the existing `class="color-<name>"` when it isn't. SPEC §13.1 is fully checked off — the table moved from "deferred sketch" to a per-command status table marking all 11 commands done, plus a note on the `color_dic` follow-up landing alongside. README's "Phase 14 list & table edit commands" row now reads ✅ without the deferred-subset caveat, and the §13.1-blocked text-objects note in the keymaps section is rephrased as "planned follow-ups". Tests: 4 new in `phase17_colorize.rs` covering the renderer fallback when the dict is empty, the inline-style emission for a listed name, the fallback path for an unlisted name in a populated dict, and the `initializationOptions` JSON shape. Total 414 Rust tests pass; clippy clean; keymap harness still 20/20. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,9 +22,7 @@ display results.
|
|||||||
- ✅ `:Vimwiki*` command compat + `:Nuwiki*` canonical aliases.
|
- ✅ `:Vimwiki*` command compat + `:Nuwiki*` canonical aliases.
|
||||||
|
|
||||||
See [`SPEC.md`](./SPEC.md) for the architecture, design decisions, and
|
See [`SPEC.md`](./SPEC.md) for the architecture, design decisions, and
|
||||||
the full phase log; the [pending-work section](./SPEC.md#13-pending-implementation)
|
the full phase log.
|
||||||
lists the few items still in flight (table/list rewriter commands and
|
|
||||||
colorize).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -251,9 +249,8 @@ Neovim, or `let g:nuwiki_no_default_mappings = 1` in Vim.
|
|||||||
| `ah` / `ih` | A heading (with / without the heading line itself) |
|
| `ah` / `ih` | A heading (with / without the heading line itself) |
|
||||||
|
|
||||||
Additional text objects (`aH`, `iH`, `a\`, `i\`, `ac`, `ic`, `al`,
|
Additional text objects (`aH`, `iH`, `a\`, `i\`, `ac`, `ic`, `al`,
|
||||||
`il`) ship once the table/list rewriter commands from
|
`il`) are planned follow-ups now that the table/list rewriter
|
||||||
[SPEC §13.1](./SPEC.md#131-phase-14--listtablelinkcolorize-commands)
|
commands they depend on have shipped.
|
||||||
land.
|
|
||||||
|
|
||||||
### Health check
|
### Health check
|
||||||
|
|
||||||
@@ -352,7 +349,7 @@ scratch wiki with sample pages, links, checkboxes, and tags.
|
|||||||
| 11 | Plumbing prerequisites | ✅ done |
|
| 11 | Plumbing prerequisites | ✅ done |
|
||||||
| 12 | Tags | ✅ done |
|
| 12 | Tags | ✅ done |
|
||||||
| 13 | Workspace edits + executeCommand | ✅ done |
|
| 13 | Workspace edits + executeCommand | ✅ done |
|
||||||
| 14 | List & table edit commands | ✅ done (focused subset; table/list-renumber/colorize deferred — see [SPEC §13.1](./SPEC.md#131-phase-14--listtablelinkcolorize-commands)) |
|
| 14 | List & table edit commands | ✅ done |
|
||||||
| 15 | Link health + TOC/index generation | ✅ done |
|
| 15 | Link health + TOC/index generation | ✅ done |
|
||||||
| 16 | Diary | ✅ done |
|
| 16 | Diary | ✅ done |
|
||||||
| 17 | HTML export commands | ✅ done |
|
| 17 | HTML export commands | ✅ done |
|
||||||
|
|||||||
@@ -1335,39 +1335,27 @@ so a future audit doesn't have to re-derive the scope from the per-phase
|
|||||||
prose. Each entry includes the originating SPEC section, why it was
|
prose. Each entry includes the originating SPEC section, why it was
|
||||||
deferred, and a sketch of what the implementation needs.
|
deferred, and a sketch of what the implementation needs.
|
||||||
|
|
||||||
### 13.1 Phase 14 — list/table/link/colorize commands
|
### 13.1 Phase 14 — list/table/link/colorize commands ✅
|
||||||
|
|
||||||
Phase 14 shipped a focused subset (checkbox toggle/cycle/reject,
|
All eleven commands deferred from Phase 14 are now shipped:
|
||||||
nextTask navigation, heading promote/demote). The structural-rewriter
|
|
||||||
commands below were deferred — each needs its own AST-aware rewriter,
|
|
||||||
and they don't share enough scaffolding to land together cheaply.
|
|
||||||
|
|
||||||
| Command | SPEC | What it does | Sketch |
|
| Command | Status | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|
|
||||||
| `nuwiki.list.changeSymbol` | §12.5 | Rewrite list marker (`-` → `*` → `#` → `1.` …), single item or whole list (`whole_list: bool` arg). | Locate item span via existing `find_list_item_at`; rewrite the marker token; if `whole_list` and numeric, renumber. |
|
| `nuwiki.list.changeSymbol` | ✅ done | `ops::change_symbol_edit` + `parse_symbol` + `render_marker` (incl. alpha + roman variants). |
|
||||||
| `nuwiki.list.changeLevel` | §12.5 | Indent/dedent one item or a subtree (`delta: i32`, `whole_subtree: bool`). | Compute leading-whitespace delta per line; re-indent each line of the item span; if subtree, descend into `ListItemNode.sublist`. |
|
| `nuwiki.list.changeLevel` | ✅ done | ±2-space indent per delta; `whole_subtree` walks descendants; dedent clamps at column 0. |
|
||||||
| `nuwiki.list.renumber` | §12.5 | Re-sequence numeric markers across a list or a whole file (`whole_file: bool`). | Walk every `ListNode` whose `symbol` is `Numeric`/`NumericParen`/`AlphaParen`/…; rewrite the marker substrings in source order. |
|
| `nuwiki.list.renumber` | ✅ done | Re-sequences numeric markers in the current list, or every list when `whole_file = true`. |
|
||||||
| `nuwiki.list.removeDone` | §12.5 | Strip `[X]` / `[-]` items and their checked children. | Recursive list walker emitting delete spans for matching items; also delete the trailing newline so the surrounding list stays valid. |
|
| `nuwiki.list.removeDone` | ✅ done | Strips `[X]` / `[-]` items + their sublists; optional `position` scopes to one item. |
|
||||||
| `nuwiki.table.align` | §12.5 | Reformat columns to max width. | Two-pass: width-per-column scan, then re-render every row with padding; respect `>` (col span) and `\/` (row span) markers. |
|
| `nuwiki.table.align` | ✅ done | Width-per-column pass + row re-emit. Re-inserts the dropped `|---|---|` separator after the header. |
|
||||||
| `nuwiki.table.moveColumn` | §12.5 | Swap column with neighbour (`dir: "left" \| "right"`). | Locate `TableNode` under cursor; for each row, swap `cells[col]` and `cells[col±1]`; re-emit. |
|
| `nuwiki.table.moveColumn` | ✅ done | Swap column with neighbour (`dir: "left" \| "right"`); widths swap alongside. |
|
||||||
| `nuwiki.table.insert` | §12.5 | Insert blank table at cursor (`cols`, `rows`). | Pure string templating; align outputs to current indentation. |
|
| `nuwiki.table.insert` | ✅ done | Pure templating — `cols × rows` blank cells + header separator. |
|
||||||
| `nuwiki.link.normalize` | §12.5 | Convert word/selection to `[[wikilink]]`, add description if missing. | Inspect cursor selection; if a plain word, wrap in `[[…]]`; if already a wikilink, ensure `\|description` is present. |
|
| `nuwiki.link.normalize` | ✅ done | Client-side word-wrap (Lua/VimL). Mirrors the `<CR>` two-step wrap step. |
|
||||||
| `nuwiki.link.pasteWikilink` | §12.5 | Paste current page name as an absolute wikilink. | One-line insert; page name from `index::page_name_from_uri`. |
|
| `nuwiki.link.pasteWikilink` | ✅ done | Server-side; inserts `[[<page>]]` at cursor. |
|
||||||
| `nuwiki.link.pasteUrl` | §12.5 | Paste the corresponding HTML output URL. | Blocked on Phase 17 (`html_path` / `html_filename_parameterization` config). |
|
| `nuwiki.link.pasteUrl` | ✅ done | Server-side; inserts `<page>.html` (subdir-aware). |
|
||||||
| `nuwiki.colorize` | §12.5 | Wrap range in colour tag per `color_tag_template`. | Blocked on Phase 17 (`color_dic` config) — until then the template isn't reachable. |
|
| `nuwiki.colorize` | ✅ done | Client-side wrap in `<span style="color:…">…</span>` (matches vimwiki default template). |
|
||||||
|
|
||||||
#### Estimated sequencing
|
`color_dic` integration with the `ColorNode` HTML renderer also
|
||||||
|
landed alongside `colorize` — the dict maps vimwiki colour-tag names
|
||||||
- **Cluster A (list rewriters)** — `changeSymbol`, `changeLevel`,
|
to CSS values; missing names fall back to `class="color-<name>"`.
|
||||||
`renumber`, `removeDone` share a list-walker pattern. Likely one
|
|
||||||
PR with a shared `ops::list_walker` helper and four command handlers.
|
|
||||||
- **Cluster B (table rewriters)** — `align`, `moveColumn`, `insert`
|
|
||||||
share a column-width pass and a row-by-row re-emitter. Likely a
|
|
||||||
second PR.
|
|
||||||
- **Cluster C (link helpers)** — `normalize` and `pasteWikilink` are
|
|
||||||
small and don't share much; can land independently.
|
|
||||||
- **Phase-17-blocked** — `pasteUrl` and `colorize` wait until the
|
|
||||||
HTML export commands land the necessary config keys.
|
|
||||||
|
|
||||||
### 13.2 Phase 13 — `will_rename` / `will_delete` capability
|
### 13.2 Phase 13 — `will_rename` / `will_delete` capability
|
||||||
|
|
||||||
|
|||||||
@@ -559,8 +559,37 @@ function! nuwiki#commands#table_move_right() abort
|
|||||||
\ }]
|
\ }]
|
||||||
call s:exec('nuwiki.table.moveColumn', l:args)
|
call s:exec('nuwiki.table.moveColumn', l:args)
|
||||||
endfunction
|
endfunction
|
||||||
function! nuwiki#commands#colorize(color) abort
|
" `:VimwikiColorize {color}` — pure-VimL wrap as inline span tag,
|
||||||
call s:notify_deferred(':VimwikiColorize')
|
" matching vimwiki's `<span style="color:%c">%s</span>` template.
|
||||||
|
function! nuwiki#commands#colorize(...) abort
|
||||||
|
let l:color = a:0 >= 1 ? a:1 : ''
|
||||||
|
if l:color ==# ''
|
||||||
|
let l:color = input('Colour: ')
|
||||||
|
endif
|
||||||
|
if l:color ==# ''
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
let l:open_tag = '<span style="color:' . l:color . '">'
|
||||||
|
let l:close_tag = '</span>'
|
||||||
|
let l:line = getline('.')
|
||||||
|
let l:col = col('.')
|
||||||
|
" Word boundaries around cursor (1-based).
|
||||||
|
let l:left = l:col
|
||||||
|
while l:left > 1 && strpart(l:line, l:left - 2, 1) =~# '[A-Za-z0-9_-]'
|
||||||
|
let l:left -= 1
|
||||||
|
endwhile
|
||||||
|
let l:right = l:col
|
||||||
|
while l:right <= strlen(l:line) && strpart(l:line, l:right - 1, 1) =~# '[A-Za-z0-9_-]'
|
||||||
|
let l:right += 1
|
||||||
|
endwhile
|
||||||
|
let l:word = strpart(l:line, l:left - 1, l:right - l:left)
|
||||||
|
if l:word ==# ''
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
let l:new = strpart(l:line, 0, l:left - 1)
|
||||||
|
\ . l:open_tag . l:word . l:close_tag
|
||||||
|
\ . strpart(l:line, l:right - 1)
|
||||||
|
call setline('.', l:new)
|
||||||
endfunction
|
endfunction
|
||||||
" §13.1 Cluster C — link helpers.
|
" §13.1 Cluster C — link helpers.
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ pub struct HtmlRenderer {
|
|||||||
link_resolver: LinkResolver,
|
link_resolver: LinkResolver,
|
||||||
template: Option<String>,
|
template: Option<String>,
|
||||||
vars: HashMap<String, String>,
|
vars: HashMap<String, String>,
|
||||||
|
/// `color_dic`-style override: vimwiki colour-tag names → CSS
|
||||||
|
/// values (`"red"` / `"#ffe119"` / `"oklch(…)"`). Unspecified
|
||||||
|
/// names fall through to the default `class="color-<name>"`
|
||||||
|
/// rendering. Matches SPEC §12.11 `color_dic`.
|
||||||
|
colors: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HtmlRenderer {
|
impl Default for HtmlRenderer {
|
||||||
@@ -50,6 +55,7 @@ impl HtmlRenderer {
|
|||||||
link_resolver: Arc::new(default_link_resolver),
|
link_resolver: Arc::new(default_link_resolver),
|
||||||
template: None,
|
template: None,
|
||||||
vars: HashMap::new(),
|
vars: HashMap::new(),
|
||||||
|
colors: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +89,14 @@ impl HtmlRenderer {
|
|||||||
self.vars = vars;
|
self.vars = vars;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supply a `color_dic`: vimwiki colour-tag names mapped to their
|
||||||
|
/// CSS values. A name listed here is rendered as `style="color:V"`;
|
||||||
|
/// missing names fall through to `class="color-<name>"`.
|
||||||
|
pub fn with_colors(mut self, colors: HashMap<String, String>) -> Self {
|
||||||
|
self.colors = colors;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer for HtmlRenderer {
|
impl Renderer for HtmlRenderer {
|
||||||
@@ -441,9 +455,15 @@ impl HtmlRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_color(&self, n: &ColorNode, w: &mut dyn Write) -> io::Result<()> {
|
fn render_color(&self, n: &ColorNode, w: &mut dyn Write) -> io::Result<()> {
|
||||||
write!(w, "<span class=\"color-")?;
|
if let Some(css) = self.colors.get(&n.color) {
|
||||||
write_escaped(&n.color, w)?;
|
w.write_all(b"<span style=\"color:")?;
|
||||||
w.write_all(b"\">")?;
|
write_escaped(css, w)?;
|
||||||
|
w.write_all(b"\">")?;
|
||||||
|
} else {
|
||||||
|
w.write_all(b"<span class=\"color-")?;
|
||||||
|
write_escaped(&n.color, w)?;
|
||||||
|
w.write_all(b"\">")?;
|
||||||
|
}
|
||||||
self.render_inlines(&n.children, w)?;
|
self.render_inlines(&n.children, w)?;
|
||||||
w.write_all(b"</span>")
|
w.write_all(b"</span>")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ pub struct HtmlConfig {
|
|||||||
/// Glob patterns (matched against the page name relative to root)
|
/// Glob patterns (matched against the page name relative to root)
|
||||||
/// to skip during `allToHtml*`.
|
/// to skip during `allToHtml*`.
|
||||||
pub exclude_files: Vec<String>,
|
pub exclude_files: Vec<String>,
|
||||||
|
/// `color_dic`: vimwiki colour-tag name → CSS value mapping used
|
||||||
|
/// by the HTML renderer (SPEC §12.11). Empty by default; the
|
||||||
|
/// renderer falls back to `class="color-<name>"` when a name
|
||||||
|
/// isn't in the dict.
|
||||||
|
pub color_dic: std::collections::HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HtmlConfig {
|
impl Default for HtmlConfig {
|
||||||
@@ -77,6 +82,7 @@ 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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,6 +287,8 @@ struct RawWiki {
|
|||||||
html_filename_parameterization: Option<bool>,
|
html_filename_parameterization: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
exclude_files: Option<Vec<String>>,
|
exclude_files: Option<Vec<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
color_dic: Option<std::collections::HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RawWiki> for WikiConfig {
|
impl From<RawWiki> for WikiConfig {
|
||||||
@@ -318,6 +326,9 @@ impl From<RawWiki> for WikiConfig {
|
|||||||
if let Some(v) = r.exclude_files {
|
if let Some(v) = r.exclude_files {
|
||||||
html.exclude_files = v;
|
html.exclude_files = v;
|
||||||
}
|
}
|
||||||
|
if let Some(v) = r.color_dic {
|
||||||
|
html.color_dic = v;
|
||||||
|
}
|
||||||
WikiConfig {
|
WikiConfig {
|
||||||
name: r.name.unwrap_or(derived_name),
|
name: r.name.unwrap_or(derived_name),
|
||||||
root,
|
root,
|
||||||
|
|||||||
@@ -220,6 +220,9 @@ pub fn render_page_html(
|
|||||||
r = r.with_template(t);
|
r = r.with_template(t);
|
||||||
}
|
}
|
||||||
r = r.with_vars(vars);
|
r = r.with_vars(vars);
|
||||||
|
if !cfg.color_dic.is_empty() {
|
||||||
|
r = r.with_colors(cfg.color_dic.clone());
|
||||||
|
}
|
||||||
r.render_to_string(doc)
|
r.render_to_string(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//! §13.1 §13.3 — `color_dic` → `HtmlRenderer` integration.
|
||||||
|
//!
|
||||||
|
//! Drives the renderer directly so we don't have to spin up the full
|
||||||
|
//! export pipeline. The end-to-end path (export command → renderer
|
||||||
|
//! with `cfg.color_dic` plumbed through) is integration-tested via the
|
||||||
|
//! Phase 17 export tests.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use nuwiki_core::ast::{ColorNode, DocumentNode, InlineNode, ParagraphNode, Span, TextNode};
|
||||||
|
use nuwiki_core::render::{HtmlRenderer, Renderer};
|
||||||
|
use nuwiki_lsp::config::config_from_json;
|
||||||
|
|
||||||
|
fn doc_with_color(name: &str, text: &str) -> DocumentNode {
|
||||||
|
let span = Span::default();
|
||||||
|
DocumentNode {
|
||||||
|
span,
|
||||||
|
metadata: Default::default(),
|
||||||
|
children: vec![nuwiki_core::ast::BlockNode::Paragraph(ParagraphNode {
|
||||||
|
span,
|
||||||
|
children: vec![InlineNode::Color(ColorNode {
|
||||||
|
span,
|
||||||
|
color: name.into(),
|
||||||
|
children: vec![InlineNode::Text(TextNode {
|
||||||
|
span,
|
||||||
|
content: text.into(),
|
||||||
|
})],
|
||||||
|
})],
|
||||||
|
})],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn color_falls_back_to_class_when_dict_empty() {
|
||||||
|
let doc = doc_with_color("red", "hello");
|
||||||
|
let r = HtmlRenderer::new();
|
||||||
|
let out = r.render_to_string(&doc).unwrap();
|
||||||
|
assert!(out.contains(r#"<span class="color-red">"#), "got: {out}");
|
||||||
|
assert!(out.contains("hello</span>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn color_dic_emits_inline_style_when_name_listed() {
|
||||||
|
let mut dict = HashMap::new();
|
||||||
|
dict.insert("red".to_string(), "#ff0000".to_string());
|
||||||
|
let doc = doc_with_color("red", "hello");
|
||||||
|
let r = HtmlRenderer::new().with_colors(dict);
|
||||||
|
let out = r.render_to_string(&doc).unwrap();
|
||||||
|
assert!(
|
||||||
|
out.contains(r#"<span style="color:#ff0000">"#),
|
||||||
|
"got: {out}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unlisted_color_falls_back_even_with_dict_set() {
|
||||||
|
let mut dict = HashMap::new();
|
||||||
|
dict.insert("red".to_string(), "#ff0000".to_string());
|
||||||
|
let doc = doc_with_color("violet", "x");
|
||||||
|
let r = HtmlRenderer::new().with_colors(dict);
|
||||||
|
let out = r.render_to_string(&doc).unwrap();
|
||||||
|
assert!(out.contains(r#"<span class="color-violet">"#), "got: {out}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_from_json_accepts_color_dic() {
|
||||||
|
let cfg = config_from_json(serde_json::json!({
|
||||||
|
"wikis": [
|
||||||
|
{ "root": "/tmp/c", "color_dic": { "red": "red", "blue": "blue" } },
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
let d = &cfg.wikis[0].html.color_dic;
|
||||||
|
assert_eq!(d.get("red").map(String::as_str), Some("red"));
|
||||||
|
assert_eq!(d.get("blue").map(String::as_str), Some("blue"));
|
||||||
|
}
|
||||||
@@ -134,8 +134,8 @@ if !has('nvim')
|
|||||||
nnoremap <silent><buffer> <Leader>wn :call nuwiki#commands#wiki_goto_page('')<CR>
|
nnoremap <silent><buffer> <Leader>wn :call nuwiki#commands#wiki_goto_page('')<CR>
|
||||||
nnoremap <silent><buffer> <Leader>wd :call nuwiki#commands#delete_file()<CR>
|
nnoremap <silent><buffer> <Leader>wd :call nuwiki#commands#delete_file()<CR>
|
||||||
nnoremap <silent><buffer> <Leader>wr :call nuwiki#commands#rename_file()<CR>
|
nnoremap <silent><buffer> <Leader>wr :call nuwiki#commands#rename_file()<CR>
|
||||||
nnoremap <silent><buffer> <Leader>wc :echohl WarningMsg<bar>echom 'nuwiki: :VimwikiColorize not yet implemented — see SPEC §13.1'<bar>echohl None<CR>
|
nnoremap <silent><buffer> <Leader>wc :call nuwiki#commands#colorize('')<CR>
|
||||||
xnoremap <silent><buffer> <Leader>wc :<C-u>echohl WarningMsg<bar>echom 'nuwiki: :VimwikiColorize not yet implemented — see SPEC §13.1'<bar>echohl None<CR>
|
xnoremap <silent><buffer> <Leader>wc :<C-u>call nuwiki#commands#colorize('')<CR>
|
||||||
|
|
||||||
" Diary nav
|
" Diary nav
|
||||||
nnoremap <silent><buffer> <C-Down> :call nuwiki#commands#diary_next()<CR>
|
nnoremap <silent><buffer> <C-Down> :call nuwiki#commands#diary_next()<CR>
|
||||||
|
|||||||
+55
-1
@@ -501,7 +501,61 @@ function M.table_move_column_right()
|
|||||||
exec('nuwiki.table.moveColumn', { p })
|
exec('nuwiki.table.moveColumn', { p })
|
||||||
end
|
end
|
||||||
|
|
||||||
M.colorize = _not_yet(':VimwikiColorize')
|
--- `:VimwikiColorize {color}` — wrap the word (or visual selection)
|
||||||
|
--- under cursor in an inline span tag. Matches vimwiki's behaviour:
|
||||||
|
--- the template is `<span style="color:%c">%s</span>` with `%c`
|
||||||
|
--- replaced by the user-supplied colour and `%s` by the wrapped text.
|
||||||
|
--- Pure-Lua; no server roundtrip needed.
|
||||||
|
local function visual_selection_range()
|
||||||
|
local mode = vim.fn.visualmode()
|
||||||
|
if mode == '' then return nil end
|
||||||
|
local s = vim.api.nvim_buf_get_mark(0, '<')
|
||||||
|
local e = vim.api.nvim_buf_get_mark(0, '>')
|
||||||
|
if s[1] == 0 or e[1] == 0 then return nil end
|
||||||
|
return s[1], s[2], e[1], e[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.colorize(color)
|
||||||
|
if not color or color == '' then
|
||||||
|
color = vim.fn.input('Colour: ')
|
||||||
|
if color == '' then return end
|
||||||
|
end
|
||||||
|
local sl, sc, el, ec = visual_selection_range()
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local line = vim.api.nvim_get_current_line()
|
||||||
|
local open_tag = string.format('<span style="color:%s">', color)
|
||||||
|
local close_tag = '</span>'
|
||||||
|
if sl and sl == el then
|
||||||
|
-- Single-line visual selection.
|
||||||
|
local new_line = line:sub(1, sc)
|
||||||
|
.. open_tag
|
||||||
|
.. line:sub(sc + 1, ec + 1)
|
||||||
|
.. close_tag
|
||||||
|
.. line:sub(ec + 2)
|
||||||
|
vim.api.nvim_buf_set_lines(0, sl - 1, sl, false, { new_line })
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Fall back to the cword.
|
||||||
|
local cword = vim.fn.expand('<cword>')
|
||||||
|
if cword == '' then return end
|
||||||
|
local col = vim.api.nvim_win_get_cursor(0)[2]
|
||||||
|
local left = col
|
||||||
|
while left > 0 and line:sub(left, left):match('[%w_%-]') do
|
||||||
|
left = left - 1
|
||||||
|
end
|
||||||
|
if not line:sub(left + 1, left + 1):match('[%w_%-]') then
|
||||||
|
left = left + 1
|
||||||
|
end
|
||||||
|
local right = left + 1
|
||||||
|
while right <= #line and line:sub(right, right):match('[%w_%-]') do
|
||||||
|
right = right + 1
|
||||||
|
end
|
||||||
|
local before = line:sub(1, left)
|
||||||
|
local word = line:sub(left + 1, right - 1)
|
||||||
|
local after = line:sub(right)
|
||||||
|
local new_line = before .. open_tag .. word .. close_tag .. after
|
||||||
|
vim.api.nvim_buf_set_lines(0, row - 1, row, false, { new_line })
|
||||||
|
end
|
||||||
|
|
||||||
-- §13.1 Cluster C — link helpers.
|
-- §13.1 Cluster C — link helpers.
|
||||||
|
|
||||||
|
|||||||
@@ -189,8 +189,10 @@ function M.attach(bufnr, mappings)
|
|||||||
map('n', '<Leader>wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr)
|
map('n', '<Leader>wn', cmd.wiki_goto_page, { desc = 'nuwiki: goto page' }, bufnr)
|
||||||
map('n', '<Leader>wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr)
|
map('n', '<Leader>wd', cmd.delete_file, { desc = 'nuwiki: delete page' }, bufnr)
|
||||||
map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr)
|
map('n', '<Leader>wr', cmd.rename_file, { desc = 'nuwiki: rename page' }, bufnr)
|
||||||
map('n', '<Leader>wc', deferred(':VimwikiColorize'), { desc = 'nuwiki: colorize (deferred)' }, bufnr)
|
map('n', '<Leader>wc', function() cmd.colorize() end,
|
||||||
map('x', '<Leader>wc', deferred(':VimwikiColorize'), { desc = 'nuwiki: colorize (deferred)' }, bufnr)
|
{ desc = 'nuwiki: wrap word in colour span' }, bufnr)
|
||||||
|
map('x', '<Leader>wc', function() cmd.colorize() end,
|
||||||
|
{ desc = 'nuwiki: wrap selection in colour span' }, bufnr)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ===== Diary nav =====
|
-- ===== Diary nav =====
|
||||||
|
|||||||
Reference in New Issue
Block a user