From cf336ee8394c6f7b3c1ca6588d4fc1a77fedfce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Fr=C3=B3es=20Franco?= Date: Sun, 10 May 2026 16:01:58 +0000 Subject: [PATCH] phase 0: scaffold workspace, plugin layout, and CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lay down the empty repo skeleton defined in SPEC.md §5 so subsequent phases have somewhere to land: - Cargo workspace (resolver v2, edition 2021, MSRV 1.83) with nuwiki-core, nuwiki-lsp, nuwiki-ls — dep direction matches §6.2. - Vim/Neovim plugin directory layout (plugin, lua, autoload, ftdetect, ftplugin, syntax, doc, scripts) with header-only stubs. - .gitea/workflows/ci.yaml running fmt, clippy -D warnings, and tests pinned to Rust 1.83. - README, dual MIT/Apache-2.0 license texts, .gitignore. Verified: cargo check / fmt --check / clippy / test all clean. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/ci.yaml | 57 ++++ .gitignore | 16 + Cargo.lock | 21 ++ Cargo.toml | 22 ++ LICENSE-APACHE | 201 ++++++++++++ LICENSE-MIT | 21 ++ README.md | 48 +++ SPEC.md | 596 ++++++++++++++++++++++++++++++++++ autoload/nuwiki/lsp.vim | 2 + crates/nuwiki-core/Cargo.toml | 15 + crates/nuwiki-core/src/lib.rs | 4 + crates/nuwiki-ls/Cargo.toml | 20 ++ crates/nuwiki-ls/src/main.rs | 1 + crates/nuwiki-lsp/Cargo.toml | 16 + crates/nuwiki-lsp/src/lib.rs | 4 + doc/nuwiki.txt | 5 + ftdetect/nuwiki.vim | 2 + ftplugin/nuwiki.vim | 2 + lua/nuwiki/config.lua | 6 + lua/nuwiki/init.lua | 6 + lua/nuwiki/install.lua | 6 + lua/nuwiki/lsp.lua | 6 + plugin/nuwiki.vim | 8 + scripts/download_bin.vim | 2 + syntax/nuwiki.vim | 2 + 25 files changed, 1089 insertions(+) create mode 100644 .gitea/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 SPEC.md create mode 100644 autoload/nuwiki/lsp.vim create mode 100644 crates/nuwiki-core/Cargo.toml create mode 100644 crates/nuwiki-core/src/lib.rs create mode 100644 crates/nuwiki-ls/Cargo.toml create mode 100644 crates/nuwiki-ls/src/main.rs create mode 100644 crates/nuwiki-lsp/Cargo.toml create mode 100644 crates/nuwiki-lsp/src/lib.rs create mode 100644 doc/nuwiki.txt create mode 100644 ftdetect/nuwiki.vim create mode 100644 ftplugin/nuwiki.vim create mode 100644 lua/nuwiki/config.lua create mode 100644 lua/nuwiki/init.lua create mode 100644 lua/nuwiki/install.lua create mode 100644 lua/nuwiki/lsp.lua create mode 100644 plugin/nuwiki.vim create mode 100644 scripts/download_bin.vim create mode 100644 syntax/nuwiki.vim diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..f752887 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + pull_request: + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: short + RUSTFLAGS: -D warnings + +jobs: + fmt: + name: cargo fmt --check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.83 + with: + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + name: cargo clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.83 + with: + components: clippy + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-cargo-clippy- + - run: cargo clippy --workspace --all-targets -- -D warnings + + test: + name: cargo test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@1.83 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock', '**/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-cargo-test- + - run: cargo test --workspace --all-targets diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14bc173 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Rust build artifacts +/target +**/*.rs.bk + +# Plugin runtime: downloaded LSP binary lives here (see SPEC.md §7.2) +/bin + +# Editor / OS noise +.DS_Store +*.swp +*.swo +.idea/ +.vscode/ + +# Vim help tags (regenerated by :helptags) +doc/tags diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..171bb73 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,21 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "nuwiki-core" +version = "0.1.0" + +[[package]] +name = "nuwiki-ls" +version = "0.1.0" +dependencies = [ + "nuwiki-lsp", +] + +[[package]] +name = "nuwiki-lsp" +version = "0.1.0" +dependencies = [ + "nuwiki-core", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c630683 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +resolver = "2" +members = [ + "crates/nuwiki-core", + "crates/nuwiki-lsp", + "crates/nuwiki-ls", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +rust-version = "1.83" +authors = ["Gabriel Fróes Franco "] +license = "MIT OR Apache-2.0" +repository = "https://code.gfran.co/gffranco/nuwiki" +homepage = "https://code.gfran.co/gffranco/nuwiki" + +[workspace.lints.rust] +unsafe_code = "forbid" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..905b115 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for describing the origin of the Work and + reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or support. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Gabriel Fróes Franco + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..3257bc0 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Gabriel Fróes Franco + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e69de29..92ad588 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,48 @@ +# nuwiki + +A Vim/Neovim plugin providing full vimwiki syntax support, implemented as a +Rust-based language server (LSP). + +> **Status:** Phase 0 — scaffolding. Not yet functional. + +See [`SPEC.md`](./SPEC.md) for the full project specification. + +## Repository layout + +``` +nuwiki/ +├── Cargo.toml workspace root +├── crates/ +│ ├── nuwiki-core/ parser, AST, renderer (no editor deps) +│ ├── nuwiki-lsp/ LSP protocol bridge +│ └── nuwiki-ls/ binary that speaks LSP over stdio +├── plugin/ universal Vim/Neovim entry point +├── lua/nuwiki/ Neovim Lua glue +├── autoload/nuwiki/ Vim VimL glue +├── ftdetect/ filetype detection for .wiki +├── ftplugin/ per-buffer filetype settings +├── syntax/ static fallback highlighting +├── doc/ `:h nuwiki` +├── scripts/ build-time helpers +└── .gitea/workflows/ CI / release pipelines +``` + +## Building + +```sh +cargo build --workspace +cargo test --workspace +``` + +MSRV: Rust 1.83. + +## License + +Dual-licensed under either of: + +- Apache License, Version 2.0 ([`LICENSE-APACHE`](./LICENSE-APACHE)) +- MIT license ([`LICENSE-MIT`](./LICENSE-MIT)) + +at your option. Unless you explicitly state otherwise, any contribution +intentionally submitted for inclusion in this work shall be dual licensed as +above, without any additional terms or conditions. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..1a5aa64 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,596 @@ +# nuwiki — Project Specification + +> Last updated: 2026-05-08 +> Status: In design — Phase 0 not yet started + +--- + +## 1. Overview + +nuwiki is a Vim/Neovim plugin that provides full vimwiki syntax support, implemented as a Rust-based Language Server (LSP). It is a spiritual successor to vimwiki, architecturally distinct from it, designed for correctness, performance, and extensibility. + +**Goals:** +- Full vimwiki syntax support (highlighting, navigation, link following, diagnostics) +- First-class support for both Vim 9+ and Neovim 0.5+ +- Installable via standard plugin managers (lazy.nvim, vim-plug, Dein) +- Extensible to Markdown syntax without changes to core architecture +- Independently testable core library with no editor dependencies + +**Non-goals (v1.0):** +- MediaWiki syntax support +- Multi-wiki configurations +- Incremental / tree-sitter parsing +- Browser-based or WASM build + +--- + +## 2. Repository + +| Property | Value | +|---|---| +| License | Dual MIT/Apache-2.0 | +| MSRV | Rust 1.83 (stable-2) | +|---|---| +| URL | `https://code.gfran.co/gffranco/nuwiki` | +| Version control | Gitea | +| CI/CD | Gitea Actions | + +--- + +## 3. Naming Conventions + +| Context | Name | +|---|---| +| GitHub/Gitea repo | `nuwiki` | +| Cargo workspace | `nuwiki` | +| Core library crate | `nuwiki-core` | +| LSP bridge crate | `nuwiki-lsp` | +| Binary crate | `nuwiki-ls` | +| Vim plugin entry | `plugin/nuwiki.vim` | +| Lua module | `require('nuwiki')` | +| VimL autoload | `nuwiki#lsp#start()` | +| Vim help file | `doc/nuwiki.txt` → `:h nuwiki` | +| Release binary | `nuwiki-ls-{version}-{target}.tar.gz` | + +--- + +## 4. Technology Stack + +| Concern | Choice | Rationale | +|---|---|---| +| Implementation language | Rust | Performance, type safety, single binary distribution, ideal for AST modelling | +| Editor integration | LSP over stdio | Works in both Vim and Neovim without a shared scripting language | +| LSP server library | `tower-lsp` | Async, tokio-based, higher-level than `lsp-server`; easier to start with | +| Parser library | `winnow` | Successor to `nom`, designed for resilient/partial parsing; strong error recovery | +| Cargo edition | 2021 | Current standard; required for resolver v2 | +| Cargo resolver | v2 | Required for edition 2021 workspaces | +| Vim glue layer | VimL (`plugin/nuwiki.vim`, `autoload/`) | Universal entry point for all plugin managers | +| Neovim glue layer | Lua (`lua/nuwiki/`) | First-class Neovim API access via `vim.lsp.start()` | + +--- + +## 5. Repository Layout + +``` +nuwiki/ +│ +├── Cargo.toml # Rust workspace root +├── Cargo.lock +│ +├── crates/ +│ ├── nuwiki-ls/ # Binary crate — thin main.rs, starts stdio LSP server +│ │ ├── Cargo.toml +│ │ └── src/main.rs +│ │ +│ ├── nuwiki-core/ # Library crate — parser, AST, renderer (no editor deps) +│ │ ├── Cargo.toml +│ │ └── src/ +│ │ ├── lib.rs +│ │ ├── syntax/ +│ │ │ ├── mod.rs +│ │ │ ├── registry.rs +│ │ │ └── vimwiki/ +│ │ │ ├── mod.rs +│ │ │ ├── lexer.rs +│ │ │ └── parser.rs +│ │ ├── ast/ +│ │ │ ├── mod.rs +│ │ │ ├── block.rs +│ │ │ ├── inline.rs +│ │ │ └── link.rs +│ │ └── render/ +│ │ ├── mod.rs +│ │ └── html.rs +│ │ +│ └── nuwiki-lsp/ # Library crate — LSP protocol bridge +│ ├── Cargo.toml +│ └── src/lib.rs +│ +├── plugin/ # Universal Vim/Neovim entry point +│ └── nuwiki.vim +│ +├── lua/ # Neovim-specific Lua layer +│ └── nuwiki/ +│ ├── init.lua +│ ├── config.lua +│ ├── lsp.lua +│ └── install.lua # Binary download / build-from-source logic +│ +├── autoload/ # Lazy-loaded VimL (Vim compat layer) +│ └── nuwiki/ +│ └── lsp.vim +│ +├── ftdetect/ # Filetype detection for .wiki files +│ └── nuwiki.vim +│ +├── ftplugin/ # Per-filetype buffer settings +│ └── nuwiki.vim +│ +├── syntax/ # Static fallback syntax highlighting (no LSP required) +│ └── nuwiki.vim +│ +├── doc/ # Vim help documentation +│ └── nuwiki.txt +│ +├── scripts/ +│ └── download_bin.vim # VimL binary download (used by Dein/vim-plug build hooks) +│ +└── .gitea/ + └── workflows/ + ├── ci.yaml # Lint, test, fmt check on every push/PR + └── release.yaml # Cross-compile + release on v* tag +``` + +--- + +## 6. Architecture + +### 6.1 Layer Model + +``` +┌─────────────────────────────────────────────────────┐ +│ Consumer Layer │ +│ (editor highlight, HTML export, TOC, …) │ +└────────────────────────┬────────────────────────────┘ + │ operates on + ▼ +┌─────────────────────────────────────────────────────┐ +│ AST (nuwiki-core, shared) │ +│ Document > Block nodes > Inline nodes │ +└────────────────────────┬────────────────────────────┘ + │ produced by + ▼ +┌─────────────────────────────────────────────────────┐ +│ Parser — syntax-specific (nuwiki-core) │ +│ Registered per syntax; receives TokenStream │ +└────────────────────────┬────────────────────────────┘ + │ consumes + ▼ +┌─────────────────────────────────────────────────────┐ +│ Lexer — syntax-specific (nuwiki-core) │ +│ Registered per syntax; receives raw text │ +└────────────────────────┬────────────────────────────┘ + │ reads + ▼ + Raw text input +``` + +### 6.2 Crate Dependency Rules + +``` +nuwiki-ls → nuwiki-lsp → nuwiki-core +``` + +- `nuwiki-core` must never depend on `nuwiki-lsp` or `nuwiki-ls` +- `nuwiki-lsp` must never depend on `nuwiki-ls` +- The VimL and Lua editor layers must never contain logic — pure wiring only + +### 6.3 Syntax Plugin Interface + +Every syntax (vimwiki, future markdown) implements: + +``` +SyntaxPlugin + id: string -- "vimwiki" | "markdown" + display_name: string + file_extensions: string[] -- [".wiki"] | [".md"] + lexer: fn(text) → TokenStream + parser: fn(TokenStream) → DocumentNode +``` + +Registry: +``` +SyntaxRegistry + register(plugin: SyntaxPlugin) + get(id: &str) → Option<&SyntaxPlugin> + detect_from_extension(ext: &str) → Option<&SyntaxPlugin> +``` + +All plugins must be `Send + Sync` (held in the LSP server registry). + +### 6.4 AST Node Types + +#### Document +``` +DocumentNode + children: Vec + metadata: PageMetadata -- title, nohtml, template, date +``` + +#### Block Nodes +``` +HeadingNode level: 1–6 | children: Vec | centered: bool +ParagraphNode children: Vec +HorizontalRuleNode +BlockquoteNode children: Vec +PreformattedNode content: String | language: Option +MathBlockNode content: String | environment: Option +ListNode ordered: bool | symbol: ListSymbol | items: Vec +DefinitionListNode items: Vec +TableNode rows: Vec | has_header: bool +CommentNode content: String +ErrorNode raw: String | message: String -- resilient parse failure +``` + +#### Inline Nodes +``` +TextNode content: String +BoldNode children: Vec +ItalicNode children: Vec +BoldItalicNode children: Vec +StrikethroughNode children: Vec +CodeNode content: String +SuperscriptNode children: Vec +SubscriptNode children: Vec +MathInlineNode content: String +KeywordNode keyword: Keyword -- TODO|DONE|STARTED|FIXME|FIXED|XXX +ColorNode color: String | children: Vec +WikiLinkNode target: LinkTarget | description: Option> +ExternalLinkNode url: String | description: Option> +TransclusionNode url: String | alt: Option | attrs: HashMap +RawUrlNode url: String +``` + +#### Supporting Types +``` +ListItemNode + symbol: ListSymbol + level: usize + checkbox: Option + children: Vec + sublist: Option + +DefinitionItemNode + term: Option> + definitions: Vec> + +TableRowNode cells: Vec | is_header: bool +TableCellNode children: Vec | col_span: bool | row_span: bool + +LinkTarget + kind: LinkKind -- Wiki|Interwiki|Diary|File|Local|Raw|AnchorOnly + path: Option + wiki_index: Option + wiki_name: Option + anchor: Option + is_absolute: bool + is_directory: bool + +ListSymbol = Dash | Star | Hash | Numeric | NumericParen + | AlphaParen | AlphaUpperParen | RomanParen | RomanUpperParen +CheckboxState = Empty | Quarter | Half | ThreeQuarters | Done | Rejected +Keyword = Todo | Done | Started | Fixme | Fixed | Xxx +``` + +### 6.5 Span / Source Location + +Every AST node carries a `Span`: + +```rust +struct Span { + start: Position, + end: Position, +} + +struct Position { + line: u32, -- 0-indexed + column: u32, -- 0-indexed, byte offset within line + offset: usize, -- absolute byte offset from document start +} +``` + +Spans are required for LSP diagnostics, semantic tokens, and go-to-definition. + +### 6.6 Lexer Strategy + +- **Two-pass:** block-level pass first, then inline pass within each block +- **Token types:** syntax-specific (`VimwikiToken`) — not shared across syntaxes +- **TokenStream:** eager `Vec` wrapped in a `TokenStream` newtype (changeable to lazy later) +- Operates on Rust `char`s for correctness; spans stored as byte offsets + +### 6.7 Parser Strategy + +- Uses `winnow` parser combinator library +- Resilient: on malformed input, emits `ErrorNode` and continues — never fails the whole document +- Inline marker precedence rules documented explicitly in code and tests + +### 6.8 Renderer + +- `Renderer` trait: writer-based (`fn render(&self, doc: &DocumentNode, w: &mut dyn Write)`) +- `HtmlRenderer` is the first implementation +- Link resolution injected as a callback at construction time +- Template support: minimal `{{content}}` / `{{title}}` token substitution +- CSS: linked external stylesheet (matches vimwiki convention) + +### 6.9 LSP Features + +| Feature | LSP Method | +|---|---| +| Syntax highlighting | `textDocument/semanticTokens/full` + `/range` | +| Diagnostics (broken links, parse errors) | `textDocument/publishDiagnostics` | +| Follow link / go to definition | `textDocument/definition` | +| Backlinks | `textDocument/references` | +| TOC / outline | `textDocument/documentSymbol` | +| Link preview | `textDocument/hover` | +| Link autocomplete | `textDocument/completion` (trigger: `[[`) | +| Page rename | `workspace/rename` | +| Workspace search | `workspace/symbol` | + +### 6.10 Document Store + +``` +DocumentStore: Arc> + +DocumentState + text: String + ast: DocumentNode + version: i32 +``` + +Full re-parse on every `didOpen` / `didChange`. Incremental parsing deferred post-v1. + +### 6.11 UTF-16 / Position Encoding + +LSP positions negotiated as UTF-8 (`positionEncoding = "utf-8"`) during `initialize` where the client supports LSP 3.17+. UTF-16 conversion fallback implemented in the LSP layer for older clients. `nuwiki-core` is always UTF-8 / byte-offset internally. + +--- + +## 7. Editor Integration + +### 7.1 Plugin Manager Installation + +**lazy.nvim:** +```lua +{ + "gffranco/nuwiki", + build = "lua require('nuwiki').install()", + ft = { "vimwiki" }, + opts = {}, +} +``` + +**vim-plug:** +```vim +Plug 'gffranco/nuwiki', { 'do': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' } +``` + +**Dein:** +```vim +call dein#add('gffranco/nuwiki', { + \ 'build': 'vim -e -s -c "source scripts/download_bin.vim" -c "q"' + \ }) +``` + +### 7.2 Binary Distribution + +- Pre-built binaries published as Gitea release assets +- Named: `nuwiki-ls-{version}-{target}.tar.gz` +- Downloaded at install time by `lua/nuwiki/install.lua` or `scripts/download_bin.vim` +- Fallback: `cargo build --release` if download fails or `g:nuwiki_build_from_source = 1` +- Binary installed to `{plugin_dir}/bin/nuwiki-ls[.exe]` + +### 7.3 Editor Detection + +```vim +" plugin/nuwiki.vim +if has('nvim') + lua require('nuwiki').setup() +else + call nuwiki#lsp#start() +endif +``` + +### 7.4 Vim LSP Client Preference Order (Vim only) + +1. `vim-lsp` (matoto/vim-lsp) +2. `coc.nvim` +3. Error message if neither found + +### 7.5 User Config Schema + +```lua +require('nuwiki').setup({ + wiki_root = "~/vimwiki", -- root directory of the wiki + file_extension = ".wiki", -- file extension to associate + syntax = "vimwiki", -- "vimwiki" | "markdown" (future) + log_level = "warn", -- "error"|"warn"|"info"|"debug" +}) +``` + +### 7.6 Health Check + +`:checkhealth nuwiki` verifies: +- Binary exists at `{plugin_dir}/bin/nuwiki-ls` +- Binary is executable and responds to `--version` +- LSP client is running +- Filetype detection works for `.wiki` files + +--- + +## 8. CI/CD + +### 8.1 Runner Setup + +| Property | Value | +|---|---| +| System | Gitea Actions (`act_runner`) | +| Mode | Docker (jobs run in containers) | +| Docker access | Host socket mounted (`/var/run/docker.sock`) | +| Internet access | Yes | + +### 8.2 Workflow Files + +**`.gitea/workflows/ci.yaml`** — triggers on every push and PR: +- `cargo fmt --check` +- `cargo clippy -- -D warnings` +- `cargo test --workspace` + +**`.gitea/workflows/release.yaml`** — triggers on `v*` tag push: +- Cross-compile for all 4 Linux targets using `cross` +- Package binaries as `.tar.gz` +- Create Gitea release and upload assets via REST API using `RELEASE_TOKEN` secret +- Publish `nuwiki-core` to crates.io using `CARGO_REGISTRY_TOKEN` secret + +### 8.3 Build Targets + +| Target | Built by | +|---|---| +| `x86_64-unknown-linux-gnu` | CI (`cross`) | +| `aarch64-unknown-linux-gnu` | CI (`cross`) | +| `x86_64-unknown-linux-musl` | CI (`cross`) | +| `aarch64-unknown-linux-musl` | CI (`cross`) | +| `x86_64-apple-darwin` | Manual | +| `aarch64-apple-darwin` | Manual | +| `x86_64-pc-windows-msvc` | Manual | + +### 8.4 Release Secrets + +| Secret | Purpose | +|---|---| +| `RELEASE_TOKEN` | Gitea personal access token for creating releases and uploading assets | +| `CARGO_REGISTRY_TOKEN` | crates.io API token for publishing `nuwiki-core` | + +### 8.5 `actions/cache` Configuration + +Requires `act_runner`'s `config.yaml` to have the cache server host set to the runner host's LAN IP so job containers can reach it. + +### 8.6 Breaking Change Policy + +A semver-breaking change (`v0.x` → `v0.x+1` or `v1.x` → `v2.0`) is defined as any of: +- AST node type additions, removals, or field changes in `nuwiki-core` +- `SyntaxPlugin` or `Renderer` trait signature changes +- User config schema key removals or type changes +- Removed LSP capabilities + +--- + +## 9. Vimwiki Syntax Feature Checklist + +### Typefaces +- [ ] Bold: `*text*` +- [ ] Italic: `_text_` +- [ ] Bold italic: `_*text*_` / `*_text_*` +- [ ] Strikethrough: `~~text~~` +- [ ] Inline code: `` `text` `` +- [ ] Superscript: `super^script^` +- [ ] Subscript: `sub,,script,,` +- [ ] Keywords: `TODO` `DONE` `STARTED` `FIXME` `FIXED` `XXX` + +### Links +- [ ] Plain wikilink: `[[Target]]` +- [ ] Described wikilink: `[[Target|Description]]` +- [ ] Subdirectory wikilink: `[[dir/Page]]` +- [ ] Root-relative wikilink: `[[/Page]]` +- [ ] Filesystem-absolute wikilink: `[[//path]]` +- [ ] Subdirectory link: `[[dir/]]` +- [ ] Interwiki numbered: `[[wiki1:Page]]` +- [ ] Interwiki named: `[[wn.Name:Page]]` +- [ ] Diary link: `[[diary:YYYY-MM-DD]]` +- [ ] Anchor-only link: `[[#Anchor]]` +- [ ] Wikilink with anchor: `[[Page#Anchor]]` +- [ ] Raw URLs: `https://…` `mailto:…` `ftp://…` +- [ ] External file link: `[[file:path]]` / `[[local:path]]` +- [ ] Transclusion: `{{URL}}` with optional alt and attrs +- [ ] Thumbnail link: `[[imgURL|{{thumbURL}}]]` + +### Headers +- [ ] Levels 1–6: `= H1 =` … `====== H6 ======` +- [ ] Centered header (leading whitespace before `=`) + +### Lists +- [ ] Unordered: `-` and `*` +- [ ] Ordered: `1.` `1)` `a)` `A)` `i)` `I)` `#` +- [ ] Nested and mixed types +- [ ] Multi-line items (indentation continuation) +- [ ] Definition lists: `Term:: Definition` +- [ ] Checkboxes: `[ ]` `[.]` `[o]` `[O]` `[X]` `[-]` + +### Tables +- [ ] Basic `|`-delimited table +- [ ] Header row separator: `|---|` +- [ ] Column span: `>` +- [ ] Row span: `\/` +- [ ] Inline formatting inside cells + +### Preformatted Text +- [ ] Fenced block: `{{{ … }}}` +- [ ] Optional language/class on opening fence + +### Mathematical Formulae +- [ ] Inline math: `$ … $` +- [ ] Block display math: `{{$ … }}$` +- [ ] Block environment math: `{{$%env% … }}$` + +### Blockquotes +- [ ] 4-space indent blockquote +- [ ] `>` prefix blockquote + +### Comments +- [ ] Single-line: `%% …` +- [ ] Multi-line: `%%+ … +%%` + +### Horizontal Rule +- [ ] Four or more dashes: `----` + +### Placeholders +- [ ] `%title ` +- [ ] `%nohtml` +- [ ] `%template ` +- [ ] `%date ` + +--- + +## 10. Implementation Phases + +| Phase | Name | Key Output | +|---|---|---| +| 0 | Scaffolding | Compiling empty workspace + CI skeleton | +| 1 | Core AST | All node types, spans, Visitor trait | +| 2 | Syntax Plugin Interface | `Lexer`/`Parser`/`SyntaxPlugin` traits, `SyntaxRegistry` | +| 3 | Vimwiki Lexer | `VimwikiToken`, two-pass lexer with spans | +| 4 | Vimwiki Parser | Full AST from token stream, error recovery | +| 5 | Renderer | `Renderer` trait + `HtmlRenderer` | +| 6 | LSP Foundation | `didOpen`/`didChange`, diagnostics, document symbols | +| 7 | Semantic Tokens | AST → LSP semantic token stream | +| 8 | Navigation | Definition, references, hover, completion, workspace index | +| 9 | Editor Glue | Installable plugin, health check, binary download | +| 10 | CI/CD | Automated lint/test/release pipeline | + +--- + +## 11. Pending Decisions + +These decisions are required before or during the phase indicated. + +| # | Decision | Needed by | Options | Notes | +|---|---|---|---|---| +| ~~P1~~ | ~~**License**~~ | ~~Phase 0~~ | ✅ **Dual MIT/Apache-2.0** | | +| ~~P2~~ | ~~**MSRV**~~ | ~~Phase 0~~ | ✅ **stable-2 (Rust 1.83)** | | +| P3 | **String representation in AST** | Phase 1 | `String` (owned) · `Cow<'a, str>` · `Arc` | `String` simplest; `Cow` enables zero-copy parsing later | +| P4 | **Visitor pattern** | Phase 1 | Define trait now · Defer to Phase 5 | Recommend defining interface now; avoids refactor when renderer arrives | +| P5 | **Minimum Neovim version** | Phase 9 | 0.8 (`vim.lsp.start`) · 0.11 (`vim.lsp.config`) | 0.8 = broader compat; 0.11 = cleaner Lua API | +| P6 | **Minimum Vim version** | Phase 9 | Vim 8.2 · Vim 9.0 · Vim 9.1 | LSP client plugins work best on 9.0+; recommend documenting 9.1 as minimum | +| P7 | **Semantic token type mapping** | Phase 7 | Standard LSP types only · Custom token types | Custom types need client-side highlight group definitions; more flexible but more setup for users | +| P8 | **Workspace indexing strategy** | Phase 8 | Eager on startup · Lazy + background | Eager blocks startup on large wikis; recommend lazy with progress notification | +| P9 | **macOS runner** | Phase 10 | Stay manual · Register macOS runner +| Revisit if a Mac is available; no action needed now | diff --git a/autoload/nuwiki/lsp.vim b/autoload/nuwiki/lsp.vim new file mode 100644 index 0000000..16be926 --- /dev/null +++ b/autoload/nuwiki/lsp.vim @@ -0,0 +1,2 @@ +" autoload/nuwiki/lsp.vim — Vim (non-Neovim) LSP client wiring. +" Implementation deferred to Phase 9 (Editor Glue). diff --git a/crates/nuwiki-core/Cargo.toml b/crates/nuwiki-core/Cargo.toml new file mode 100644 index 0000000..8b9eb13 --- /dev/null +++ b/crates/nuwiki-core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "nuwiki-core" +description = "Core parser, AST, and renderer for nuwiki — editor-independent." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[lints] +workspace = true + +[dependencies] diff --git a/crates/nuwiki-core/src/lib.rs b/crates/nuwiki-core/src/lib.rs new file mode 100644 index 0000000..b375515 --- /dev/null +++ b/crates/nuwiki-core/src/lib.rs @@ -0,0 +1,4 @@ +//! Core parser, AST, and renderer for nuwiki. +//! +//! This crate is editor-independent and must never depend on `nuwiki-lsp` or +//! `nuwiki-ls`. See SPEC.md §6.2 for the dependency rules. diff --git a/crates/nuwiki-ls/Cargo.toml b/crates/nuwiki-ls/Cargo.toml new file mode 100644 index 0000000..992e687 --- /dev/null +++ b/crates/nuwiki-ls/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nuwiki-ls" +description = "nuwiki language server binary — speaks LSP over stdio." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[lints] +workspace = true + +[[bin]] +name = "nuwiki-ls" +path = "src/main.rs" + +[dependencies] +nuwiki-lsp = { path = "../nuwiki-lsp", version = "0.1.0" } diff --git a/crates/nuwiki-ls/src/main.rs b/crates/nuwiki-ls/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/crates/nuwiki-ls/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/crates/nuwiki-lsp/Cargo.toml b/crates/nuwiki-lsp/Cargo.toml new file mode 100644 index 0000000..711a709 --- /dev/null +++ b/crates/nuwiki-lsp/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "nuwiki-lsp" +description = "LSP protocol bridge for nuwiki, built on top of nuwiki-core." +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[lints] +workspace = true + +[dependencies] +nuwiki-core = { path = "../nuwiki-core", version = "0.1.0" } diff --git a/crates/nuwiki-lsp/src/lib.rs b/crates/nuwiki-lsp/src/lib.rs new file mode 100644 index 0000000..30c443a --- /dev/null +++ b/crates/nuwiki-lsp/src/lib.rs @@ -0,0 +1,4 @@ +//! LSP protocol bridge for nuwiki. +//! +//! Translates between LSP requests/notifications and `nuwiki-core` AST +//! operations. Must never depend on `nuwiki-ls` (see SPEC.md §6.2). diff --git a/doc/nuwiki.txt b/doc/nuwiki.txt new file mode 100644 index 0000000..d7e33b4 --- /dev/null +++ b/doc/nuwiki.txt @@ -0,0 +1,5 @@ +*nuwiki.txt* LSP-backed vimwiki support for Vim and Neovim. + +Documentation deferred to Phase 9 (Editor Glue). + + vim:tw=78:ts=8:ft=help:norl: diff --git a/ftdetect/nuwiki.vim b/ftdetect/nuwiki.vim new file mode 100644 index 0000000..1471a04 --- /dev/null +++ b/ftdetect/nuwiki.vim @@ -0,0 +1,2 @@ +" ftdetect/nuwiki.vim — filetype detection for .wiki files. +" Implementation deferred to Phase 9 (Editor Glue). diff --git a/ftplugin/nuwiki.vim b/ftplugin/nuwiki.vim new file mode 100644 index 0000000..dd8dd89 --- /dev/null +++ b/ftplugin/nuwiki.vim @@ -0,0 +1,2 @@ +" ftplugin/nuwiki.vim — per-buffer filetype settings for .wiki buffers. +" Implementation deferred to Phase 9 (Editor Glue). diff --git a/lua/nuwiki/config.lua b/lua/nuwiki/config.lua new file mode 100644 index 0000000..53a63e2 --- /dev/null +++ b/lua/nuwiki/config.lua @@ -0,0 +1,6 @@ +-- lua/nuwiki/config.lua — user configuration schema and defaults. +-- Implementation deferred to Phase 9 (Editor Glue). + +local M = {} + +return M diff --git a/lua/nuwiki/init.lua b/lua/nuwiki/init.lua new file mode 100644 index 0000000..d2d2a38 --- /dev/null +++ b/lua/nuwiki/init.lua @@ -0,0 +1,6 @@ +-- lua/nuwiki/init.lua — Neovim entry point. Exposes setup() and install(). +-- Implementation deferred to Phase 9 (Editor Glue). + +local M = {} + +return M diff --git a/lua/nuwiki/install.lua b/lua/nuwiki/install.lua new file mode 100644 index 0000000..539907d --- /dev/null +++ b/lua/nuwiki/install.lua @@ -0,0 +1,6 @@ +-- lua/nuwiki/install.lua — binary download / build-from-source. +-- Implementation deferred to Phase 9 (Editor Glue). + +local M = {} + +return M diff --git a/lua/nuwiki/lsp.lua b/lua/nuwiki/lsp.lua new file mode 100644 index 0000000..94ae4de --- /dev/null +++ b/lua/nuwiki/lsp.lua @@ -0,0 +1,6 @@ +-- lua/nuwiki/lsp.lua — vim.lsp.start wiring for the nuwiki-ls binary. +-- Implementation deferred to Phase 9 (Editor Glue). + +local M = {} + +return M diff --git a/plugin/nuwiki.vim b/plugin/nuwiki.vim new file mode 100644 index 0000000..abd7405 --- /dev/null +++ b/plugin/nuwiki.vim @@ -0,0 +1,8 @@ +" plugin/nuwiki.vim — universal entry point for Vim and Neovim. +" Loaded once per session by all plugin managers. +" Implementation deferred to Phase 9 (Editor Glue). + +if exists('g:loaded_nuwiki') + finish +endif +let g:loaded_nuwiki = 1 diff --git a/scripts/download_bin.vim b/scripts/download_bin.vim new file mode 100644 index 0000000..02b534b --- /dev/null +++ b/scripts/download_bin.vim @@ -0,0 +1,2 @@ +" scripts/download_bin.vim — VimL binary download for Dein/vim-plug build hooks. +" Implementation deferred to Phase 9 (Editor Glue). diff --git a/syntax/nuwiki.vim b/syntax/nuwiki.vim new file mode 100644 index 0000000..00923aa --- /dev/null +++ b/syntax/nuwiki.vim @@ -0,0 +1,2 @@ +" syntax/nuwiki.vim — static fallback syntax highlighting (no LSP required). +" Implementation deferred to Phase 9 (Editor Glue).