Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7da13ffc5c | |||
| 31f41315b2 | |||
| caaafd3cf3 | |||
| d21237cfdc | |||
| 14301586e0 | |||
| 2c835120f8 | |||
| 84d3b03485 | |||
| d293e6b5b6 |
@@ -6,6 +6,9 @@ name: Release
|
|||||||
# the vX.Y.Z tag. The build matrix + release job then run off that tag. No
|
# the vX.Y.Z tag. The build matrix + release job then run off that tag. No
|
||||||
# more hand-editing versions across Cargo.toml and individual crate manifests.
|
# more hand-editing versions across Cargo.toml and individual crate manifests.
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
@@ -32,8 +35,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
# Push the release commit + tag back with a write-capable token.
|
|
||||||
token: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@1.83
|
- uses: dtolnay/rust-toolchain@1.83
|
||||||
|
|
||||||
@@ -48,26 +49,43 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cargo-prepare-
|
${{ runner.os }}-cargo-prepare-
|
||||||
|
|
||||||
- name: Validate version + stamp
|
- name: Stamp version + tag
|
||||||
id: stamp
|
id: stamp
|
||||||
env:
|
|
||||||
VERSION: ${{ inputs.version }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ver="${VERSION#v}" # tolerate a leading v
|
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
|
||||||
if ! printf '%s' "$ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'; then
|
# Tag pushed locally: tag already exists, just extract version
|
||||||
echo "::error::'$ver' is not a semver version (expected e.g. 0.4.0)"
|
tag="${GITHUB_REF_NAME}"
|
||||||
|
ver="${tag#v}"
|
||||||
|
else
|
||||||
|
# Manual dispatch from Gitea UI: use the input version
|
||||||
|
ver="${VERSION#v}"
|
||||||
|
tag="v${ver}"
|
||||||
|
if ! printf '%s' "$ver" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$'; then
|
||||||
|
echo "::error::'$ver' is not a semver version (expected e.g. 0.4.0)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# If the tag already exists locally, skip version stamping.
|
||||||
|
# This covers the case where the tag was created outside this
|
||||||
|
# workflow — we just need the version number for downstream jobs.
|
||||||
|
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null && \
|
||||||
|
! git ls-remote --exit-code --tags origin "$tag" >/dev/null 2>&1; then
|
||||||
|
echo "Tag already exists locally — skipping version stamp."
|
||||||
|
else
|
||||||
|
echo "Stamping version $ver …"
|
||||||
|
bash scripts/set-version.sh "$ver"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Verify the Cargo.toml version matches so downstream jobs are safe.
|
||||||
|
manifest_ver=$(grep '^version' Cargo.toml | head -1 | sed 's/version = "//;s/"//')
|
||||||
|
if [ "$ver" != "$manifest_ver" ]; then
|
||||||
|
echo "::error::version mismatch: step says $ver but Cargo.toml says $manifest_ver"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
tag="v$ver"
|
|
||||||
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null \
|
|
||||||
|| git ls-remote --exit-code --tags origin "$tag" >/dev/null 2>&1; then
|
|
||||||
echo "::error::tag $tag already exists"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
bash scripts/set-version.sh "$ver"
|
|
||||||
echo "version=$ver" >> "$GITHUB_OUTPUT"
|
echo "version=$ver" >> "$GITHUB_OUTPUT"
|
||||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
VERSION: ${{ inputs.version }}
|
||||||
|
|
||||||
# Gate: never tag code that doesn't build/test. If this fails nothing
|
# Gate: never tag code that doesn't build/test. If this fails nothing
|
||||||
# is committed or pushed, so the release simply doesn't happen.
|
# is committed or pushed, so the release simply doesn't happen.
|
||||||
@@ -75,6 +93,7 @@ jobs:
|
|||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
|
|
||||||
- name: Commit, tag, push
|
- name: Commit, tag, push
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
env:
|
env:
|
||||||
VER: ${{ steps.stamp.outputs.version }}
|
VER: ${{ steps.stamp.outputs.version }}
|
||||||
TAG: ${{ steps.stamp.outputs.tag }}
|
TAG: ${{ steps.stamp.outputs.tag }}
|
||||||
@@ -84,10 +103,18 @@ jobs:
|
|||||||
git config user.email "gitea-actions@users.noreply.code.gfran.co"
|
git config user.email "gitea-actions@users.noreply.code.gfran.co"
|
||||||
git add Cargo.toml Cargo.lock \
|
git add Cargo.toml Cargo.lock \
|
||||||
crates/nuwiki-lsp/Cargo.toml crates/nuwiki-ls/Cargo.toml
|
crates/nuwiki-lsp/Cargo.toml crates/nuwiki-ls/Cargo.toml
|
||||||
git commit -m "chore(release): $VER"
|
# Check if there are staged changes. If not, the version was already
|
||||||
git tag -a "$TAG" -m "nuwiki $VER"
|
# correct — create an empty commit just to have a release commit,
|
||||||
git push origin "HEAD:${GITHUB_REF_NAME}"
|
# and skip the tag push since it already exists on origin.
|
||||||
git push origin "$TAG"
|
if git diff --cached --quiet; then
|
||||||
|
git commit --allow-empty -m "chore(release): $VER"
|
||||||
|
echo "No version changes — skipping tag push."
|
||||||
|
else
|
||||||
|
git commit -m "chore(release): $VER"
|
||||||
|
git tag -a "$TAG" -m "nuwiki $VER"
|
||||||
|
git push origin "HEAD:${GITHUB_REF_NAME}"
|
||||||
|
git push origin "$TAG"
|
||||||
|
fi
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: build ${{ matrix.target }}
|
name: build ${{ matrix.target }}
|
||||||
@@ -115,8 +142,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# Build the just-tagged commit, not the branch tip.
|
ref: main
|
||||||
ref: ${{ needs.prepare.outputs.tag }}
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@1.83
|
- uses: dtolnay/rust-toolchain@1.83
|
||||||
with:
|
with:
|
||||||
@@ -176,8 +202,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.tag }}
|
ref: main
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download all build artifacts
|
- name: Download all build artifacts
|
||||||
# download-artifact@v3 nests each artifact under its own dir
|
# download-artifact@v3 nests each artifact under its own dir
|
||||||
|
|||||||
Generated
+3
-3
@@ -376,11 +376,11 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nuwiki-core"
|
name = "nuwiki-core"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nuwiki-ls"
|
name = "nuwiki-ls"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nuwiki-lsp",
|
"nuwiki-lsp",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -388,7 +388,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nuwiki-lsp"
|
name = "nuwiki-lsp"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"dashmap 6.1.0",
|
"dashmap 6.1.0",
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ resolver = "2"
|
|||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.83"
|
rust-version = "1.83"
|
||||||
authors = ["Gabriel Fróes Franco <gffranco@gmail.com>"]
|
authors = ["Gabriel Fróes Franco <gffranco@gmail.com>"]
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
# nuwiki-rs
|
# nuwiki-rs
|
||||||
|
|
||||||
Rust crates powering [nuwiki](https://code.gfran.co/gffranco/nuwiki): the vimwiki-compatible language server and editor plugin for Vim/Neovim.
|
The Rust language server (`nuwiki-ls`) behind [nuwiki](https://code.gfran.co/gffranco/nuwiki), the vimwiki-compatible Vim/Neovim plugin.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **This repo is the server only.** If you just want to *use* nuwiki in
|
||||||
|
> Vim/Neovim, install the plugin from
|
||||||
|
> **[gffranco/nuwiki](https://code.gfran.co/gffranco/nuwiki)** — it downloads
|
||||||
|
> a prebuilt `nuwiki-ls` binary from this repo's releases automatically, so
|
||||||
|
> you don't need this repo or a Rust toolchain. Work here only to develop the
|
||||||
|
> parser / LSP / HTML renderer.
|
||||||
|
|
||||||
## Crates
|
## Crates
|
||||||
|
|
||||||
|
|||||||
@@ -526,10 +526,9 @@ impl<'src> LexState<'src> {
|
|||||||
if level == 0 {
|
if level == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Must be followed by at least one space.
|
// vimwiki accepts both spaced (`== H ==`) and spaceless (`==H==`)
|
||||||
if leading_ws + level >= bytes.len() || bytes[leading_ws + level] != b' ' {
|
// headings, so we don't require a space after the opening `=`s. The
|
||||||
return false;
|
// title is still trimmed of one optional space per side below.
|
||||||
}
|
|
||||||
|
|
||||||
// Trailing `=`s of the same level.
|
// Trailing `=`s of the same level.
|
||||||
let trimmed_end = line.trim_end_matches([' ', '\t']);
|
let trimmed_end = line.trim_end_matches([' ', '\t']);
|
||||||
|
|||||||
@@ -77,6 +77,27 @@ fn centered_heading_gets_centered_class() {
|
|||||||
assert!(out.contains("<h1 class=\"centered\" id=\"T\">T</h1>"));
|
assert!(out.contains("<h1 class=\"centered\" id=\"T\">T</h1>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spaceless_heading_renders_as_heading() {
|
||||||
|
// `==Heading==` (no surrounding spaces) is a heading, like vimwiki — not a
|
||||||
|
// literal `<p>==Heading==</p>`.
|
||||||
|
let out = render("==Plain==\n");
|
||||||
|
assert!(
|
||||||
|
out.contains(
|
||||||
|
"<div id=\"Plain\"><h2 id=\"Plain\" class=\"header\">\
|
||||||
|
<a href=\"#Plain\">Plain</a></h2></div>"
|
||||||
|
),
|
||||||
|
"got: {out}"
|
||||||
|
);
|
||||||
|
// The spaceless + keyword combo from the report: a header, not a badge.
|
||||||
|
let out2 = render("==TODO==\n");
|
||||||
|
assert!(
|
||||||
|
out2.contains("<h2 id=\"TODO\" class=\"header\">"),
|
||||||
|
"got: {out2}"
|
||||||
|
);
|
||||||
|
assert!(!out2.contains("<p>"), "not a paragraph: {out2}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heading_keyword_renders_as_plain_text_not_badge() {
|
fn heading_keyword_renders_as_plain_text_not_badge() {
|
||||||
// `== TODO ==` is a section title, not an inline keyword: it must render as
|
// `== TODO ==` is a section title, not an inline keyword: it must render as
|
||||||
|
|||||||
@@ -85,6 +85,32 @@ fn centered_heading_is_marked_centered() {
|
|||||||
assert!(matches!(lexed[2], HeadingClose));
|
assert!(matches!(lexed[2], HeadingClose));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spaceless_heading_is_a_heading() {
|
||||||
|
// vimwiki accepts `==Heading==` with no spaces around the text.
|
||||||
|
let lexed = lex("==Plain==\n");
|
||||||
|
assert_eq!(
|
||||||
|
lexed,
|
||||||
|
vec![
|
||||||
|
HeadingOpen {
|
||||||
|
level: 2,
|
||||||
|
centered: false
|
||||||
|
},
|
||||||
|
text("Plain"),
|
||||||
|
HeadingClose,
|
||||||
|
Newline,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn marker_only_or_unbalanced_lines_are_not_headings() {
|
||||||
|
// No title between markers, and trailing `=` not at line end → plain text.
|
||||||
|
assert!(!matches!(lex("======\n")[0], HeadingOpen { .. }));
|
||||||
|
assert!(!matches!(lex("==> arrow\n")[0], HeadingOpen { .. }));
|
||||||
|
assert!(!matches!(lex("==a==b\n")[0], HeadingOpen { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heading_with_inline_bold() {
|
fn heading_with_inline_bold() {
|
||||||
let lexed = lex("== Hello *world* ==\n");
|
let lexed = lex("== Hello *world* ==\n");
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ name = "nuwiki-ls"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nuwiki-lsp = { path = "../nuwiki-lsp", version = "0.4.1" }
|
nuwiki-lsp = { path = "../nuwiki-lsp", version = "0.5.0" }
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std"] }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ homepage.workspace = true
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nuwiki-core = { path = "../nuwiki-core", version = "0.4.1" }
|
nuwiki-core = { path = "../nuwiki-core", version = "0.5.0" }
|
||||||
tower-lsp = "0.20"
|
tower-lsp = "0.20"
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std", "sync", "fs"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std", "sync", "fs"] }
|
||||||
dashmap = "6"
|
dashmap = "6"
|
||||||
|
|||||||
Reference in New Issue
Block a user