From 4cc1130a175121fda2136ede9bccefd7e228388b Mon Sep 17 00:00:00 2001 From: PoiScript Date: Wed, 20 Dec 2023 21:56:10 +0800 Subject: [PATCH] chore: add orgize-{cli,common,lsp} package --- .github/workflows/ci.yml | 4 +- .gitignore | 2 +- Cargo.toml | 54 +- orgize-cli/Cargo.toml | 25 + orgize-cli/src/detangle.rs | 80 + orgize-cli/src/diff.rs | 46 + orgize-cli/src/execute_src_block.rs | 86 + orgize-cli/src/main.rs | 54 + orgize-cli/src/tangle.rs | 111 + orgize-common/Cargo.toml | 16 + orgize-common/src/detangle.rs | 110 + orgize-common/src/execute_src_block.rs | 142 ++ orgize-common/src/formatting.rs | 133 ++ orgize-common/src/header_argument.rs | 111 + orgize-common/src/lib.rs | 13 + orgize-common/src/tangle.rs | 130 ++ orgize-common/src/utils.rs | 35 + orgize-lsp/Cargo.toml | 25 + orgize-lsp/README.md | 50 + orgize-lsp/editors/vscode/.gitignore | 3 + orgize-lsp/editors/vscode/.vscodeignore | 6 + orgize-lsp/editors/vscode/LICENSE | 1 + orgize-lsp/editors/vscode/build.mjs | 13 + .../editors/vscode/org.configuration.json | 29 + orgize-lsp/editors/vscode/package.json | 111 + orgize-lsp/editors/vscode/pnpm-lock.yaml | 1268 ++++++++++ orgize-lsp/editors/vscode/src/main.ts | 53 + orgize-lsp/editors/vscode/src/preview-html.ts | 234 ++ orgize-lsp/editors/vscode/src/syntax-tree.ts | 77 + .../vscode/syntaxes/org.tmLanguage.json | 2060 +++++++++++++++++ orgize-lsp/editors/vscode/tsconfig.json | 12 + orgize-lsp/justfile | 8 + orgize-lsp/src/code_lens.rs | 104 + orgize-lsp/src/commands/headline_toc.rs | 101 + orgize-lsp/src/commands/mod.rs | 116 + orgize-lsp/src/commands/src_block_detangle.rs | 53 + orgize-lsp/src/commands/src_block_execute.rs | 51 + orgize-lsp/src/commands/src_block_tangle.rs | 49 + orgize-lsp/src/document_link.rs | 129 ++ orgize-lsp/src/document_link_resolve.rs | 81 + orgize-lsp/src/document_symbol.rs | 66 + orgize-lsp/src/folding_range.rs | 94 + orgize-lsp/src/formatting.rs | 13 + orgize-lsp/src/main.rs | 285 +++ orgize-lsp/src/org_document.rs | 128 + orgize-lsp/src/semantic_token.rs | 123 + {wasm => orgize-wasm}/.gitignore | 0 orgize-wasm/Cargo.toml | 15 + {wasm => orgize-wasm}/README.md | 0 {wasm => orgize-wasm}/build.rs | 0 {wasm => orgize-wasm}/index.html | 0 {wasm => orgize-wasm}/package.json | 0 {wasm => orgize-wasm}/src/lib.rs | 0 orgize/Cargo.toml | 39 + orgize/README.md | 71 + orgize/benches/.gitignore | 1 + {benches => orgize/benches}/parse.rs | 0 {examples => orgize/examples}/html-slugify.rs | 0 {examples => orgize/examples}/parse.rs | 0 {fuzz => orgize/fuzz}/.gitignore | 0 {fuzz => orgize/fuzz}/Cargo.toml | 0 .../fuzz}/fuzz_targets/fuzz_target_1.rs | 0 {src => orgize/src}/ast/affiliated_keyword.rs | 0 {src => orgize/src}/ast/block.rs | 0 {src => orgize/src}/ast/clock.rs | 0 {src => orgize/src}/ast/comment.rs | 0 {src => orgize/src}/ast/drawer.rs | 0 {src => orgize/src}/ast/entity.rs | 0 {src => orgize/src}/ast/fixed_width.rs | 0 {src => orgize/src}/ast/generate.js | 0 {src => orgize/src}/ast/generated.rs | 0 {src => orgize/src}/ast/headline.rs | 0 {src => orgize/src}/ast/inline_call.rs | 0 {src => orgize/src}/ast/inline_src.rs | 0 {src => orgize/src}/ast/keyword.rs | 0 {src => orgize/src}/ast/link.rs | 0 {src => orgize/src}/ast/list.rs | 0 {src => orgize/src}/ast/macros.rs | 0 {src => orgize/src}/ast/mod.rs | 0 {src => orgize/src}/ast/planning.rs | 0 {src => orgize/src}/ast/snippet.rs | 0 {src => orgize/src}/ast/table.rs | 0 {src => orgize/src}/ast/timestamp.rs | 0 {src => orgize/src}/config.rs | 0 {src => orgize/src}/entities.rs | 0 {src => orgize/src}/export/event.rs | 0 {src => orgize/src}/export/html.rs | 0 {src => orgize/src}/export/mod.rs | 0 {src => orgize/src}/export/traverse.rs | 0 {src => orgize/src}/lib.rs | 0 {src => orgize/src}/org.rs | 0 {src => orgize/src}/syntax/block.rs | 0 {src => orgize/src}/syntax/clock.rs | 0 {src => orgize/src}/syntax/combinator.rs | 0 {src => orgize/src}/syntax/comment.rs | 0 {src => orgize/src}/syntax/cookie.rs | 0 {src => orgize/src}/syntax/document.rs | 0 {src => orgize/src}/syntax/drawer.rs | 0 {src => orgize/src}/syntax/dyn_block.rs | 0 {src => orgize/src}/syntax/element.rs | 0 {src => orgize/src}/syntax/emphasis.rs | 0 {src => orgize/src}/syntax/entity.rs | 0 {src => orgize/src}/syntax/fixed_width.rs | 0 {src => orgize/src}/syntax/fn_def.rs | 0 {src => orgize/src}/syntax/fn_ref.rs | 0 {src => orgize/src}/syntax/headline.rs | 0 {src => orgize/src}/syntax/inline_call.rs | 0 {src => orgize/src}/syntax/inline_src.rs | 0 {src => orgize/src}/syntax/input.rs | 0 {src => orgize/src}/syntax/keyword.rs | 0 .../src}/syntax/latex_environment.rs | 0 {src => orgize/src}/syntax/latex_fragment.rs | 0 {src => orgize/src}/syntax/line_break.rs | 0 {src => orgize/src}/syntax/link.rs | 0 {src => orgize/src}/syntax/list.rs | 0 {src => orgize/src}/syntax/macros.rs | 0 {src => orgize/src}/syntax/mod.rs | 0 {src => orgize/src}/syntax/object.rs | 0 {src => orgize/src}/syntax/paragraph.rs | 0 {src => orgize/src}/syntax/planning.rs | 0 {src => orgize/src}/syntax/radio_target.rs | 0 {src => orgize/src}/syntax/rule.rs | 0 {src => orgize/src}/syntax/snippet.rs | 0 .../src}/syntax/subscript_superscript.rs | 0 {src => orgize/src}/syntax/table.rs | 0 {src => orgize/src}/syntax/target.rs | 0 {src => orgize/src}/syntax/timestamp.rs | 0 {src => orgize/src}/tests.rs | 0 {tests => orgize/tests}/html.rs | 0 {tests => orgize/tests}/parse.rs | 0 wasm/Cargo.toml | 12 - 131 files changed, 6577 insertions(+), 56 deletions(-) create mode 100644 orgize-cli/Cargo.toml create mode 100644 orgize-cli/src/detangle.rs create mode 100644 orgize-cli/src/diff.rs create mode 100644 orgize-cli/src/execute_src_block.rs create mode 100644 orgize-cli/src/main.rs create mode 100644 orgize-cli/src/tangle.rs create mode 100644 orgize-common/Cargo.toml create mode 100644 orgize-common/src/detangle.rs create mode 100644 orgize-common/src/execute_src_block.rs create mode 100644 orgize-common/src/formatting.rs create mode 100644 orgize-common/src/header_argument.rs create mode 100644 orgize-common/src/lib.rs create mode 100644 orgize-common/src/tangle.rs create mode 100644 orgize-common/src/utils.rs create mode 100644 orgize-lsp/Cargo.toml create mode 100644 orgize-lsp/README.md create mode 100644 orgize-lsp/editors/vscode/.gitignore create mode 100644 orgize-lsp/editors/vscode/.vscodeignore create mode 120000 orgize-lsp/editors/vscode/LICENSE create mode 100644 orgize-lsp/editors/vscode/build.mjs create mode 100644 orgize-lsp/editors/vscode/org.configuration.json create mode 100644 orgize-lsp/editors/vscode/package.json create mode 100644 orgize-lsp/editors/vscode/pnpm-lock.yaml create mode 100644 orgize-lsp/editors/vscode/src/main.ts create mode 100644 orgize-lsp/editors/vscode/src/preview-html.ts create mode 100644 orgize-lsp/editors/vscode/src/syntax-tree.ts create mode 100644 orgize-lsp/editors/vscode/syntaxes/org.tmLanguage.json create mode 100644 orgize-lsp/editors/vscode/tsconfig.json create mode 100644 orgize-lsp/justfile create mode 100644 orgize-lsp/src/code_lens.rs create mode 100644 orgize-lsp/src/commands/headline_toc.rs create mode 100644 orgize-lsp/src/commands/mod.rs create mode 100644 orgize-lsp/src/commands/src_block_detangle.rs create mode 100644 orgize-lsp/src/commands/src_block_execute.rs create mode 100644 orgize-lsp/src/commands/src_block_tangle.rs create mode 100644 orgize-lsp/src/document_link.rs create mode 100644 orgize-lsp/src/document_link_resolve.rs create mode 100644 orgize-lsp/src/document_symbol.rs create mode 100644 orgize-lsp/src/folding_range.rs create mode 100644 orgize-lsp/src/formatting.rs create mode 100644 orgize-lsp/src/main.rs create mode 100644 orgize-lsp/src/org_document.rs create mode 100644 orgize-lsp/src/semantic_token.rs rename {wasm => orgize-wasm}/.gitignore (100%) create mode 100644 orgize-wasm/Cargo.toml rename {wasm => orgize-wasm}/README.md (100%) rename {wasm => orgize-wasm}/build.rs (100%) rename {wasm => orgize-wasm}/index.html (100%) rename {wasm => orgize-wasm}/package.json (100%) rename {wasm => orgize-wasm}/src/lib.rs (100%) create mode 100644 orgize/Cargo.toml create mode 100644 orgize/README.md create mode 100644 orgize/benches/.gitignore rename {benches => orgize/benches}/parse.rs (100%) rename {examples => orgize/examples}/html-slugify.rs (100%) rename {examples => orgize/examples}/parse.rs (100%) rename {fuzz => orgize/fuzz}/.gitignore (100%) rename {fuzz => orgize/fuzz}/Cargo.toml (100%) rename {fuzz => orgize/fuzz}/fuzz_targets/fuzz_target_1.rs (100%) rename {src => orgize/src}/ast/affiliated_keyword.rs (100%) rename {src => orgize/src}/ast/block.rs (100%) rename {src => orgize/src}/ast/clock.rs (100%) rename {src => orgize/src}/ast/comment.rs (100%) rename {src => orgize/src}/ast/drawer.rs (100%) rename {src => orgize/src}/ast/entity.rs (100%) rename {src => orgize/src}/ast/fixed_width.rs (100%) rename {src => orgize/src}/ast/generate.js (100%) rename {src => orgize/src}/ast/generated.rs (100%) rename {src => orgize/src}/ast/headline.rs (100%) rename {src => orgize/src}/ast/inline_call.rs (100%) rename {src => orgize/src}/ast/inline_src.rs (100%) rename {src => orgize/src}/ast/keyword.rs (100%) rename {src => orgize/src}/ast/link.rs (100%) rename {src => orgize/src}/ast/list.rs (100%) rename {src => orgize/src}/ast/macros.rs (100%) rename {src => orgize/src}/ast/mod.rs (100%) rename {src => orgize/src}/ast/planning.rs (100%) rename {src => orgize/src}/ast/snippet.rs (100%) rename {src => orgize/src}/ast/table.rs (100%) rename {src => orgize/src}/ast/timestamp.rs (100%) rename {src => orgize/src}/config.rs (100%) rename {src => orgize/src}/entities.rs (100%) rename {src => orgize/src}/export/event.rs (100%) rename {src => orgize/src}/export/html.rs (100%) rename {src => orgize/src}/export/mod.rs (100%) rename {src => orgize/src}/export/traverse.rs (100%) rename {src => orgize/src}/lib.rs (100%) rename {src => orgize/src}/org.rs (100%) rename {src => orgize/src}/syntax/block.rs (100%) rename {src => orgize/src}/syntax/clock.rs (100%) rename {src => orgize/src}/syntax/combinator.rs (100%) rename {src => orgize/src}/syntax/comment.rs (100%) rename {src => orgize/src}/syntax/cookie.rs (100%) rename {src => orgize/src}/syntax/document.rs (100%) rename {src => orgize/src}/syntax/drawer.rs (100%) rename {src => orgize/src}/syntax/dyn_block.rs (100%) rename {src => orgize/src}/syntax/element.rs (100%) rename {src => orgize/src}/syntax/emphasis.rs (100%) rename {src => orgize/src}/syntax/entity.rs (100%) rename {src => orgize/src}/syntax/fixed_width.rs (100%) rename {src => orgize/src}/syntax/fn_def.rs (100%) rename {src => orgize/src}/syntax/fn_ref.rs (100%) rename {src => orgize/src}/syntax/headline.rs (100%) rename {src => orgize/src}/syntax/inline_call.rs (100%) rename {src => orgize/src}/syntax/inline_src.rs (100%) rename {src => orgize/src}/syntax/input.rs (100%) rename {src => orgize/src}/syntax/keyword.rs (100%) rename {src => orgize/src}/syntax/latex_environment.rs (100%) rename {src => orgize/src}/syntax/latex_fragment.rs (100%) rename {src => orgize/src}/syntax/line_break.rs (100%) rename {src => orgize/src}/syntax/link.rs (100%) rename {src => orgize/src}/syntax/list.rs (100%) rename {src => orgize/src}/syntax/macros.rs (100%) rename {src => orgize/src}/syntax/mod.rs (100%) rename {src => orgize/src}/syntax/object.rs (100%) rename {src => orgize/src}/syntax/paragraph.rs (100%) rename {src => orgize/src}/syntax/planning.rs (100%) rename {src => orgize/src}/syntax/radio_target.rs (100%) rename {src => orgize/src}/syntax/rule.rs (100%) rename {src => orgize/src}/syntax/snippet.rs (100%) rename {src => orgize/src}/syntax/subscript_superscript.rs (100%) rename {src => orgize/src}/syntax/table.rs (100%) rename {src => orgize/src}/syntax/target.rs (100%) rename {src => orgize/src}/syntax/timestamp.rs (100%) rename {src => orgize/src}/tests.rs (100%) rename {tests => orgize/tests}/html.rs (100%) rename {tests => orgize/tests}/parse.rs (100%) delete mode 100644 wasm/Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5746014..d14ea38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,12 +45,12 @@ jobs: run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - name: Build - run: wasm-pack build -t web -d ./dist --out-name orgize ./wasm/ + run: wasm-pack build -t web -d ./dist --out-name orgize ./orgize-wasm/ - name: Upload artifact uses: actions/upload-pages-artifact@v2 with: - path: "./wasm" + path: "./orgize-wasm" - name: Deploy to GitHub Pages id: deployment diff --git a/.gitignore b/.gitignore index 4ca2515..081fe7a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ **/*.rs.bk Cargo.lock -benches/*.org +.vscode .gdb_history perf.data* diff --git a/Cargo.toml b/Cargo.toml index 2f5ab80..c5f3ea0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,49 +1,21 @@ -[package] -name = "orgize" +[workspace] +resolver = "2" +members = [ + ".", + "./orgize", + "./orgize-cli", + "./orgize-common", + "./orgize-lsp", + "./orgize-wasm", +] + +[workspace.package] version = "0.10.0-alpha.6" authors = ["PoiScript "] -description = "A Rust library for parsing org-mode files." repository = "https://github.com/PoiScript/orgize" -readme = "README.md" -edition = "2018" +edition = "2021" license = "MIT" -keywords = ["orgmode", "org-mode", "emacs", "parser"] -exclude = ["/wasm", "/.github"] - -[package.metadata.docs.rs] -all-features = true - -[features] -default = [] -indexmap = ["dep:indexmap"] -chrono = ["dep:chrono"] - -[workspace] -members = [".", "./wasm"] - -[dependencies] -bytecount = "0.6" -chrono = { version = "0.4", optional = true } -indexmap = { version = "2.1", optional = true } -jetscii = "0.5" -memchr = "2.5" -nom = { version = "7.1", default-features = false, features = ["std"] } -rowan = "0.15" -tracing = "0.1" - -[dev-dependencies] -criterion = "0.5" -insta = "1.29" -slugify = "0.1" -tracing-subscriber = { version = "0.3", features = ["fmt"] } [profile.dev.package] insta.opt-level = 3 similar.opt-level = 3 - -[profile.bench] -debug = true - -[[bench]] -name = "parse" -harness = false diff --git a/orgize-cli/Cargo.toml b/orgize-cli/Cargo.toml new file mode 100644 index 0000000..1449e26 --- /dev/null +++ b/orgize-cli/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "orgize-cli" +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +license.workspace = true +description = "CLI tools for org-mode file, powered by orgize." + +[dependencies] +anyhow = "1.0.75" +clap = { version = "4.4.11", features = ["derive"] } +clap-verbosity-flag = "2.1.0" +jetscii = "0.5.3" +nom = "7.1.3" +orgize = { path = "../orgize" } +orgize-common = { path = "../orgize-common" } +resolve-path = "0.1.0" +tempfile = "3.8.1" +tracing = "0.1.40" +tracing-subscriber = { version = "0.3", features = ["fmt"] } + +[[bin]] +name = "orgize" +path = "src/main.rs" diff --git a/orgize-cli/src/detangle.rs b/orgize-cli/src/detangle.rs new file mode 100644 index 0000000..19a4950 --- /dev/null +++ b/orgize-cli/src/detangle.rs @@ -0,0 +1,80 @@ +use clap::Args; +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + Org, +}; +use std::path::PathBuf; + +use crate::diff; + +#[derive(Debug, Args)] +pub struct Command { + path: Vec, + + #[arg(short, long)] + dry_run: bool, +} + +impl Command { + pub fn run(self) -> anyhow::Result<()> { + for path in self.path { + if !path.exists() { + tracing::error!("{:?} is not existed", path); + } + + let orgi = std::fs::read_to_string(&path)?; + + let mut t = DetangleTraverser { + results: Vec::new(), + org_file_path: path, + }; + + let org = Org::parse(&orgi); + org.traverse(&mut t); + + if self.dry_run { + diff::print(&orgi, t.results); + } else { + diff::write_to_file(&orgi, t.results, t.org_file_path)?; + } + } + + Ok(()) + } +} + +struct DetangleTraverser { + results: Vec<(usize, usize, String)>, + org_file_path: PathBuf, +} + +impl Traverser for DetangleTraverser { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::SourceBlock(block)) => { + if let Ok(Some((start, end, content))) = + orgize_common::detangle(block, &self.org_file_path) + { + self.results.push((start, end, content)); + } + + ctx.skip(); + } + + // skip some containers for performance + Event::Enter(Container::List(_)) + | Event::Enter(Container::OrgTable(_)) + | Event::Enter(Container::SpecialBlock(_)) + | Event::Enter(Container::QuoteBlock(_)) + | Event::Enter(Container::CenterBlock(_)) + | Event::Enter(Container::VerseBlock(_)) + | Event::Enter(Container::CommentBlock(_)) + | Event::Enter(Container::ExampleBlock(_)) + | Event::Enter(Container::ExportBlock(_)) => { + ctx.skip(); + } + + _ => {} + } + } +} diff --git a/orgize-cli/src/diff.rs b/orgize-cli/src/diff.rs new file mode 100644 index 0000000..35d3f14 --- /dev/null +++ b/orgize-cli/src/diff.rs @@ -0,0 +1,46 @@ +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; + +use clap::builder::styling::{AnsiColor, Color, Style}; + +pub fn print(orgi: &str, mut patches: Vec<(usize, usize, String)>) { + patches.sort_by(|a, b| a.0.cmp(&b.0)); + + let mut off = 0; + + for (start, end, content) in patches { + print!("{}", &orgi[off..(start)]); + + if orgi[start..end] != content { + let style = Style::new().fg_color(Color::Ansi(AnsiColor::Cyan).into()); + print!("{}{}{}", style.render(), &content, style.render_reset()); + } else { + print!("{}", &content); + } + + off = end; + } + + print!("{}", &orgi[off..]); +} + +pub fn write_to_file( + orgi: &str, + mut patches: Vec<(usize, usize, String)>, + path: PathBuf, +) -> anyhow::Result<()> { + patches.sort_by(|a, b| a.0.cmp(&b.0)); + + let file = &mut OpenOptions::new().write(true).open(path)?; + let mut off = 0; + + for (start, end, content) in patches { + write!(file, "{}{}", &orgi[off..start], &content)?; + off = end; + } + + write!(file, "{}", &orgi[off..])?; + + Ok(()) +} diff --git a/orgize-cli/src/execute_src_block.rs b/orgize-cli/src/execute_src_block.rs new file mode 100644 index 0000000..19500d4 --- /dev/null +++ b/orgize-cli/src/execute_src_block.rs @@ -0,0 +1,86 @@ +use clap::Args; +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + Org, +}; +use std::path::PathBuf; + +use crate::diff; + +#[derive(Debug, Args)] +pub struct Command { + path: Vec, + + #[arg(short, long)] + dry_run: bool, +} + +impl Command { + pub fn run(self) -> anyhow::Result<()> { + let dir = tempfile::tempdir()?; + + tracing::debug!("Create tempdir {:?}", dir.path().to_string_lossy()); + + for path in self.path { + if !path.exists() { + tracing::error!("{:?} is not existed", path); + } + + let mut t = ExecuteTraverser { + results: Vec::new(), + dir: &dir, + }; + + let orgi = std::fs::read_to_string(&path)?; + let org = Org::parse(&orgi); + org.traverse(&mut t); + + t.results.sort_by(|a, b| a.0.cmp(&b.0)); + + if self.dry_run { + diff::print(&orgi, t.results); + } else { + diff::write_to_file(&orgi, t.results, path)?; + } + } + + Ok(()) + } +} + +struct ExecuteTraverser<'a> { + results: Vec<(usize, usize, String)>, + + dir: &'a tempfile::TempDir, +} + +impl<'a> Traverser for ExecuteTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::SourceBlock(block)) => { + if let Ok(Some((start, end, content))) = + orgize_common::execute(block, self.dir.path()) + { + self.results.push((start, end, content)); + } + + ctx.skip(); + } + + // skip some containers for performance + Event::Enter(Container::List(_)) + | Event::Enter(Container::OrgTable(_)) + | Event::Enter(Container::SpecialBlock(_)) + | Event::Enter(Container::QuoteBlock(_)) + | Event::Enter(Container::CenterBlock(_)) + | Event::Enter(Container::VerseBlock(_)) + | Event::Enter(Container::CommentBlock(_)) + | Event::Enter(Container::ExampleBlock(_)) + | Event::Enter(Container::ExportBlock(_)) => { + ctx.skip(); + } + + _ => {} + } + } +} diff --git a/orgize-cli/src/main.rs b/orgize-cli/src/main.rs new file mode 100644 index 0000000..01dfb69 --- /dev/null +++ b/orgize-cli/src/main.rs @@ -0,0 +1,54 @@ +mod detangle; +mod diff; +mod execute_src_block; +mod tangle; + +use clap::{Parser, Subcommand}; +use clap_verbosity_flag::{InfoLevel, LevelFilter as CLevelFilter, Verbosity}; +use tracing::level_filters::LevelFilter; + +#[derive(Debug, Parser)] +#[clap(name = "orgize-tools", version)] +pub struct App { + #[clap(subcommand)] + command: Command, + + #[command(flatten)] + verbose: Verbosity, +} + +#[derive(Debug, Subcommand)] +enum Command { + #[clap(name = "tangle")] + Tangle(tangle::Command), + + #[clap(name = "detangle")] + Detangle(detangle::Command), + + #[clap(name = "execute-src-block")] + ExecuteSrcBlock(execute_src_block::Command), +} + +fn main() -> anyhow::Result<()> { + let parsed = App::parse(); + + tracing_subscriber::fmt() + .with_max_level(match parsed.verbose.log_level_filter() { + CLevelFilter::Off => LevelFilter::OFF, + CLevelFilter::Error => LevelFilter::ERROR, + CLevelFilter::Warn => LevelFilter::WARN, + CLevelFilter::Info => LevelFilter::INFO, + CLevelFilter::Debug => LevelFilter::DEBUG, + CLevelFilter::Trace => LevelFilter::TRACE, + }) + .without_time() + .with_file(false) + .with_line_number(false) + .init(); + + match parsed.command { + Command::Tangle(cmd) => cmd.run(), + Command::Detangle(cmd) => cmd.run(), + Command::ExecuteSrcBlock(cmd) => cmd.run(), + } +} diff --git a/orgize-cli/src/tangle.rs b/orgize-cli/src/tangle.rs new file mode 100644 index 0000000..67e68df --- /dev/null +++ b/orgize-cli/src/tangle.rs @@ -0,0 +1,111 @@ +use clap::{ + builder::styling::{AnsiColor, Color, Style}, + Args, +}; +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + Org, +}; +use std::fs; +use std::{collections::HashMap, path::PathBuf}; + +#[derive(Debug, Args)] +pub struct Command { + path: Vec, + + #[arg(short, long)] + dry_run: bool, +} + +impl Command { + pub fn run(self) -> anyhow::Result<()> { + let mut t = TangleTraverser::default(); + + for path in self.path { + if !path.exists() { + tracing::error!("{:?} is not existed", path); + } + + let string = std::fs::read_to_string(&path)?; + let org = Org::parse(string); + t.org_file_path = path; + t.count = 0; + org.traverse(&mut t); + tracing::info!( + "Found {} code block from {}", + t.count, + t.org_file_path.to_string_lossy() + ); + } + + if self.dry_run { + for (path, (permission, content, mkdir)) in t.results { + let style = Style::new() + .fg_color(Color::Ansi(AnsiColor::BrightYellow).into()) + .underline() + .bold(); + print!( + "{}{}{}", + style.render(), + path.to_string_lossy(), + style.render_reset(), + ); + if let Some(permission) = permission { + print!(" (permission: {:o})", permission); + } + if mkdir { + print!(" (mkdir: yes)"); + } + println!("\n{}", content); + } + } else { + for (path, (_, contents, _)) in t.results { + fs::write(&path, contents)?; + tracing::info!("Wrote to {}", path.to_string_lossy()); + } + } + + Ok(()) + } +} + +#[derive(Default)] +struct TangleTraverser { + results: HashMap, String, bool)>, + count: usize, + org_file_path: PathBuf, +} + +impl Traverser for TangleTraverser { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::SourceBlock(block)) => { + if let Ok(Some((path, permission, content, mkdir))) = + orgize_common::tangle(block, &self.org_file_path) + { + let value = self.results.entry(path).or_default(); + value.0 = permission; + value.1.push_str(&content); + value.2 = mkdir; + } + + ctx.skip(); + } + + // skip some containers for performance + Event::Enter(Container::List(_)) + | Event::Enter(Container::OrgTable(_)) + | Event::Enter(Container::SpecialBlock(_)) + | Event::Enter(Container::QuoteBlock(_)) + | Event::Enter(Container::CenterBlock(_)) + | Event::Enter(Container::VerseBlock(_)) + | Event::Enter(Container::CommentBlock(_)) + | Event::Enter(Container::ExampleBlock(_)) + | Event::Enter(Container::ExportBlock(_)) => { + ctx.skip(); + } + + _ => {} + } + } +} diff --git a/orgize-common/Cargo.toml b/orgize-common/Cargo.toml new file mode 100644 index 0000000..f7e3593 --- /dev/null +++ b/orgize-common/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "orgize-common" +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +license.workspace = true +description = "Shared code between orgize-lsp and orgize-lsp." + +[dependencies] +anyhow = "1.0.75" +jetscii = "0.5.3" +nom = "7.1.3" +orgize = { path = "../orgize" } +resolve-path = "0.1.0" +tracing = "0.1.40" diff --git a/orgize-common/src/detangle.rs b/orgize-common/src/detangle.rs new file mode 100644 index 0000000..9966a24 --- /dev/null +++ b/orgize-common/src/detangle.rs @@ -0,0 +1,110 @@ +use orgize::{ + ast::{Headline, SourceBlock}, + rowan::ast::AstNode, + SyntaxKind, +}; +use resolve_path::PathResolveExt; +use std::{fs, path::PathBuf}; + +use crate::{ + header_argument::{header_argument, property_drawer, property_keyword}, + utils::language_comments, +}; + +pub fn detangle( + block: SourceBlock, + file_path: &PathBuf, +) -> anyhow::Result> { + let arg1 = block.parameters().unwrap_or_default(); + let arg2 = property_drawer(block.syntax()).unwrap_or_default(); + let arg3 = property_keyword(block.syntax()).unwrap_or_default(); + let language = block.language().unwrap_or_default(); + + let tangle = header_argument(&arg1, &arg2, &arg3, ":tangle", "no"); + + if tangle == "no" { + return Ok(None); + } + + let comments = header_argument(&arg1, &arg2, &arg3, ":comments", "no"); + + let parent = block + .syntax() + .ancestors() + .find(|n| n.kind() == SyntaxKind::HEADLINE || n.kind() == SyntaxKind::DOCUMENT); + + let nth = parent + .as_ref() + .and_then(|n| n.children().position(|c| &c == block.syntax())) + .unwrap_or(1); + + let headline_title = parent.and_then(Headline::cast).map(|headline| { + headline + .title() + .fold(String::new(), |a, n| a + &n.to_string()) + }); + + let dest_path = tangle.try_resolve_in(file_path)?.to_path_buf(); + + let content = fs::read_to_string(dest_path)?; + + let Some(begin) = block + .syntax() + .children() + .find(|n| n.kind() == SyntaxKind::BLOCK_CONTENT) + else { + return Ok(None); + }; + + let text_range = begin.text_range(); + + if comments == "yes" || comments == "link" || comments == "noweb" || comments == "both" { + let begin_comments = format!( + "[[file:{path}::*{title}][{title}:{nth}]]", + title = headline_title.as_deref().unwrap_or("No heading"), + path = file_path.to_string_lossy(), + ); + let end_comments = format!( + "{title}:{nth} ends here", + title = headline_title.as_deref().unwrap_or("No heading"), + ); + + let mut block_content = String::new(); + + for line in content + .lines() + .skip_while(|line| trim_comments(line, &language).unwrap_or_default() != begin_comments) + .skip(1) + { + if trim_comments(line, &language).unwrap_or_default() == end_comments { + return Ok(Some(( + text_range.start().into(), + text_range.end().into(), + block_content, + ))); + } else { + block_content += line; + block_content += "\n"; + } + } + + tracing::warn!( + "Cannot found contents wrapped by comments for code block {path}*{title}:{nth}.", + title = headline_title.as_deref().unwrap_or("No heading"), + path = file_path.to_string_lossy(), + ); + + return Ok(None); + } + + Ok(Some(( + text_range.start().into(), + text_range.end().into(), + content, + ))) +} + +fn trim_comments<'a>(input: &'a str, language: &str) -> Option<&'a str> { + let (begin, end) = language_comments(language)?; + Some(input.trim().strip_prefix(begin)?.strip_suffix(end)?.trim()) +} diff --git a/orgize-common/src/execute_src_block.rs b/orgize-common/src/execute_src_block.rs new file mode 100644 index 0000000..2d0426e --- /dev/null +++ b/orgize-common/src/execute_src_block.rs @@ -0,0 +1,142 @@ +use orgize::{ + ast::{AffiliatedKeyword, SourceBlock}, + rowan::ast::AstNode, + SyntaxKind, +}; +use std::{fs::File, io::Write, iter::once, path::Path, process}; + +use crate::{ + header_argument::{header_argument, property_drawer, property_keyword}, + utils::language_execute_command, +}; + +#[derive(Debug)] +enum Format { + Code, + List, + Verbatim, + Html, + Latex, + Raw, +} + +pub fn execute(block: SourceBlock, path: &Path) -> anyhow::Result> { + let arg1 = block.parameters().unwrap_or_default(); + let arg2 = property_drawer(block.syntax()).unwrap_or_default(); + let arg3 = property_keyword(block.syntax()).unwrap_or_default(); + let language = block.language().unwrap_or_default(); + let results = header_argument(&arg1, &arg2, &arg3, ":results", "no"); + + if results == "no" { + return Ok(None); + } + + let Some(command) = language_execute_command(&language) else { + anyhow::bail!("{language:?} is not supported.") + }; + + let mut segs = results.split(&[' ', '\t']).filter(|x| !x.is_empty()); + + let format = match (segs.next(), segs.next()) { + (Some("output"), Some("code")) | (Some("code"), None) => Format::Code, + (Some("output"), Some("list")) | (Some("list"), None) => Format::List, + (Some("output"), Some("scalar")) + | (Some("scalar"), None) + | (Some("output"), Some("verbatim")) + | (Some("verbatim"), None) => Format::Verbatim, + (Some("output"), Some("html")) | (Some("html"), None) => Format::Html, + (Some("output"), Some("latex")) | (Some("latex"), None) => Format::Latex, + (Some("output"), Some("raw")) | (Some("raw"), None) => Format::Raw, + (Some("value"), _) => anyhow::bail!("{language:?} is not supported."), + _ => return Ok(None), + }; + + let results = collect_output(command, &block.value(), format, path)?; + + if let Some((start, end)) = find_existing_results(&block) { + Ok(Some((start, end, results))) + } else { + let start = block.end() as usize; + Ok(Some((start, start, format!("\n#+RESULTS:\n{}\n", results)))) + } +} + +fn collect_output( + command: &str, + value: &str, + format: Format, + path: &Path, +) -> anyhow::Result { + let path = path.join("orgize-temporary"); + + let mut file = File::create(&path)?; + + file.write_all(value.as_bytes())?; + + let output = process::Command::new(command).arg(path).output()?; + + let output = String::from_utf8_lossy(&output.stdout); + + match format { + Format::Code => Ok(once("#+begin_src") + .chain(output.lines()) + .chain(once("#+end_src")) + .fold(String::new(), |acc, line| acc + line + "\n")), + + Format::Html => Ok(once("#+begin_export html") + .chain(output.lines()) + .chain(once("#+end_export")) + .fold(String::new(), |acc, line| acc + line + "\n")), + + Format::Latex => Ok(once("#+begin_export latex") + .chain(output.lines()) + .chain(once("#+end_export")) + .fold(String::new(), |acc, line| acc + line + "\n")), + + Format::List => Ok(output + .lines() + .fold(String::new(), |acc, line| acc + "- " + line + "\n")), + + Format::Verbatim => Ok(output + .lines() + .fold(String::new(), |acc, line| acc + ": " + line + "\n")), + + Format::Raw => Ok(output.to_string()), + } +} + +fn find_existing_results(block: &SourceBlock) -> Option<(usize, usize)> { + let results = block + .syntax() + .next_sibling() + .filter(|n| { + matches!( + n.kind(), + SyntaxKind::ORG_TABLE + | SyntaxKind::FIXED_WIDTH + | SyntaxKind::LIST + | SyntaxKind::SOURCE_BLOCK + | SyntaxKind::EXPORT_BLOCK + ) + }) + .filter(|n| { + n.children() + .filter_map(AffiliatedKeyword::cast) + .any(|k| k.key().eq_ignore_ascii_case("results")) + })?; + + let mut iter = results + .children_with_tokens() + .skip_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) + .take_while(|n| n.kind() != SyntaxKind::BLANK_LINE); + + let first = iter.next(); + + let last = iter.last(); + + let start = first.as_ref().map(|n| n.text_range().start())?; + + let end = last.or(first).map(|x| x.text_range().end())?; + + Some((start.into(), end.into())) +} diff --git a/orgize-common/src/formatting.rs b/orgize-common/src/formatting.rs new file mode 100644 index 0000000..d37ef4c --- /dev/null +++ b/orgize-common/src/formatting.rs @@ -0,0 +1,133 @@ +use orgize::{ + ast::Rule, + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::AstNode, + Org, SyntaxKind, SyntaxNode, +}; + +pub fn formatting(org: &Org) -> Vec<(usize, usize, String)> { + let mut format = FormattingTraverser { edits: vec![] }; + + org.traverse(&mut format); + + format.edits +} + +struct FormattingTraverser { + edits: Vec<(usize, usize, String)>, +} + +impl Traverser for FormattingTraverser { + fn event(&mut self, event: Event, _: &mut TraversalContext) { + match event { + Event::Rule(rule) => { + format_rule(&rule, &mut self.edits); + format_blank_lines(rule.syntax(), &mut self.edits); + } + Event::Clock(clock) => { + format_blank_lines(clock.syntax(), &mut self.edits); + } + + Event::Enter(Container::Document(document)) => { + format_blank_lines(document.syntax(), &mut self.edits); + } + Event::Enter(Container::Paragraph(paragraph)) => { + format_blank_lines(paragraph.syntax(), &mut self.edits); + } + Event::Enter(Container::List(list)) => { + format_blank_lines(list.syntax(), &mut self.edits); + } + Event::Enter(Container::OrgTable(table)) => { + format_blank_lines(table.syntax(), &mut self.edits); + } + Event::Enter(Container::SpecialBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::QuoteBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::CenterBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::VerseBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::CommentBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::ExampleBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + Event::Enter(Container::ExportBlock(block)) => { + format_blank_lines(block.syntax(), &mut self.edits); + } + + _ => {} + } + } +} + +fn format_rule(rule: &Rule, edits: &mut Vec<(usize, usize, String)>) { + let node = rule.syntax(); + + for token in node.children_with_tokens().filter_map(|e| e.into_token()) { + if token.kind() == SyntaxKind::WHITESPACE && !token.text().is_empty() { + edits.push(( + token.text_range().start().into(), + token.text_range().end().into(), + "".into(), + )); + } + + if token.kind() == SyntaxKind::TEXT && token.text().len() != 5 { + edits.push(( + token.text_range().start().into(), + token.text_range().end().into(), + "-----".into(), + )); + } + + if token.kind() == SyntaxKind::NEW_LINE && token.text() != "\n" { + edits.push(( + token.text_range().start().into(), + token.text_range().end().into(), + "\n".into(), + )); + } + } +} + +fn format_blank_lines(node: &SyntaxNode, edits: &mut Vec<(usize, usize, String)>) { + let mut blank_lines = node + .children_with_tokens() + .filter_map(|e| e.into_token()) + .filter(|n| n.kind() == SyntaxKind::BLANK_LINE); + + if let Some(line) = blank_lines.next() { + if line.text() != "\n" { + edits.push(( + line.text_range().start().into(), + line.text_range().end().into(), + "\n".into(), + )); + } + } + + match (blank_lines.next(), blank_lines.last()) { + (Some(first), Some(last)) => { + edits.push(( + first.text_range().start().into(), + last.text_range().end().into(), + "".into(), + )); + } + (Some(first), None) => { + edits.push(( + first.text_range().start().into(), + first.text_range().end().into(), + "".into(), + )); + } + _ => {} + } +} diff --git a/orgize-common/src/header_argument.rs b/orgize-common/src/header_argument.rs new file mode 100644 index 0000000..367fd79 --- /dev/null +++ b/orgize-common/src/header_argument.rs @@ -0,0 +1,111 @@ +use jetscii::Substring; +use nom::{ + bytes::complete::take_while1, + character::complete::{space0, space1}, + InputTake, +}; +use orgize::{ + ast::{Headline, Keyword, Token}, + rowan::ast::AstNode, + SyntaxKind, SyntaxNode, +}; + +pub fn header_argument<'a>( + arg1: &'a str, + arg2: &'a str, + arg3: &'a str, + key: &str, + default: &'static str, +) -> &'a str { + extract_header_args(arg1, key) + .or_else(|_| extract_header_args(arg2, key)) + .or_else(|_| extract_header_args(arg3, key)) + .unwrap_or(default) +} + +pub fn property_keyword(node: &SyntaxNode) -> Option { + node.ancestors() + .find(|n| n.kind() == SyntaxKind::DOCUMENT) + .and_then(|n| n.first_child()) + .filter(|n| n.kind() == SyntaxKind::SECTION) + .and_then(|n| { + n.children() + .filter_map(Keyword::cast) + .filter(|kw| kw.key().eq_ignore_ascii_case("PROPERTY")) + .map(|kw| kw.value()) + .find(|value| value.trim_start().starts_with("header-args ")) + }) +} + +pub fn property_drawer(node: &SyntaxNode) -> Option { + node.ancestors() + .find_map(Headline::cast) + .and_then(|hdl| hdl.properties()) + .and_then(|drawer| drawer.get("header-args")) +} + +pub fn extract_header_args<'a>(input: &'a str, key: &str) -> Result<&'a str, nom::Err<()>> { + let mut i = input; + + while !i.is_empty() { + let (input, _) = space0(i)?; + let (input, name) = take_while1(|c| c != ' ' && c != '\t')(input)?; + + if !name.eq_ignore_ascii_case(key) { + debug_assert!(input.len() < i.len(), "{} < {}", input.len(), i.len()); + i = input; + continue; + } + + let (input, _) = space1(input)?; + + if let Some(idx) = Substring::new(" :") + .find(input) + .or_else(|| Substring::new("\t:").find(input)) + { + let idx = input[0..idx] + .rfind(|c| c != ' ' && c != '\t') + .map(|i| i + 1) + .unwrap_or(idx); + + let (_, value) = input.take_split(idx); + + return Ok(value.trim()); + } else { + return Ok(input.trim()); + } + } + + Err(nom::Err::Error(())) +} + +#[test] +fn parse_header_args() { + assert!(extract_header_args("", ":tangle").is_err()); + assert!(extract_header_args(" :noweb yes", ":tangle1").is_err()); + assert!(extract_header_args(":tangle", ":tangle").is_err()); + + assert_eq!(extract_header_args(":tangle ", ":tangle").unwrap(), ""); + + assert_eq!( + extract_header_args(":tangle emacs.d/init.el", ":tangle").unwrap(), + "emacs.d/init.el" + ); + assert_eq!( + extract_header_args(" :tangle emacs.d/init.el", ":tangle").unwrap(), + "emacs.d/init.el" + ); + assert_eq!( + extract_header_args(" :tangle emacs.d/init.el :noweb yes", ":tangle").unwrap(), + "emacs.d/init.el" + ); + assert_eq!( + extract_header_args(" :noweb yes :tangle emacs.d/init.el", ":tangle").unwrap(), + "emacs.d/init.el" + ); + + assert_eq!( + extract_header_args(":results output code", ":results").unwrap(), + "output code" + ); +} diff --git a/orgize-common/src/lib.rs b/orgize-common/src/lib.rs new file mode 100644 index 0000000..d261106 --- /dev/null +++ b/orgize-common/src/lib.rs @@ -0,0 +1,13 @@ +mod detangle; +mod execute_src_block; +mod formatting; +mod header_argument; +mod tangle; +mod utils; + +pub use detangle::detangle; +pub use execute_src_block::execute; +pub use formatting::formatting; +pub use header_argument::*; +pub use tangle::tangle; +pub use utils::headline_slug; diff --git a/orgize-common/src/tangle.rs b/orgize-common/src/tangle.rs new file mode 100644 index 0000000..5c779fe --- /dev/null +++ b/orgize-common/src/tangle.rs @@ -0,0 +1,130 @@ +// TODO: :noweb support + +use orgize::{ + ast::{Headline, SourceBlock}, + rowan::{ast::AstNode, Direction}, + SyntaxKind, +}; +use resolve_path::PathResolveExt; +use std::fmt::Write; +use std::path::PathBuf; + +use crate::{ + header_argument::{header_argument, property_drawer, property_keyword}, + utils::language_comments, +}; + +pub fn tangle( + block: SourceBlock, + path: &PathBuf, +) -> anyhow::Result, String, bool)>> { + let arg1 = block.parameters().unwrap_or_default(); + let arg2 = property_drawer(block.syntax()).unwrap_or_default(); + let arg3 = property_keyword(block.syntax()).unwrap_or_default(); + let language = block.language().unwrap_or_default(); + + let tangle = header_argument(&arg1, &arg2, &arg3, ":tangle", "no"); + + if tangle == "no" { + return Ok(None); + } + + let comments = header_argument(&arg1, &arg2, &arg3, ":comments", "no"); + let padline = header_argument(&arg1, &arg2, &arg3, ":padline", "no"); + let shebang = header_argument(&arg1, &arg2, &arg3, ":shebang", "no"); + let mode = header_argument( + &arg1, + &arg2, + &arg3, + ":tangle-mode", + if shebang == "yea" { "o755" } else { "no" }, + ); + let is_mkdir = header_argument(&arg1, &arg2, &arg3, ":mkdir", "no"); + + let parent = block + .syntax() + .ancestors() + .find(|n| n.kind() == SyntaxKind::HEADLINE || n.kind() == SyntaxKind::DOCUMENT); + + let nth = parent + .as_ref() + .and_then(|n| n.children().position(|c| &c == block.syntax())) + .unwrap_or(1); + + let headline_title = parent.and_then(Headline::cast).map(|headline| { + headline + .title() + .fold(String::new(), |a, n| a + &n.to_string()) + }); + + let path = tangle.try_resolve_in(path)?.to_path_buf(); + + let mut permission = None; + let mut content = String::new(); + + if mode != "no" + && mode.len() == 4 + && mode.starts_with('o') + && mode.bytes().skip(1).all(|b| (b'0'..=b'7').contains(&b)) + { + permission = u32::from_str_radix(&mode[1..], 8).ok(); + } + + if shebang != "no" && !shebang.is_empty() { + content.push_str(shebang); + } + + if comments == "org" || comments == "both" { + if let Some((begin, end)) = language_comments(&language) { + let start = block + .syntax() + .siblings(Direction::Prev) + .skip(1) // skip self + .take_while(|n| n.kind() != SyntaxKind::SOURCE_BLOCK) + .last(); + + for sibling in start + .into_iter() + .flat_map(|start| start.siblings(Direction::Next)) + .take_while(|node| node != block.syntax()) + { + for line in sibling.to_string().lines() { + if line.is_empty() { + let _ = writeln!(content); + } else { + let _ = writeln!(content, "{begin} {line} {end}"); + } + } + } + } + } + + if comments == "yes" || comments == "link" || comments == "noweb" || comments == "both" { + if let Some((begin, end)) = language_comments(&language) { + let _ = writeln!( + content, + "{begin} [[file:{path}::*{title}][{title}:{nth}]] {end}", + title = headline_title.as_deref().unwrap_or("No heading"), + path = path.to_string_lossy(), + ); + } + } + + content.push_str(&block.value()); + + if padline != "no" { + let _ = writeln!(content); + } + + if comments == "yes" || comments == "link" || comments == "noweb" || comments == "both" { + if let Some((begin, end)) = language_comments(&language) { + let _ = writeln!( + content, + "{begin} {title}:{nth} ends here {end}", + title = headline_title.as_deref().unwrap_or("No heading"), + ); + } + } + + Ok(Some((path, permission, content, is_mkdir != "no"))) +} diff --git a/orgize-common/src/utils.rs b/orgize-common/src/utils.rs new file mode 100644 index 0000000..9aa6592 --- /dev/null +++ b/orgize-common/src/utils.rs @@ -0,0 +1,35 @@ +use orgize::ast::Headline; + +pub fn language_comments(language: &str) -> Option<(&str, &str)> { + match language { + "c" | "cpp" | "c++" | "go" | "js" | "javascript" | "ts" | "typescript" | "rust" + | "vera" | "jsonc" => Some(("//", "")), + "toml" | "tml" | "yaml" | "yml" | "conf" | "gitconfig" | "conf-toml" | "sh" | "shell" + | "bash" | "zsh" | "fish" => Some(("#", "")), + "lua" | "sql" => Some(("--", "")), + "lisp" | "emacs-lisp" | "elisp" => Some((";;", "")), + "xml" | "html" | "svg" => Some(("")), + _ => None, + } +} + +pub fn language_execute_command(language: &str) -> Option<&str> { + match language { + "js" | "javascript" => Some("node"), + "sh" | "bash" => Some("bash"), + "py" | "python" => Some("python"), + "fish" => Some("fish"), + _ => None, + } +} + +pub fn headline_slug(headline: &Headline) -> String { + headline.title().fold(String::new(), |mut acc, elem| { + for ch in elem.to_string().chars() { + if ch.is_ascii_graphic() { + acc.push(ch); + } + } + acc + }) +} diff --git a/orgize-lsp/Cargo.toml b/orgize-lsp/Cargo.toml new file mode 100644 index 0000000..0093b76 --- /dev/null +++ b/orgize-lsp/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "orgize-lsp" +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +license.workspace = true +description = "Language server for Org-mode, powered by orgize." + +[dependencies] +orgize = { path = "../orgize" } +orgize-common = { path = "../orgize-common" } +tokio = { version = "1.17", features = ["full"] } +tower-lsp = { version = "0.20", features = ["proposed"] } +dashmap = "5.1" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +anyhow = "1.0" +tempfile = "3.8" +resolve-path = "0.1" +memchr = "2.6" + +[[bin]] +name = "orgize-lsp" +path = "src/main.rs" diff --git a/orgize-lsp/README.md b/orgize-lsp/README.md new file mode 100644 index 0000000..3c4e569 --- /dev/null +++ b/orgize-lsp/README.md @@ -0,0 +1,50 @@ +# `orgize-lsp` + +Language server for org-mode, powered by orgize. + +## Install + +### Server + +```sh +$ cargo install --path . +``` + +### Client (vscode) + +```sh +$ pnpm run -C editors/vscode package --no-dependencies +$ code --install-extension ./editors/vscode/orgize-lsp.vsix --force +``` + +## Supported features + +1. Folding range + + - Fold headline, list, table, blocks + +2. Document symbols + + - Headings + +3. Formatting + +4. Document link + + - File links + + - Source block `:tangle` arguments + + - Internal links + +5. Code lens + + - Generate toc heading + + - Tangle/detanlge source block + + - Evaluate source block + +6. Commands + + - Show syntax tree diff --git a/orgize-lsp/editors/vscode/.gitignore b/orgize-lsp/editors/vscode/.gitignore new file mode 100644 index 0000000..713e0c1 --- /dev/null +++ b/orgize-lsp/editors/vscode/.gitignore @@ -0,0 +1,3 @@ +dist +*.vsix +node_modules \ No newline at end of file diff --git a/orgize-lsp/editors/vscode/.vscodeignore b/orgize-lsp/editors/vscode/.vscodeignore new file mode 100644 index 0000000..cb42174 --- /dev/null +++ b/orgize-lsp/editors/vscode/.vscodeignore @@ -0,0 +1,6 @@ +** +!dist/ +!syntaxes/ +!package.json +!org.configuration.json +!README.md \ No newline at end of file diff --git a/orgize-lsp/editors/vscode/LICENSE b/orgize-lsp/editors/vscode/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/orgize-lsp/editors/vscode/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/orgize-lsp/editors/vscode/build.mjs b/orgize-lsp/editors/vscode/build.mjs new file mode 100644 index 0000000..e834128 --- /dev/null +++ b/orgize-lsp/editors/vscode/build.mjs @@ -0,0 +1,13 @@ +import * as esbuild from "esbuild"; + +await esbuild.build({ + bundle: true, + entryPoints: ["src/main.ts"], + external: ["vscode"], + outfile: "dist/main.js", + format: "cjs", + platform: "node", + target: "node16", + minify: true, + treeShaking: true, +}); diff --git a/orgize-lsp/editors/vscode/org.configuration.json b/orgize-lsp/editors/vscode/org.configuration.json new file mode 100644 index 0000000..8144529 --- /dev/null +++ b/orgize-lsp/editors/vscode/org.configuration.json @@ -0,0 +1,29 @@ +{ + "comments": { + "lineComment": "#", + "blockComment": ["#+BEGIN_COMMENT\n", "\n#+END_COMMENT"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"], + ["*", "*"], + ["/", "/"], + ["_", "_"], + ["=", "="], + ["~", "~"] + ] +} diff --git a/orgize-lsp/editors/vscode/package.json b/orgize-lsp/editors/vscode/package.json new file mode 100644 index 0000000..6b48b59 --- /dev/null +++ b/orgize-lsp/editors/vscode/package.json @@ -0,0 +1,111 @@ +{ + "name": "orgize-lsp", + "private": true, + "version": "0.0.0-dev", + "engines": { + "vscode": "^1.75.0" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/PoiScript/orgize" + }, + "scripts": { + "vscode:prepublish": "node build.mjs", + "package": "vsce package -o orgize-lsp.vsix --skip-license --no-dependencies", + "format": "prettier --write .", + "format:check": "prettier --check .", + "typecheck": "tsc" + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/node": "~16.11.68", + "@types/vscode": "~1.75.1", + "@vscode/test-electron": "^2.3.8", + "@vscode/vsce": "^2.22.0", + "esbuild": "^0.18.20", + "ovsx": "^0.8.3", + "prettier": "^3.1.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + }, + "main": "./dist/main", + "contributes": { + "commands": [ + { + "command": "orgize.syntax-tree", + "title": "(Debug) Show org syntax tree" + } + ], + "languages": [ + { + "id": "org", + "aliases": [ + "Org", + "Org Markup", + "Org Mode" + ], + "extensions": [ + ".org" + ], + "configuration": "./org.configuration.json" + } + ], + "grammars": [ + { + "language": "org", + "scopeName": "source.org", + "path": "./syntaxes/org.tmLanguage.json", + "embeddedLanguages": { + "meta.embedded.block.html": "html", + "source.js": "javascript", + "source.css": "css", + "meta.embedded.block.frontmatter": "yaml", + "meta.embedded.block.css": "css", + "meta.embedded.block.ini": "ini", + "meta.embedded.block.java": "java", + "meta.embedded.block.lua": "lua", + "meta.embedded.block.makefile": "makefile", + "meta.embedded.block.perl": "perl", + "meta.embedded.block.r": "r", + "meta.embedded.block.ruby": "ruby", + "meta.embedded.block.php": "php", + "meta.embedded.block.sql": "sql", + "meta.embedded.block.vs_net": "vs_net", + "meta.embedded.block.xml": "xml", + "meta.embedded.block.xsl": "xsl", + "meta.embedded.block.yaml": "yaml", + "meta.embedded.block.dosbatch": "dosbatch", + "meta.embedded.block.clojure": "clojure", + "meta.embedded.block.coffee": "coffee", + "meta.embedded.block.c": "c", + "meta.embedded.block.cpp": "cpp", + "meta.embedded.block.diff": "diff", + "meta.embedded.block.dockerfile": "dockerfile", + "meta.embedded.block.go": "go", + "meta.embedded.block.groovy": "groovy", + "meta.embedded.block.pug": "jade", + "meta.embedded.block.javascript": "javascript", + "meta.embedded.block.json": "json", + "meta.embedded.block.jsonc": "jsonc", + "meta.embedded.block.latex": "latex", + "meta.embedded.block.less": "less", + "meta.embedded.block.objc": "objc", + "meta.embedded.block.scss": "scss", + "meta.embedded.block.perl6": "perl6", + "meta.embedded.block.powershell": "powershell", + "meta.embedded.block.python": "python", + "meta.embedded.block.rust": "rust", + "meta.embedded.block.scala": "scala", + "meta.embedded.block.shellscript": "shellscript", + "meta.embedded.block.typescript": "typescript", + "meta.embedded.block.typescriptreact": "typescriptreact", + "meta.embedded.block.csharp": "csharp", + "meta.embedded.block.fsharp": "fsharp" + } + } + ] + } +} diff --git a/orgize-lsp/editors/vscode/pnpm-lock.yaml b/orgize-lsp/editors/vscode/pnpm-lock.yaml new file mode 100644 index 0000000..769206e --- /dev/null +++ b/orgize-lsp/editors/vscode/pnpm-lock.yaml @@ -0,0 +1,1268 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + vscode-languageclient: + specifier: ^9.0.1 + version: 9.0.1 + +devDependencies: + '@types/node': + specifier: ~16.11.68 + version: 16.11.68 + '@types/vscode': + specifier: ~1.75.1 + version: 1.75.1 + '@vscode/test-electron': + specifier: ^2.3.8 + version: 2.3.8 + '@vscode/vsce': + specifier: ^2.22.0 + version: 2.22.0 + esbuild: + specifier: ^0.18.20 + version: 0.18.20 + ovsx: + specifier: ^0.8.3 + version: 0.8.3 + prettier: + specifier: ^3.1.1 + version: 3.1.1 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: ^5.3.3 + version: 5.3.3 + +packages: + + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tootallnate/once@1.1.2: + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + dev: true + + /@types/node@16.11.68: + resolution: {integrity: sha512-JkRpuVz3xCNCWaeQ5EHLR/6woMbHZz/jZ7Kmc63AkU+1HxnoUugzSWMck7dsR4DvNYX8jp9wTi9K7WvnxOIQZQ==} + dev: true + + /@types/vscode@1.75.1: + resolution: {integrity: sha512-emg7wdsTFzdi+elvoyoA+Q8keEautdQHyY5LNmHVM4PTpY8JgOTVADrGVyXGepJ6dVW2OS5/xnLUWh+nZxvdiA==} + dev: true + + /@vscode/test-electron@2.3.8: + resolution: {integrity: sha512-b4aZZsBKtMGdDljAsOPObnAi7+VWIaYl3ylCz1jTs+oV6BZ4TNHcVNC3xUn0azPeszBmwSBDQYfFESIaUQnrOg==} + engines: {node: '>=16'} + dependencies: + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + jszip: 3.10.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@vscode/vsce@2.22.0: + resolution: {integrity: sha512-8df4uJiM3C6GZ2Sx/KilSKVxsetrTBBIUb3c0W4B1EWHcddioVs5mkyDKtMNP0khP/xBILVSzlXxhV+nm2rC9A==} + engines: {node: '>= 14'} + hasBin: true + dependencies: + azure-devops-node-api: 11.2.0 + chalk: 2.4.2 + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + glob: 7.2.3 + hosted-git-info: 4.1.0 + jsonc-parser: 3.2.0 + leven: 3.1.0 + markdown-it: 12.3.2 + mime: 1.6.0 + minimatch: 3.1.2 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 7.5.4 + tmp: 0.2.1 + typed-rest-client: 1.8.11 + url-join: 4.0.1 + xml2js: 0.5.0 + yauzl: 2.10.0 + yazl: 2.5.1 + optionalDependencies: + keytar: 7.9.0 + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /azure-devops-node-api@11.2.0: + resolution: {integrity: sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==} + dependencies: + tunnel: 0.0.6 + typed-rest-client: 1.8.11 + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + requiresBuild: true + dev: true + optional: true + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + requiresBuild: true + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + optional: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + requiresBuild: true + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + optional: true + + /call-bind@1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + dev: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + dev: true + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: true + + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + requiresBuild: true + dev: true + optional: true + + /ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + mimic-response: 3.1.0 + dev: true + optional: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + requiresBuild: true + dev: true + optional: true + + /define-data-property@1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + requiresBuild: true + dev: true + optional: true + + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + requiresBuild: true + dependencies: + once: 1.4.0 + dev: true + optional: true + + /entities@2.1.0: + resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==} + dev: true + + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + requiresBuild: true + dev: true + optional: true + + /fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + dependencies: + pend: 1.2.0 + dev: true + + /follow-redirects@1.15.3: + resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: true + + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + requiresBuild: true + dev: true + optional: true + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + dev: true + + /get-intrinsic@1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + dev: true + + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + requiresBuild: true + dev: true + optional: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-property-descriptors@1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + dev: true + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /hasown@2.0.0: + resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + dev: true + + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + dev: true + + /http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + requiresBuild: true + dev: true + optional: true + + /immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + dev: true + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + requiresBuild: true + dev: true + optional: true + + /is-ci@2.0.0: + resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} + hasBin: true + dependencies: + ci-info: 2.0.0 + dev: true + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + dev: true + + /keytar@7.9.0: + resolution: {integrity: sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==} + requiresBuild: true + dependencies: + node-addon-api: 4.3.0 + prebuild-install: 7.1.1 + dev: true + optional: true + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + + /lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + dependencies: + immediate: 3.0.6 + dev: true + + /linkify-it@3.0.3: + resolution: {integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==} + dependencies: + uc.micro: 1.0.6 + dev: true + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + + /markdown-it@12.3.2: + resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 2.1.0 + linkify-it: 3.0.3 + mdurl: 1.0.1 + uc.micro: 1.0.6 + dev: true + + /mdurl@1.0.1: + resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + dev: true + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + requiresBuild: true + dev: true + optional: true + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + requiresBuild: true + dev: true + optional: true + + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + requiresBuild: true + dev: true + optional: true + + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + requiresBuild: true + dev: true + optional: true + + /node-abi@3.52.0: + resolution: {integrity: sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + semver: 7.5.4 + dev: true + optional: true + + /node-addon-api@4.3.0: + resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + requiresBuild: true + dev: true + optional: true + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + dev: true + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /ovsx@0.8.3: + resolution: {integrity: sha512-LG7wTzy4eYV/KolFeO4AwWPzQSARvCONzd5oHQlNvYOlji2r/zjbdK8pyObZN84uZlk6rQBWrJrAdJfh/SX0Hg==} + engines: {node: '>= 14'} + hasBin: true + dependencies: + '@vscode/vsce': 2.22.0 + commander: 6.2.1 + follow-redirects: 1.15.3 + is-ci: 2.0.0 + leven: 3.1.0 + semver: 7.5.4 + tmp: 0.2.1 + transitivePeerDependencies: + - debug + dev: true + + /pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: true + + /parse-semver@1.1.1: + resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} + dependencies: + semver: 5.7.2 + dev: true + + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: true + + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.5.0 + dev: true + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + dev: true + + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.52.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: true + optional: true + + /prettier@3.1.1: + resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + requiresBuild: true + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + optional: true + + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: true + + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + requiresBuild: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: true + optional: true + + /read@1.0.7: + resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} + engines: {node: '>=0.8'} + dependencies: + mute-stream: 0.0.8 + dev: true + + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + requiresBuild: true + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + optional: true + + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + requiresBuild: true + dev: true + optional: true + + /sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + dev: true + + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + dev: true + + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + + /set-function-length@1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: true + + /setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: true + + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + object-inspect: 1.13.1 + dev: true + + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + requiresBuild: true + dev: true + optional: true + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + requiresBuild: true + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: true + optional: true + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + requiresBuild: true + dependencies: + safe-buffer: 5.2.1 + dev: true + optional: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dev: true + optional: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + requiresBuild: true + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: true + optional: true + + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + dev: true + optional: true + + /tmp@0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: true + + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + requiresBuild: true + dependencies: + safe-buffer: 5.2.1 + dev: true + optional: true + + /tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + dev: true + + /typed-rest-client@1.8.11: + resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + dependencies: + qs: 6.11.2 + tunnel: 0.0.6 + underscore: 1.13.6 + dev: true + + /typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /uc.micro@1.0.6: + resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + dev: true + + /underscore@1.13.6: + resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} + dev: true + + /url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + dev: false + + /vscode-languageclient@9.0.1: + resolution: {integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==} + engines: {vscode: ^1.82.0} + dependencies: + minimatch: 5.1.6 + semver: 7.5.4 + vscode-languageserver-protocol: 3.17.5 + dev: false + + /vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + dev: false + + /vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + dev: false + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} + engines: {node: '>=4.0.0'} + dependencies: + sax: 1.3.0 + xmlbuilder: 11.0.1 + dev: true + + /xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + /yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + dev: true + + /yazl@2.5.1: + resolution: {integrity: sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==} + dependencies: + buffer-crc32: 0.2.13 + dev: true diff --git a/orgize-lsp/editors/vscode/src/main.ts b/orgize-lsp/editors/vscode/src/main.ts new file mode 100644 index 0000000..1efcddc --- /dev/null +++ b/orgize-lsp/editors/vscode/src/main.ts @@ -0,0 +1,53 @@ +import { ExtensionContext } from "vscode"; + +import { + Executable, + LanguageClient, + LanguageClientOptions, + ServerOptions, +} from "vscode-languageclient/node"; + +import SyntaxTreeProvider from "./syntax-tree"; +import PreviewHtmlProvider from "./preview-html"; + +export let client: LanguageClient; + +export function activate(context: ExtensionContext) { + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const run: Executable = { + command: "orgize-lsp", + }; + + const serverOptions: ServerOptions = { + run, + debug: run, + }; + + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: "file", language: "org" }], + }; + + // Create the language client and start the client. + client = new LanguageClient( + "orgize-lsp", + "Orgize LSP", + serverOptions, + clientOptions + ); + + // Start the client. This will also launch the server + client.start(); + + context.subscriptions.push(SyntaxTreeProvider.register()); + context.subscriptions.push(PreviewHtmlProvider.register()); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/orgize-lsp/editors/vscode/src/preview-html.ts b/orgize-lsp/editors/vscode/src/preview-html.ts new file mode 100644 index 0000000..941715e --- /dev/null +++ b/orgize-lsp/editors/vscode/src/preview-html.ts @@ -0,0 +1,234 @@ +import { + Disposable, + ExtensionContext, + TextDocumentContentProvider, + Uri, + ViewColumn, + Webview, + WebviewOptions, + WebviewPanel, + commands, + window, + workspace, +} from "vscode"; + +import { client } from "./main"; + +export const register = (context: ExtensionContext) => { + const provider = new PreviewHtmlProvider(); + + context.subscriptions.push( + workspace.registerTextDocumentContentProvider( + "orgize-lsp-preview", + provider + ) + ); +}; + +export default class PreviewHtmlProvider + implements TextDocumentContentProvider +{ + static readonly scheme = "orgize-preview-html"; + + static register(): Disposable { + const provider = new PreviewHtmlProvider(); + + // register content provider for scheme `references` + // register document link provider for scheme `references` + const providerRegistrations = workspace.registerTextDocumentContentProvider( + PreviewHtmlProvider.scheme, + provider + ); + + // register command that crafts an uri with the `references` scheme, + // open the dynamic document, and shows it in the next editor + const commandRegistration = commands.registerTextEditorCommand( + "orgize.preview-html", + (editor) => { + return workspace + .openTextDocument(encode(editor.document.uri)) + .then((doc) => window.showTextDocument(doc, editor.viewColumn! + 1)); + } + ); + + return Disposable.from( + provider, + commandRegistration, + providerRegistrations + ); + } + + dispose() { + // this._subscriptions.dispose(); + // this._documents.clear(); + // this._editorDecoration.dispose(); + // this._onDidChange.dispose(); + } + + async provideTextDocumentContent(uri: Uri): Promise { + if (!client) { + return "LSP server is not ready..."; + } + + return client.sendRequest("workspace/executeCommand", { + command: "orgize.syntax-tree", + arguments: [uri.toString()], + }); + } +} + +class PreviewHtmlPanel { + /** + * Track the currently panel. Only allow a single panel to exist at a time. + */ + public static currentPanel: PreviewHtmlPanel | undefined; + + public static readonly viewType = "orgizePreviewHtml"; + + private readonly _panel: WebviewPanel; + private readonly _extensionUri: Uri; + private _disposables: Disposable[] = []; + + public static createOrShow(uri: Uri) { + const column = window.activeTextEditor + ? window.activeTextEditor.viewColumn + : undefined; + + // If we already have a panel, show it. + if (PreviewHtmlPanel.currentPanel) { + PreviewHtmlPanel.currentPanel._panel.reveal(column); + return; + } + + // Otherwise, create a new panel. + const panel = window.createWebviewPanel( + PreviewHtmlPanel.viewType, + "Preview of " + uri.fsPath, + column || ViewColumn.One, + getWebviewOptions(uri) + ); + + PreviewHtmlPanel.currentPanel = new PreviewHtmlPanel(panel, uri); + } + + public static revive(panel: WebviewPanel, extensionUri: Uri) { + PreviewHtmlPanel.currentPanel = new PreviewHtmlPanel(panel, extensionUri); + } + + private constructor(panel: WebviewPanel, extensionUri: Uri) { + this._panel = panel; + this._extensionUri = extensionUri; + + // Set the webview's initial html content + this._update(); + + // Listen for when the panel is disposed + // This happens when the user closes the panel or when the panel is closed programmatically + this._panel.onDidDispose( + () => { + this.dispose(); + }, + null, + this._disposables + ); + + // Update the content based on view changes + this._panel.onDidChangeViewState( + (e) => { + if (this._panel.visible) { + this._update(); + } + }, + null, + this._disposables + ); + } + + public dispose() { + PreviewHtmlPanel.currentPanel = undefined; + + // Clean up our resources + this._panel.dispose(); + + while (this._disposables.length) { + const x = this._disposables.pop(); + if (x) { + x.dispose(); + } + } + } + + private _update() { + const webview = this._panel.webview; + this._panel.webview.html = this._getHtmlForWebview(webview); + } + + private _getHtmlForWebview(webview: Webview): string { + // // Local path to main script run in the webview + // const scriptPathOnDisk = Uri.joinPath( + // this._extensionUri, + // "media", + // "main.js" + // ); + + // // And the uri we use to load this script in the webview + // const scriptUri = webview.asWebviewUri(scriptPathOnDisk); + + // // Local path to css styles + // const styleResetPath = Uri.joinPath( + // this._extensionUri, + // "media", + // "reset.css" + // ); + + // const stylesPathMainPath = Uri.joinPath( + // this._extensionUri, + // "media", + // " css" + // ); + + // // Uri to load styles into webview + // const stylesResetUri = webview.asWebviewUri(styleResetPath); + // const stylesMainUri = webview.asWebviewUri(stylesPathMainPath); + + // Use a nonce to only allow specific scripts to be run + // const nonce = getNonce(); + + return ` + + + + + + + Cat Coding + + + +

0

+ + `; + } +} + +const getWebviewOptions = (extensionUri: Uri): WebviewOptions => { + return { + // Enable javascript in the webview + enableScripts: true, + + // And restrict the webview to only loading content from our extension's `media` directory. + localResourceRoots: [Uri.joinPath(extensionUri, "media")], + }; +}; + +const encode = (uri: Uri): Uri => { + return uri.with({ + scheme: PreviewHtmlProvider.scheme, + query: uri.path, + path: "tree.syntax", + }); +}; + +const decode = (uri: Uri): Uri => { + return uri.with({ scheme: "file", path: uri.query, query: "" }); +}; diff --git a/orgize-lsp/editors/vscode/src/syntax-tree.ts b/orgize-lsp/editors/vscode/src/syntax-tree.ts new file mode 100644 index 0000000..f151ef2 --- /dev/null +++ b/orgize-lsp/editors/vscode/src/syntax-tree.ts @@ -0,0 +1,77 @@ +import { + Disposable, + TextDocumentContentProvider, + Uri, + commands, + window, + workspace, +} from "vscode"; +import { client } from "./main"; + +export default class SyntaxTreeProvider implements TextDocumentContentProvider { + static readonly scheme = "orgize-syntax-tree"; + + static register(): Disposable { + const provider = new SyntaxTreeProvider(); + + // register content provider for scheme `references` + // register document link provider for scheme `references` + const providerRegistrations = workspace.registerTextDocumentContentProvider( + SyntaxTreeProvider.scheme, + provider + ); + + // register command that crafts an uri with the `references` scheme, + // open the dynamic document, and shows it in the next editor + const commandRegistration = commands.registerTextEditorCommand( + "orgize.syntax-tree", + (editor) => { + return workspace + .openTextDocument(encode(editor.document.uri)) + .then((doc) => window.showTextDocument(doc, editor.viewColumn! + 1)); + } + ); + + return Disposable.from( + provider, + commandRegistration, + providerRegistrations + ); + } + + dispose() { + // this._subscriptions.dispose(); + // this._documents.clear(); + // this._editorDecoration.dispose(); + // this._onDidChange.dispose(); + } + + async provideTextDocumentContent(uri: Uri): Promise { + if (!client) { + return "LSP server is not ready..."; + } + + const result = await client.sendRequest("workspace/executeCommand", { + command: "orgize.syntax-tree", + arguments: [decode(uri).toString()], + }); + + if (typeof result === "string") { + return result; + } + + return ""; + } +} + +const encode = (uri: Uri): Uri => { + return uri.with({ + scheme: SyntaxTreeProvider.scheme, + query: uri.path, + path: "tree.syntax", + }); +}; + +const decode = (uri: Uri): Uri => { + return uri.with({ scheme: "file", path: uri.query, query: "" }); +}; diff --git a/orgize-lsp/editors/vscode/syntaxes/org.tmLanguage.json b/orgize-lsp/editors/vscode/syntaxes/org.tmLanguage.json new file mode 100644 index 0000000..f3df40d --- /dev/null +++ b/orgize-lsp/editors/vscode/syntaxes/org.tmLanguage.json @@ -0,0 +1,2060 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Org Markup", + "patterns": [ + { + "name": "markup.heading.org", + "begin": "^\\s*[*]{1}\\s+", + "end": "$", + "patterns": [ + { + "include": "#header-matches" + } + ] + }, + { + "name": "entity.name.type.org", + "begin": "^\\s*[*]{2}\\s+", + "end": "$", + "patterns": [ + { + "include": "#header-matches" + } + ] + }, + { + "name": "entity.name.function.org", + "begin": "^\\s*[*]{3}\\s+", + "end": "$", + "patterns": [ + { + "include": "#header-matches" + } + ] + }, + { + "name": "entity.other.attribute-name.org", + "begin": "^\\s*[*]{4}\\s+", + "end": "$", + "patterns": [ + { + "include": "#header-matches" + } + ] + }, + { + "include": "#common" + }, + { + "include": "#src-blocks" + }, + { + "include": "#blocks" + } + ], + "repository": { + "src-blocks": { + "patterns": [ + { + "name": "meta.block.source.js.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(js|javascript|mjs|es6|jsx)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.js", + "patterns": [ + { + "include": "source.js" + } + ] + } + ] + }, + { + "name": "meta.block.source.js.regexp.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(js.regexp|regexp)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.js.regexp", + "patterns": [ + { + "include": "source.js.regexp" + } + ] + } + ] + }, + { + "name": "meta.block.source.ts.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(ts|typescript)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.ts", + "patterns": [ + { + "include": "source.ts" + } + ] + } + ] + }, + { + "name": "meta.block.source.tsx.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(tsx)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.tsx", + "patterns": [ + { + "include": "source.tsx" + } + ] + } + ] + }, + { + "name": "meta.block.source.java.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(java)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.java", + "patterns": [ + { + "include": "source.java" + } + ] + } + ] + }, + { + "name": "meta.block.source.python.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.python", + "patterns": [ + { + "include": "source.python" + } + ] + } + ] + }, + { + "name": "meta.block.source.regexp.python.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(regexp.python|re)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.regexp.python", + "patterns": [ + { + "include": "source.regexp.python" + } + ] + } + ] + }, + { + "name": "meta.block.source.css.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(css)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.css", + "patterns": [ + { + "include": "source.css" + } + ] + } + ] + }, + { + "name": "meta.block.source.lua.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(lua)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.lua", + "patterns": [ + { + "include": "source.lua" + } + ] + } + ] + }, + { + "name": "meta.block.source.ini.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(ini|conf|properties)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.ini", + "patterns": [ + { + "include": "source.ini" + } + ] + } + ] + }, + { + "name": "meta.block.source.makefile.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(makefile|Malefile)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.makefile", + "patterns": [ + { + "include": "source.makefile" + } + ] + } + ] + }, + { + "name": "meta.block.source.perl.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(perl|pl|pm)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.perl", + "patterns": [ + { + "include": "source.perl" + } + ] + } + ] + }, + { + "name": "meta.block.source.r.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(r|R|s|S|Rprofile)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.r", + "patterns": [ + { + "include": "source.r" + } + ] + } + ] + }, + { + "name": "meta.block.source.ruby.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.ruby", + "patterns": [ + { + "include": "source.ruby" + } + ] + } + ] + }, + { + "name": "meta.block.source.php.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(php|php3|php4|php5|phpt|phtml|aw|ctp)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.php", + "patterns": [ + { + "include": "source.php" + } + ] + } + ] + }, + { + "name": "meta.block.source.sql.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(sql|ddl|dml)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.sql", + "patterns": [ + { + "include": "source.sql" + } + ] + } + ] + }, + { + "name": "meta.block.source.asp.vb.net.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(asp.vb.net|vb)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.asp.vb.net", + "patterns": [ + { + "include": "source.asp.vb.net" + } + ] + } + ] + }, + { + "name": "meta.block.source.dosbatch.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(dosbatch|batch|bat)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.dosbatch", + "patterns": [ + { + "include": "source.dosbatch" + } + ] + } + ] + }, + { + "name": "meta.block.source.clojure.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(clojure|clj|cljs)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.clojure", + "patterns": [ + { + "include": "source.clojure" + } + ] + } + ] + }, + { + "name": "meta.block.source.coffee.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(coffee|Cakefile|coffe.erb)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.coffee", + "patterns": [ + { + "include": "source.coffee" + } + ] + } + ] + }, + { + "name": "meta.block.source.c.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(c|h)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.c", + "patterns": [ + { + "include": "source.c" + } + ] + } + ] + }, + { + "name": "meta.block.source.cpp.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(cpp|c\\+\\+|cxx)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.cpp", + "patterns": [ + { + "include": "source.cpp" + } + ] + } + ] + }, + { + "name": "meta.block.source.objc.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(objc|objectivec|objective-c|mm|m|obj-c)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.objc", + "patterns": [ + { + "include": "source.objc" + } + ] + } + ] + }, + { + "name": "meta.block.source.diff.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(diff|patch|rej)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.diff", + "patterns": [ + { + "include": "source.diff" + } + ] + } + ] + }, + { + "name": "meta.block.source.dockerfile.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(dockerfile|Dockerfile)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.dockerfile", + "patterns": [ + { + "include": "source.dockerfile" + } + ] + } + ] + }, + { + "name": "meta.block.source.go.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(go|golang)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.go", + "patterns": [ + { + "include": "source.go" + } + ] + } + ] + }, + { + "name": "meta.block.source.groovy.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(groovy|gvy)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.groovy", + "patterns": [ + { + "include": "source.groovy" + } + ] + } + ] + }, + { + "name": "meta.block.source.lisp.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(lisp|emacs-lisp|common-lisp)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.lisp", + "patterns": [ + { + "include": "source.lisp" + } + ] + } + ] + }, + { + "name": "meta.block.source.pug.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(pug|jade)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.pug", + "patterns": [ + { + "include": "source.pug" + } + ] + } + ] + }, + { + "name": "meta.block.source.css.less.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(css.less|less)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.css.less", + "patterns": [ + { + "include": "source.css.less" + } + ] + } + ] + }, + { + "name": "meta.block.source.css.scss.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(css.scss|scss)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.css.scss", + "patterns": [ + { + "include": "source.css.scss" + } + ] + } + ] + }, + { + "name": "meta.block.source.perl.6.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(perl.6|perl6|p6|pl6|pm6|nqp)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.perl.6", + "patterns": [ + { + "include": "source.perl.6" + } + ] + } + ] + }, + { + "name": "meta.block.source.rust.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(rust|rs)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.rust", + "patterns": [ + { + "include": "source.rust" + } + ] + } + ] + }, + { + "name": "meta.block.source.scala.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(scala|sbt)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.scala", + "patterns": [ + { + "include": "source.scala" + } + ] + } + ] + }, + { + "name": "meta.block.source.shell.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(shell|sh|bash|zsh|bashrc|bash_profile)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.shell", + "patterns": [ + { + "include": "source.shell" + } + ] + } + ] + }, + { + "name": "meta.block.source.cs.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(cs|csharp|c#)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.cs", + "patterns": [ + { + "include": "source.cs" + } + ] + } + ] + }, + { + "name": "meta.block.source.fs.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(fs|fsharp|f#)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.fs", + "patterns": [ + { + "include": "source.fs" + } + ] + } + ] + }, + { + "name": "meta.block.source.dart.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(dart)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.dart", + "patterns": [ + { + "include": "source.dart" + } + ] + } + ] + }, + { + "name": "meta.block.source.nim.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(nim)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.nim", + "patterns": [ + { + "include": "source.nim" + } + ] + } + ] + }, + { + "name": "meta.block.source.elixir.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(elixir)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.elixir", + "patterns": [ + { + "include": "source.elixir" + } + ] + } + ] + }, + { + "name": "meta.block.source.erlang.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(erlang|erl|grl)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.erlang", + "patterns": [ + { + "include": "source.erlang" + } + ] + } + ] + }, + { + "name": "meta.block.source.ocaml.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(ocaml)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.ocaml", + "patterns": [ + { + "include": "source.ocaml" + } + ] + } + ] + }, + { + "name": "meta.block.source.zig.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(zig)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.zig", + "patterns": [ + { + "include": "source.zig" + } + ] + } + ] + }, + { + "name": "meta.block.source.yaml.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(yaml|yml)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.yaml", + "patterns": [ + { + "include": "source.yaml" + } + ] + } + ] + }, + { + "name": "meta.block.source.json.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(json)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.json", + "patterns": [ + { + "include": "source.json" + } + ] + } + ] + }, + { + "name": "meta.block.source.xml.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.xml", + "patterns": [ + { + "include": "text.xml" + } + ] + } + ] + }, + { + "name": "meta.block.source.xsl.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(xsl|xslt)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.xsl", + "patterns": [ + { + "include": "text.xml.xsl" + } + ] + } + ] + }, + { + "name": "meta.block.source.markdown.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(markdown|md)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.markdown", + "patterns": [ + { + "include": "text.html.markdown" + } + ] + } + ] + }, + { + "name": "meta.block.source.orgmode.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(orgmode|org)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.orgmode", + "patterns": [ + { + "include": "source.org" + } + ] + } + ] + }, + { + "name": "meta.block.source.html.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(html|htm|shtml|xhtml|inc|tmpl|tpl)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.html", + "patterns": [ + { + "include": "text.html.basic" + } + ] + } + ] + }, + { + "name": "meta.block.source.COMMIT_EDITMSG.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(COMMIT_EDITMSG|MERGE_MSG)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.COMMIT_EDITMSG", + "patterns": [ + { + "include": "text.git-commit" + } + ] + } + ] + }, + { + "name": "meta.block.source.git-rebase-todo.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(git-rebase-todo)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.git-rebase-todo", + "patterns": [ + { + "include": "text.git-rebase" + } + ] + } + ] + }, + { + "name": "meta.block.source.latex.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(latex|tex)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.latex", + "patterns": [ + { + "include": "text.tex.latex" + } + ] + } + ] + }, + { + "name": "meta.block.source.unknown.org", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_SRC)\\s+(\\w+)\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_SRC)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "2": { + "name": "constant.other.language.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(?i)(^|\\G)(?!\\s*#\\+END_SRC\\s*)", + "contentName": "meta.embedded.block.unknown" + } + ] + } + ] + }, + "blocks": { + "patterns": [ + { + "name": "meta.block.${2:/downcase}", + "begin": "(?i)(?:^|\\G)(?:\\s*)(#\\+BEGIN_(\\w+))\\b\\s*(.*)$", + "end": "(?i)(?:^|\\G)(?:\\s*)(#\\+END_\\2)\\s*$", + "beginCaptures": { + "1": { + "name": "keyword.control.block.org" + }, + "3": { + "name": "string.other.header-args.org" + } + }, + "endCaptures": { + "1": { + "name": "keyword.control.block.org" + } + }, + "patterns": [ + { + "include": "#common" + } + ] + } + ] + }, + "common": { + "patterns": [ + { + "include": "#timestamp" + }, + { + "include": "#link" + }, + { + "include": "#bold" + }, + { + "include": "#italic" + }, + { + "include": "#underline" + }, + { + "include": "#literal" + }, + { + "include": "#code" + }, + { + "include": "#verbatim" + }, + { + "include": "#comment" + }, + { + "include": "#keywords" + } + ] + }, + "header-matches": { + "patterns": [ + { + "include": "#common" + }, + { + "include": "#todo" + }, + { + "include": "#done" + }, + { + "include": "#userKeywords" + } + ] + }, + "timestamp": { + "patterns": [ + { + "name": "variable.org", + "match": "\\[\\d{4}-\\d{1,2}-\\d{1,2}(?: \\w{3})?\\]" + } + ] + }, + "link": { + "patterns": [ + { + "name": "meta.link.inline.org", + "match": "(\\[\\[)([^\\[\\]]+)(\\])(?:(\\[)([^\\[\\]]+)(\\]))?(\\])", + "captures": { + "1": { + "name": "punctuation.definition.string.begin.org" + }, + "2": { + "name": "markup.underline.link.org" + }, + "3": { + "name": "punctuation.definition.string.end.org" + }, + "4": { + "name": "punctuation.definition.string.begin.org" + }, + "5": { + "name": "string.other.link.title.org" + }, + "6": { + "name": "punctuation.definition.string.end.org" + }, + "7": { + "name": "punctuation.definition.string.end.org" + }, + "8": { + "name": "punctuation.definition.string.end.org" + } + } + } + ] + }, + "todo": { + "patterns": [ + { + "name": "invalid.illegal.org", + "match": "\\bTODO\\b" + } + ] + }, + "done": { + "patterns": [ + { + "name": "keyword.control.org", + "match": "\\bDONE\\b" + } + ] + }, + "userKeywords": { + "patterns": [ + { + "name": "string.quoted.double.org", + "match": "\\b([A-Z]{3,})\\b" + } + ] + }, + "bold": { + "patterns": [ + { + "name": "markup.bold.org", + "match": "(^|\\s)\\*[^\\s](.*?[^\\s])?\\*($|\\W)" + } + ] + }, + "italic": { + "patterns": [ + { + "name": "markup.italic.org", + "match": "(^|\\s)/[^\\s](.*?[^\\s])?/($|\\W)" + } + ] + }, + "underline": { + "patterns": [ + { + "name": "markup.underline.org", + "match": "(^|\\s)\\_[^\\s](.*?[^\\s])?\\_($|\\W)" + } + ] + }, + "literal": { + "patterns": [ + { + "name": "markup.italic.org", + "match": "^:.+" + } + ] + }, + "code": { + "patterns": [ + { + "name": "variable.org", + "match": "(^|\\s)\\~[^\\s](.*?[^\\s])?\\~($|\\W)" + } + ] + }, + "verbatim": { + "patterns": [ + { + "name": "variable.org", + "match": "(^|\\s)\\=[^\\s](.*?[^\\s])?\\=($|\\W)" + } + ] + }, + "comment": { + "patterns": [ + { + "match": "(^|\\s)(#)\\s(.*)$", + "captures": { + "2": { + "name": "punctuation.definition.comment" + }, + "3": { + "name": "comment.line" + } + } + } + ] + }, + "keywords": { + "patterns": [ + { + "match": "^(#\\+\\w+:)\\s(.*)$", + "captures": { + "1": { + "name": "support.type.property-name.org" + }, + "2": { + "name": "meta.structure.dictionary.value.org" + } + } + } + ] + } + }, + "scopeName": "source.org" +} diff --git a/orgize-lsp/editors/vscode/tsconfig.json b/orgize-lsp/editors/vscode/tsconfig.json new file mode 100644 index 0000000..43dfa0b --- /dev/null +++ b/orgize-lsp/editors/vscode/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2019", + "lib": ["ES2019"], + "outDir": "../dist", + "rootDir": "src", + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/orgize-lsp/justfile b/orgize-lsp/justfile new file mode 100644 index 0000000..b169757 --- /dev/null +++ b/orgize-lsp/justfile @@ -0,0 +1,8 @@ +a: c s + +c: + pnpm run -C client-vscode package --no-dependencies + pnpm run -C client-vscode install --no-dependencies + +s: + cargo install --path ./server --offline diff --git a/orgize-lsp/src/code_lens.rs b/orgize-lsp/src/code_lens.rs new file mode 100644 index 0000000..9d1045c --- /dev/null +++ b/orgize-lsp/src/code_lens.rs @@ -0,0 +1,104 @@ +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::AstNode, +}; +use orgize_common::{header_argument, property_drawer, property_keyword}; +use tower_lsp::lsp_types::{CodeLens, Url}; + +use crate::org_document::OrgDocument; + +use super::OrgizeCommand; + +pub struct CodeLensTraverser<'a> { + pub url: Url, + pub doc: &'a OrgDocument, + pub lens: Vec, +} + +impl<'a> Traverser for CodeLensTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::SourceBlock(block)) => { + let start = block.begin(); + + let arg1 = block.parameters().unwrap_or_default(); + let arg2 = property_drawer(block.syntax()).unwrap_or_default(); + let arg3 = property_keyword(block.syntax()).unwrap_or_default(); + + let range = self.doc.range_of(start, start); + + let tangle = header_argument(&arg1, &arg2, &arg3, ":tangle", "no"); + + if header_argument(&arg1, &arg2, &arg3, ":results", "no") != "no" { + self.lens.push(CodeLens { + range, + command: Some( + OrgizeCommand::SrcBlockExecute { + block_offset: start, + url: self.url.clone(), + } + .into(), + ), + data: None, + }); + } + + if tangle != "no" { + self.lens.push(CodeLens { + range, + command: Some( + OrgizeCommand::SrcBlockTangle { + block_offset: start, + url: self.url.clone(), + } + .into(), + ), + data: None, + }); + + self.lens.push(CodeLens { + range, + command: Some( + OrgizeCommand::SrcBlockDetangle { + block_offset: start, + url: self.url.clone(), + } + .into(), + ), + data: None, + }); + } + + ctx.skip(); + } + Event::Enter(Container::Headline(headline)) => { + if headline.tags().any(|t| t.eq_ignore_ascii_case("TOC")) { + let start = headline.begin(); + + self.lens.push(CodeLens { + range: self.doc.range_of(start, start), + command: Some( + OrgizeCommand::HeadlineToc { + heading_offset: start, + url: self.url.clone(), + } + .into(), + ), + data: None, + }); + } + } + _ => {} + } + } +} + +impl<'a> CodeLensTraverser<'a> { + pub fn new(url: Url, doc: &'a OrgDocument) -> Self { + CodeLensTraverser { + url, + lens: vec![], + doc, + } + } +} diff --git a/orgize-lsp/src/commands/headline_toc.rs b/orgize-lsp/src/commands/headline_toc.rs new file mode 100644 index 0000000..a2de377 --- /dev/null +++ b/orgize-lsp/src/commands/headline_toc.rs @@ -0,0 +1,101 @@ +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::AstNode, + SyntaxKind, +}; +use std::collections::HashMap; +use std::fmt::Write; +use tower_lsp::lsp_types::{TextEdit, Url, WorkspaceEdit}; + +use crate::Backend; + +impl Backend { + pub async fn headline_toc(&self, url: Url, headline_offset: u32) { + let uri = url.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return; + }; + + let mut toc = Toc { + indent: 0, + output: String::new(), + + headline_offset, + edit_range: None, + }; + + doc.traverse(&mut toc); + + if let Some((start, end)) = toc.edit_range { + let mut changes = HashMap::new(); + + let range = doc.range_of(start, end); + + changes.insert( + url, + vec![TextEdit { + new_text: toc.output, + range, + }], + ); + + let _ = self + .client + .apply_edit(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }) + .await; + } + } +} + +pub struct Toc { + output: String, + indent: usize, + + headline_offset: u32, + + edit_range: Option<(u32, u32)>, +} + +impl Traverser for Toc { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::Headline(headline)) => { + if headline.begin() == self.headline_offset { + let start = headline + .syntax() + .children_with_tokens() + .find(|n| n.kind() == SyntaxKind::NEW_LINE) + .map(|n| n.text_range().end().into()) + .unwrap_or(headline.end()); + + let end = headline.end(); + + self.edit_range = Some((start, end)); + } else { + let title = headline.title().map(|e| e.to_string()).collect::(); + + let slug = orgize_common::headline_slug(&headline); + + let _ = writeln!( + &mut self.output, + "{: >i$}- [[#{slug}][{title}]]", + "", + i = self.indent + ); + } + + self.indent += 2; + } + Event::Leave(Container::Headline(_)) => self.indent -= 2, + + Event::Enter(Container::Section(_)) => ctx.skip(), + Event::Enter(Container::Document(_)) => self.output += "#+begin_quote\n", + Event::Leave(Container::Document(_)) => self.output += "#+end_quote\n\n", + _ => {} + } + } +} diff --git a/orgize-lsp/src/commands/mod.rs b/orgize-lsp/src/commands/mod.rs new file mode 100644 index 0000000..60b55a0 --- /dev/null +++ b/orgize-lsp/src/commands/mod.rs @@ -0,0 +1,116 @@ +mod headline_toc; +mod src_block_detangle; +mod src_block_execute; +mod src_block_tangle; + +use orgize::rowan::ast::AstNode; +use serde_json::{json, Value}; +use tower_lsp::lsp_types::{Command, ExecuteCommandParams, MessageType, Url}; + +use crate::Backend; + +pub enum OrgizeCommand { + SrcBlockExecute { url: Url, block_offset: u32 }, + + SrcBlockTangle { url: Url, block_offset: u32 }, + + SrcBlockDetangle { url: Url, block_offset: u32 }, + + HeadlineToc { url: Url, heading_offset: u32 }, +} + +impl From for Command { + fn from(val: OrgizeCommand) -> Self { + match val { + OrgizeCommand::SrcBlockExecute { url, block_offset } => Command { + command: "orgize.src-block.execute".into(), + arguments: Some(vec![json!(url), json!(block_offset)]), + title: "Execute".into(), + }, + OrgizeCommand::SrcBlockTangle { url, block_offset } => Command { + command: "orgize.src-block.tangle".into(), + arguments: Some(vec![json!(url), json!(block_offset)]), + title: "Tangle".into(), + }, + OrgizeCommand::SrcBlockDetangle { url, block_offset } => Command { + command: "orgize.src-block.detangle".into(), + arguments: Some(vec![json!(url), json!(block_offset)]), + title: "Detangle".into(), + }, + OrgizeCommand::HeadlineToc { + url, + heading_offset, + } => Command { + command: "orgize.headline.toc".into(), + arguments: Some(vec![json!(url), json!(heading_offset)]), + title: "Generate TOC".into(), + }, + } + } +} + +impl OrgizeCommand { + pub fn all() -> Vec { + vec![ + "orgize.src-block.execute".into(), + "orgize.src-block.tangle".into(), + "orgize.src-block.detangle".into(), + "orgize.src-block.open-tangle-dest".into(), + "orgize.headline.toc".into(), + ] + } +} + +pub async fn execute(params: &ExecuteCommandParams, backend: &Backend) -> Option { + let result = match ( + params.command.as_str(), + params.arguments.get(0).and_then(|x| x.as_str()), + params.arguments.get(1).and_then(|x| x.as_u64()), + ) { + ("orgize.src-block.execute", Some(s), Some(n)) => backend + .src_block_execute(s.parse().ok()?, n as u32) + .await + .map(|_| None), + ("orgize.src-block.tangle", Some(s), Some(n)) => backend + .src_block_tangle(s.parse().ok()?, n as u32) + .await + .map(|_| None), + ("orgize.src-block.detangle", Some(s), Some(n)) => backend + .src_block_detangle(s.parse().ok()?, n as u32) + .await + .map(|_| None), + ("orgize.headline.toc", Some(s), Some(n)) => { + backend.headline_toc(s.parse().ok()?, n as u32).await; + Ok(None) + } + ("orgize.syntax-tree", Some(s), _) => { + if let Some(doc) = backend.documents.get(s) { + Ok(Some(json!(format!("{:#?}", doc.org.document().syntax())))) + } else { + Ok(None) + } + } + ("orgize.preview-html", Some(s), _) => { + if let Some(doc) = backend.documents.get(s) { + Ok(Some(json!(format!("{}", doc.org.to_html())))) + } else { + Ok(None) + } + } + _ => Ok(None), + }; + + match result { + Ok(value) => value, + Err(err) => { + backend + .client + .show_message( + MessageType::ERROR, + format!("Failed to execute {:?}: {}", params.command, err), + ) + .await; + None + } + } +} diff --git a/orgize-lsp/src/commands/src_block_detangle.rs b/orgize-lsp/src/commands/src_block_detangle.rs new file mode 100644 index 0000000..2a49811 --- /dev/null +++ b/orgize-lsp/src/commands/src_block_detangle.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use orgize::{ast::SourceBlock, rowan::ast::AstNode}; +use tower_lsp::lsp_types::{MessageType, TextEdit, Url, WorkspaceEdit}; + +use crate::Backend; + +impl Backend { + pub async fn src_block_detangle(&self, url: Url, block_offset: u32) -> anyhow::Result<()> { + let uri = url.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(()); + }; + + let Some(block) = doc + .org + .document() + .syntax() + .descendants() + .filter_map(SourceBlock::cast) + .find(|n| n.begin() == block_offset) + else { + return Ok(()); + }; + + let Ok(file_path) = url.to_file_path() else { + return Ok(()); + }; + + if let Some((start, end, new_text)) = orgize_common::detangle(block, &file_path)? { + let mut changes = HashMap::new(); + + let range = doc.range_of(start as u32, end as u32); + + changes.insert(url, vec![TextEdit { new_text, range }]); + + let _ = self + .client + .apply_edit(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }) + .await; + } else { + self.client + .show_message(MessageType::WARNING, "Code block can't be detangled.") + .await; + } + + Ok(()) + } +} diff --git a/orgize-lsp/src/commands/src_block_execute.rs b/orgize-lsp/src/commands/src_block_execute.rs new file mode 100644 index 0000000..b6b2106 --- /dev/null +++ b/orgize-lsp/src/commands/src_block_execute.rs @@ -0,0 +1,51 @@ +use std::collections::HashMap; + +use orgize::{ast::SourceBlock, rowan::ast::AstNode}; +use tower_lsp::lsp_types::{MessageType, TextEdit, Url, WorkspaceEdit}; + +use crate::Backend; + +impl Backend { + pub async fn src_block_execute(&self, url: Url, block_offset: u32) -> anyhow::Result<()> { + let uri = url.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(()); + }; + + let Some(block) = doc + .org + .document() + .syntax() + .descendants() + .filter_map(SourceBlock::cast) + .find(|n| n.begin() == block_offset) + else { + return Ok(()); + }; + + let dir = tempfile::tempdir().unwrap(); + + if let Some((start, end, new_text)) = orgize_common::execute(block, dir.path())? { + let mut changes = HashMap::new(); + + let range = doc.range_of(start as u32, end as u32); + + changes.insert(url, vec![TextEdit { new_text, range }]); + + let _ = self + .client + .apply_edit(WorkspaceEdit { + changes: Some(changes), + ..Default::default() + }) + .await; + } else { + self.client + .show_message(MessageType::WARNING, "Code block can't be executed.") + .await; + } + + Ok(()) + } +} diff --git a/orgize-lsp/src/commands/src_block_tangle.rs b/orgize-lsp/src/commands/src_block_tangle.rs new file mode 100644 index 0000000..ea286a9 --- /dev/null +++ b/orgize-lsp/src/commands/src_block_tangle.rs @@ -0,0 +1,49 @@ +use orgize::{ast::SourceBlock, rowan::ast::AstNode}; +use std::fs; +use tower_lsp::lsp_types::{MessageType, Url}; + +use crate::Backend; + +impl Backend { + pub async fn src_block_tangle(&self, url: Url, block_offset: u32) -> anyhow::Result<()> { + let uri = url.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(()); + }; + + let Some(block) = doc + .org + .document() + .syntax() + .descendants() + .filter_map(SourceBlock::cast) + .find(|n| n.begin() == block_offset) + else { + return Ok(()); + }; + + let Ok(file_path) = url.to_file_path() else { + return Ok(()); + }; + + if let Some((dest, _permission, contents, _mkdir)) = + orgize_common::tangle(block, &file_path)? + { + fs::write(&dest, contents)?; + + self.client + .show_message( + MessageType::INFO, + format!("Wrote to {}", dest.to_string_lossy()), + ) + .await; + } else { + self.client + .show_message(MessageType::WARNING, "Code block can't be tangled.") + .await; + } + + Ok(()) + } +} diff --git a/orgize-lsp/src/document_link.rs b/orgize-lsp/src/document_link.rs new file mode 100644 index 0000000..e5d7cec --- /dev/null +++ b/orgize-lsp/src/document_link.rs @@ -0,0 +1,129 @@ +use orgize::{ + ast::{Link, SourceBlock}, + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::{support, AstNode}, + SyntaxKind, +}; +use orgize_common::header_argument; +use resolve_path::PathResolveExt; +use serde_json::json; +use std::path::PathBuf; +use tower_lsp::lsp_types::{DocumentLink, Url}; + +use crate::org_document::OrgDocument; + +pub struct DocumentLinkTraverser<'a> { + pub doc: &'a OrgDocument, + pub links: Vec, + pub path: Option, +} + +impl<'a> Traverser for DocumentLinkTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + let Some(base) = &self.path else { + return ctx.skip(); + }; + + match event { + Event::Enter(Container::Link(link)) => { + if let Some(link) = link_path(link, base, self.doc) { + self.links.push(link); + } + ctx.skip(); + } + Event::Enter(Container::SourceBlock(block)) => { + if let Some(link) = block_tangle(block, base, self.doc) { + self.links.push(link); + } + ctx.skip(); + } + + _ => {} + } + } +} + +fn link_path(link: Link, base: &PathBuf, doc: &OrgDocument) -> Option { + let path = support::token(link.syntax(), SyntaxKind::LINK_PATH) + .or_else(|| support::token(link.syntax(), SyntaxKind::TEXT))?; + + let path_str = path.text(); + + let (target, data) = if let Some(file) = path_str.strip_prefix("file:") { + let path = file.try_resolve_in(base).ok()?; + (Some(Url::from_file_path(path).ok()?), None) + } else if path_str.starts_with('/') || path_str.starts_with("./") || path_str.starts_with("~/") + { + let path = path_str.try_resolve_in(base).ok()?; + (Some(Url::from_file_path(path).ok()?), None) + } else if path_str.starts_with("http://") || path_str.starts_with("https://") { + (Some(Url::parse(path_str).ok()?), None) + } else if let Some(id) = path_str.strip_prefix('#') { + let url = Url::from_file_path(base).ok()?; + ( + None, + Some(json!(vec![ + "headline-id".to_string(), + url.to_string(), + id.to_string() + ])), + ) + } else { + return None; + }; + + Some(DocumentLink { + range: doc.range_of( + path.text_range().start().into(), + path.text_range().end().into(), + ), + tooltip: Some("Jump to link".into()), + target, + data, + }) +} + +fn block_tangle(block: SourceBlock, base: &PathBuf, doc: &OrgDocument) -> Option { + let parameters = block + .syntax() + .children() + .find(|e| e.kind() == SyntaxKind::BLOCK_BEGIN) + .into_iter() + .flat_map(|n| n.children_with_tokens()) + .filter_map(|n| n.into_token()) + .find(|n| n.kind() == SyntaxKind::SRC_BLOCK_PARAMETERS)?; + + let tangle = header_argument(parameters.text(), "", "", ":tangle", "no"); + + if tangle == "no" { + return None; + } + + let path = tangle.try_resolve_in(base).ok()?; + let url = Url::from_file_path(path).ok()?; + + let start: u32 = parameters.text_range().start().into(); + + let index = parameters.text().find(tangle).unwrap_or_default() as u32; + + let len = tangle.len() as u32; + + Some(DocumentLink { + range: doc.range_of(start + index, start + index + len), + tooltip: Some("Jump to tangle destination".into()), + target: Some(url), + data: None, + }) +} + +impl<'a> DocumentLinkTraverser<'a> { + pub fn new(doc: &'a OrgDocument, path: Option) -> Self { + DocumentLinkTraverser { + path, + links: vec![], + doc, + } + } +} + +pub fn resolve() {} diff --git a/orgize-lsp/src/document_link_resolve.rs b/orgize-lsp/src/document_link_resolve.rs new file mode 100644 index 0000000..abb8f67 --- /dev/null +++ b/orgize-lsp/src/document_link_resolve.rs @@ -0,0 +1,81 @@ +use orgize::export::{Container, Event, TraversalContext, Traverser}; +use tower_lsp::lsp_types::{DocumentLink, Url}; + +use crate::{org_document::OrgDocument, Backend}; + +pub fn document_link_resolve( + document_link: &DocumentLink, + backend: &Backend, +) -> Option { + // don't need to resolve + if document_link.target.is_some() { + return None; + } + + let data = document_link.data.as_ref()?; + let data = data.as_array()?; + + match ( + data.get(0)?.as_str()?, + data.get(1)?.as_str()?, + data.get(2)?.as_str()?, + ) { + ("headline-id", url, id) => { + let mut parsed = Url::parse(url).ok()?; + + let doc = backend.documents.get(url)?; + let mut h = HeadlineIdTraverser { + id: id.to_string(), + line_number: None, + doc: &doc, + }; + doc.traverse(&mut h); + if let Some(line) = h.line_number.take() { + // results is zero-based + parsed.set_fragment(Some(&(line + 1).to_string())); + Some(DocumentLink { + target: Some(parsed), + data: None, + tooltip: None, + range: document_link.range, + }) + } else { + Some(DocumentLink { + target: Some(parsed), + data: None, + tooltip: None, + range: document_link.range, + }) + } + } + _ => None, + } +} + +struct HeadlineIdTraverser<'a> { + id: String, + line_number: Option, + doc: &'a OrgDocument, +} + +impl<'a> Traverser for HeadlineIdTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + if self.line_number.is_some() { + return ctx.stop(); + } + + match event { + Event::Enter(Container::Document(_)) => {} + Event::Enter(Container::Headline(headline)) => { + let slug = orgize_common::headline_slug(&headline); + + if slug == self.id { + let line = self.doc.line_of(headline.begin()); + self.line_number = Some(line + 1) + } + } + Event::Enter(Container::Section(_)) => ctx.skip(), + _ => {} + } + } +} diff --git a/orgize-lsp/src/document_symbol.rs b/orgize-lsp/src/document_symbol.rs new file mode 100644 index 0000000..4eff90e --- /dev/null +++ b/orgize-lsp/src/document_symbol.rs @@ -0,0 +1,66 @@ +#![allow(deprecated)] + +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::AstNode, + SyntaxKind, +}; +use tower_lsp::lsp_types::{DocumentSymbol, SymbolKind}; + +use crate::org_document::OrgDocument; + +pub struct DocumentSymbolTraverser<'a> { + pub doc: &'a OrgDocument, + pub stack: Vec, + pub symbols: Vec, +} + +impl<'a> Traverser for DocumentSymbolTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::Headline(headline)) => { + let mut symbols = &mut self.symbols; + for &i in &self.stack { + symbols = symbols[i].children.get_or_insert(vec![]); + } + + let name = headline + .syntax() + .children_with_tokens() + .take_while(|n| n.kind() != SyntaxKind::NEW_LINE) + .map(|n| n.to_string()) + .collect::(); + + let start = headline.begin(); + let end = headline.end() - 1; + + self.stack.push(symbols.len()); + symbols.push(DocumentSymbol { + children: None, + name, + detail: None, + kind: SymbolKind::STRING, + tags: Some(vec![]), + range: self.doc.range_of(start, end), + selection_range: self.doc.range_of(start, end), + deprecated: None, + }); + } + Event::Leave(Container::Headline(_)) => { + self.stack.pop(); + } + Event::Enter(Container::Section(_)) => ctx.skip(), + _ => {} + } + } +} + +impl<'a> DocumentSymbolTraverser<'a> { + pub fn new(doc: &'a OrgDocument) -> Self { + DocumentSymbolTraverser { + doc, + stack: vec![], + symbols: vec![], + } + } +} diff --git a/orgize-lsp/src/folding_range.rs b/orgize-lsp/src/folding_range.rs new file mode 100644 index 0000000..217ae04 --- /dev/null +++ b/orgize-lsp/src/folding_range.rs @@ -0,0 +1,94 @@ +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + rowan::ast::AstNode, + SyntaxKind, SyntaxNode, +}; +use tower_lsp::lsp_types::{FoldingRange, FoldingRangeKind}; + +use crate::org_document::OrgDocument; + +pub struct FoldingRangeTraverser<'a> { + pub doc: &'a OrgDocument, + pub ranges: Vec, +} + +impl<'a> Traverser for FoldingRangeTraverser<'a> { + fn event(&mut self, event: Event, _: &mut TraversalContext) { + let syntax = match &event { + Event::Enter(Container::Headline(i)) => i.syntax(), + Event::Enter(Container::OrgTable(i)) => i.syntax(), + Event::Enter(Container::TableEl(i)) => i.syntax(), + Event::Enter(Container::List(i)) => i.syntax(), + Event::Enter(Container::Drawer(i)) => i.syntax(), + Event::Enter(Container::DynBlock(i)) => i.syntax(), + Event::Enter(Container::SpecialBlock(i)) => i.syntax(), + Event::Enter(Container::QuoteBlock(i)) => i.syntax(), + Event::Enter(Container::CenterBlock(i)) => i.syntax(), + Event::Enter(Container::VerseBlock(i)) => i.syntax(), + Event::Enter(Container::CommentBlock(i)) => i.syntax(), + Event::Enter(Container::ExampleBlock(i)) => i.syntax(), + Event::Enter(Container::ExportBlock(i)) => i.syntax(), + Event::Enter(Container::SourceBlock(i)) => i.syntax(), + _ => return, + }; + + let (start, end) = if syntax.kind() == SyntaxKind::HEADLINE { + let range = syntax.text_range(); + (range.start().into(), range.end().into()) + } else { + get_block_folding_range(syntax) + }; + + let start_line = self.doc.line_of(start); + let end_line = self.doc.line_of(end - 1); + + if start_line != end_line { + self.ranges.push(FoldingRange { + start_line, + end_line, + kind: Some(FoldingRangeKind::Region), + ..Default::default() + }); + } + } +} + +fn get_block_folding_range(syntax: &SyntaxNode) -> (u32, u32) { + let start: u32 = syntax.text_range().start().into(); + + // don't include blank lines in folding range + let end = syntax + .children() + .take_while(|n| n.kind() != SyntaxKind::BLANK_LINE) + .last(); + + let end: u32 = end.map(|n| n.text_range().end().into()).unwrap_or(start); + + (start, end) +} + +impl<'a> FoldingRangeTraverser<'a> { + pub fn new(doc: &'a OrgDocument) -> Self { + FoldingRangeTraverser { + ranges: vec![], + doc, + } + } +} + +#[test] +fn test() { + let doc = OrgDocument::new("\n* a\n\n* b\n\n"); + let mut t = FoldingRangeTraverser::new(&doc); + doc.traverse(&mut t); + assert_eq!(t.ranges[0].start_line, 1); + assert_eq!(t.ranges[0].end_line, 2); + assert_eq!(t.ranges[1].start_line, 3); + assert_eq!(t.ranges[1].end_line, 4); + + let doc = OrgDocument::new("\n\r\n#+begin_src\n#+end_src\n\r\r"); + let mut t = FoldingRangeTraverser::new(&doc); + doc.traverse(&mut t); + assert_eq!(t.ranges[0].start_line, 2); + assert_eq!(t.ranges[0].end_line, 3); +} diff --git a/orgize-lsp/src/formatting.rs b/orgize-lsp/src/formatting.rs new file mode 100644 index 0000000..af836c6 --- /dev/null +++ b/orgize-lsp/src/formatting.rs @@ -0,0 +1,13 @@ +use tower_lsp::lsp_types::TextEdit; + +use crate::org_document::OrgDocument; + +pub fn formatting(doc: &OrgDocument) -> Vec { + orgize_common::formatting(&doc.org) + .into_iter() + .map(|(start, end, content)| TextEdit { + range: doc.range_of(start as u32, end as u32), + new_text: content, + }) + .collect::>() +} diff --git a/orgize-lsp/src/main.rs b/orgize-lsp/src/main.rs new file mode 100644 index 0000000..deecff7 --- /dev/null +++ b/orgize-lsp/src/main.rs @@ -0,0 +1,285 @@ +mod code_lens; +mod commands; +mod document_link; +mod document_link_resolve; +mod document_symbol; +mod folding_range; +mod formatting; +mod org_document; +mod semantic_token; + +use dashmap::DashMap; +use document_symbol::DocumentSymbolTraverser; +use org_document::OrgDocument; +use serde_json::Value; +use tower_lsp::lsp_types::*; +use tower_lsp::{jsonrpc::Result, Client, LanguageServer, LspService, Server}; + +pub use self::code_lens::*; +pub use self::commands::*; +pub use self::document_link::*; +pub use self::folding_range::*; +pub use self::semantic_token::*; + +pub struct Backend { + client: Client, + + documents: DashMap, +} + +#[tower_lsp::async_trait] +impl LanguageServer for Backend { + async fn initialize(&self, _: InitializeParams) -> Result { + Ok(InitializeResult { + server_info: None, + offset_encoding: None, + capabilities: ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::INCREMENTAL, + )), + execute_command_provider: Some(ExecuteCommandOptions { + commands: OrgizeCommand::all(), + work_done_progress_options: Default::default(), + }), + workspace: Some(WorkspaceServerCapabilities { + workspace_folders: Some(WorkspaceFoldersServerCapabilities { + supported: Some(true), + change_notifications: Some(OneOf::Left(true)), + }), + file_operations: None, + }), + semantic_tokens_provider: Some( + SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions( + SemanticTokensRegistrationOptions { + text_document_registration_options: { + TextDocumentRegistrationOptions { + document_selector: Some(vec![DocumentFilter { + language: Some("org".to_string()), + scheme: Some("file".to_string()), + pattern: None, + }]), + } + }, + semantic_tokens_options: SemanticTokensOptions { + work_done_progress_options: WorkDoneProgressOptions::default(), + legend: SemanticTokensLegend { + token_types: LEGEND_TYPE.into(), + token_modifiers: vec![], + }, + range: Some(true), + full: Some(SemanticTokensFullOptions::Bool(true)), + }, + static_registration_options: StaticRegistrationOptions::default(), + }, + ), + ), + code_lens_provider: Some(CodeLensOptions { + resolve_provider: Some(true), + }), + folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), + document_link_provider: Some(DocumentLinkOptions { + resolve_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions::default(), + }), + document_formatting_provider: Some(OneOf::Left(true)), + document_symbol_provider: Some(OneOf::Left(true)), + ..ServerCapabilities::default() + }, + }) + } + + async fn initialized(&self, _: InitializedParams) { + self.client + .log_message(MessageType::INFO, "Orgize LSP initialized") + .await; + } + + async fn shutdown(&self) -> Result<()> { + self.client + .log_message(MessageType::INFO, "Orgize LSP shutdown") + .await; + Ok(()) + } + + async fn did_open(&self, params: DidOpenTextDocumentParams) { + let url = params.text_document.uri.to_string(); + + self.documents + .insert(url.clone(), OrgDocument::new(params.text_document.text)); + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + let url = params.text_document.uri.to_string(); + + for change in params.content_changes { + if let (Some(mut doc), Some(range)) = (self.documents.get_mut(&url), change.range) { + let start = doc.offset_of(range.start); + let end = doc.offset_of(range.end); + doc.update(start, end, &change.text); + } else { + self.documents + .insert(url.clone(), OrgDocument::new(change.text)); + } + } + } + + async fn did_save(&self, _: DidSaveTextDocumentParams) {} + + async fn did_close(&self, _: DidCloseTextDocumentParams) {} + + async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {} + + async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {} + + async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {} + + async fn completion(&self, _: CompletionParams) -> Result> { + Ok(None) + } + + async fn semantic_tokens_full( + &self, + params: SemanticTokensParams, + ) -> Result> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut traverser = SemanticTokenTraverser::new(&doc); + + doc.traverse(&mut traverser); + + Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { + result_id: None, + data: traverser.tokens, + }))) + } + + async fn semantic_tokens_range( + &self, + params: SemanticTokensRangeParams, + ) -> Result> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut traverser = SemanticTokenTraverser::with_range(&doc, params.range); + + doc.traverse(&mut traverser); + + Ok(Some(SemanticTokensRangeResult::Partial( + SemanticTokensPartialResult { + data: traverser.tokens, + }, + ))) + } + + async fn document_link(&self, params: DocumentLinkParams) -> Result>> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut traverser = + DocumentLinkTraverser::new(&doc, params.text_document.uri.to_file_path().ok()); + + doc.traverse(&mut traverser); + + Ok(Some(traverser.links)) + } + + async fn document_link_resolve(&self, params: DocumentLink) -> Result { + if let Some(link) = document_link_resolve::document_link_resolve(¶ms, self) { + Ok(link) + } else { + Ok(params) + } + } + + async fn folding_range(&self, params: FoldingRangeParams) -> Result>> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut traverser = FoldingRangeTraverser::new(&doc); + + doc.traverse(&mut traverser); + + Ok(Some(traverser.ranges)) + } + + async fn code_lens(&self, params: CodeLensParams) -> Result>> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut traverser = CodeLensTraverser::new(params.text_document.uri, &doc); + + doc.traverse(&mut traverser); + + Ok(Some(traverser.lens)) + } + + async fn code_lens_resolve(&self, params: CodeLens) -> Result { + Ok(params) + } + + async fn code_action(&self, _: CodeActionParams) -> Result> { + Ok(None) + } + + async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let edits = formatting::formatting(&doc); + Ok(Some(edits)) + } + + async fn execute_command(&self, params: ExecuteCommandParams) -> Result> { + let value = commands::execute(¶ms, self).await; + Ok(value) + } + + async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result> { + let uri = params.text_document.uri.to_string(); + + let Some(doc) = self.documents.get(&uri) else { + return Ok(None); + }; + + let mut t = DocumentSymbolTraverser::new(&doc); + doc.traverse(&mut t); + dbg!(&t.symbols); + Ok(Some(DocumentSymbolResponse::Nested(t.symbols))) + } +} + +#[tokio::main] +async fn main() { + let stdin = tokio::io::stdin(); + let stdout = tokio::io::stdout(); + + let (service, socket) = LspService::build(|client| Backend { + client, + documents: DashMap::new(), + }) + .finish(); + + Server::new(stdin, stdout, socket).serve(service).await; +} diff --git a/orgize-lsp/src/org_document.rs b/orgize-lsp/src/org_document.rs new file mode 100644 index 0000000..e063e6b --- /dev/null +++ b/orgize-lsp/src/org_document.rs @@ -0,0 +1,128 @@ +use orgize::{export::Traverser, Org}; +use std::iter::once; +use tower_lsp::lsp_types::{Position, Range}; + +pub struct OrgDocument { + pub text: String, + pub line_starts: Vec, + pub org: Org, +} + +impl OrgDocument { + pub fn new(text: impl AsRef) -> Self { + let text = text.as_ref().to_string(); + + OrgDocument { + org: Org::parse(&text), + line_starts: line_starts(&text), + text, + } + } + + pub fn update(&mut self, start: u32, end: u32, text: &str) { + self.text + .replace_range((start as usize)..(end as usize), text); + + self.line_starts = line_starts(&self.text); + + self.org = Org::parse(&self.text); + } + + pub fn position_of(&self, offset: u32) -> Position { + let line = self + .line_starts + .binary_search(&offset) + .unwrap_or_else(|i| i - 1); + + let line_start = self.line_starts[line]; + + let character = self.text.as_str()[(line_start as usize)..(offset as usize)] + .chars() + .count(); + + Position::new(line as u32, character as u32) + } + + pub fn line_of(&self, offset: u32) -> u32 { + self.line_starts + .binary_search(&offset) + .unwrap_or_else(|i| i - 1) as u32 + } + + pub fn range_of(&self, start_offset: u32, end_offset: u32) -> Range { + Range::new(self.position_of(start_offset), self.position_of(end_offset)) + } + + pub fn offset_of(&self, position: Position) -> u32 { + let line_start = self.line_starts[position.line as usize] as usize; + + let index = self.text.as_str()[line_start..] + .char_indices() + .nth(position.character as usize) + .map(|(i, _)| i) + .unwrap_or_default(); + + (line_start + index) as u32 + } + + pub fn traverse(&self, h: &mut H) { + self.org.traverse(h); + } +} + +fn line_starts(text: &str) -> Vec { + let bytes = text.as_bytes(); + + once(0) + .chain( + memchr::memchr2_iter(b'\r', b'\n', bytes) + .filter(|&i| bytes[i] == b'\n' || !matches!(bytes.get(i + 1), Some(b'\n'))) + .map(|i| (i + 1) as u32), + ) + .collect() +} + +#[test] +fn test() { + let doc = OrgDocument::new( + r#"* toc :toc: + +fsfs +fasdfs + + + +fasdfs + +*a* _a_ /1/ ~default~ =default= a_a + +# abc + +* abc12121 +12121 + + +#+begin_src javascript +console.log(a); +#+end_src + +"#, + ); + + let start = 12; + let start_position = Position { + line: 1, + character: 0, + }; + let end = 81; + let end_position = Position { + line: 13, + character: 0, + }; + + assert_eq!(doc.position_of(start), start_position); + assert_eq!(doc.position_of(end), end_position); + + assert_eq!(doc.offset_of(start_position), start); + assert_eq!(doc.offset_of(end_position), end); +} diff --git a/orgize-lsp/src/semantic_token.rs b/orgize-lsp/src/semantic_token.rs new file mode 100644 index 0000000..de75fdd --- /dev/null +++ b/orgize-lsp/src/semantic_token.rs @@ -0,0 +1,123 @@ +use orgize::{ + export::{Container, Event, TraversalContext, Traverser}, + rowan::{ast::AstNode, TextRange}, +}; +use tower_lsp::lsp_types::{Range, SemanticToken, SemanticTokenType}; + +use crate::org_document::OrgDocument; + +/// Semantic token types that are used for highlighting +pub const LEGEND_TYPE: &[SemanticTokenType] = &[SemanticTokenType::COMMENT]; + +pub struct SemanticTokenTraverser<'a> { + pub doc: &'a OrgDocument, + + pub range: Option, + + pub tokens: Vec, + pub previous_line: u32, + pub previous_start: u32, +} + +impl<'a> Traverser for SemanticTokenTraverser<'a> { + fn event(&mut self, event: Event, ctx: &mut TraversalContext) { + match event { + Event::Enter(Container::Comment(comment)) => { + let range = comment.syntax().text_range(); + + if self.contains_range(range) { + if let Some(token) = self.create_token( + comment.begin(), + comment.end(), + SemanticTokenType::COMMENT, + ) { + self.tokens.push(token); + } + } + + ctx.skip(); + } + Event::Enter(Container::CommentBlock(comment)) => { + let range = comment.syntax().text_range(); + + if self.contains_range(range) { + if let Some(token) = self.create_token( + comment.begin(), + comment.end(), + SemanticTokenType::COMMENT, + ) { + self.tokens.push(token); + } + } + + ctx.skip(); + } + + _ => {} + } + } +} + +impl<'a> SemanticTokenTraverser<'a> { + pub fn new(doc: &'a OrgDocument) -> Self { + SemanticTokenTraverser { + doc, + range: None, + previous_line: 0, + previous_start: 0, + tokens: vec![], + } + } + + pub fn with_range(doc: &'a OrgDocument, range: Range) -> Self { + let start = doc.offset_of(range.start); + let end = doc.offset_of(range.end); + + SemanticTokenTraverser { + doc, + range: Some(TextRange::new(start.into(), end.into())), + previous_line: 0, + previous_start: 0, + tokens: vec![], + } + } + + fn contains_range(&self, range: TextRange) -> bool { + match self.range { + Some(r) => r.contains_range(range), + None => true, + } + } + + fn create_token( + &mut self, + start: u32, + end: u32, + kind: SemanticTokenType, + ) -> Option { + let length = end - start; + let token_type = LEGEND_TYPE.iter().position(|item| item == &kind)? as u32; + + let line = self.doc.line_of(start); + let first = self.doc.line_of(line); + let start = self.doc.line_of(start) - first; + + let delta_line = line - self.previous_line; + let delta_start = if delta_line == 0 { + start - self.previous_start + } else { + start + }; + + self.previous_line = line; + self.previous_start = start; + + Some(SemanticToken { + delta_line, + delta_start, + length, + token_type, + token_modifiers_bitset: 0, + }) + } +} diff --git a/wasm/.gitignore b/orgize-wasm/.gitignore similarity index 100% rename from wasm/.gitignore rename to orgize-wasm/.gitignore diff --git a/orgize-wasm/Cargo.toml b/orgize-wasm/Cargo.toml new file mode 100644 index 0000000..27a78ec --- /dev/null +++ b/orgize-wasm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "orgize-wasm" +publish = false +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +orgize = { path = "../orgize" } +wasm-bindgen = "0.2" diff --git a/wasm/README.md b/orgize-wasm/README.md similarity index 100% rename from wasm/README.md rename to orgize-wasm/README.md diff --git a/wasm/build.rs b/orgize-wasm/build.rs similarity index 100% rename from wasm/build.rs rename to orgize-wasm/build.rs diff --git a/wasm/index.html b/orgize-wasm/index.html similarity index 100% rename from wasm/index.html rename to orgize-wasm/index.html diff --git a/wasm/package.json b/orgize-wasm/package.json similarity index 100% rename from wasm/package.json rename to orgize-wasm/package.json diff --git a/wasm/src/lib.rs b/orgize-wasm/src/lib.rs similarity index 100% rename from wasm/src/lib.rs rename to orgize-wasm/src/lib.rs diff --git a/orgize/Cargo.toml b/orgize/Cargo.toml new file mode 100644 index 0000000..40b2a0a --- /dev/null +++ b/orgize/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "orgize" +version.workspace = true +authors.workspace = true +description = "A Rust library for parsing org-mode files." +repository.workspace = true +readme = "README.md" +edition.workspace = true +license.workspace = true +keywords = ["orgmode", "org-mode", "emacs", "parser"] +exclude = ["/wasm", "/.github"] + +[package.metadata.docs.rs] +all-features = true + +[features] +default = [] +indexmap = ["dep:indexmap"] +chrono = ["dep:chrono"] + +[dependencies] +bytecount = "0.6" +chrono = { version = "0.4", optional = true } +indexmap = { version = "2.1", optional = true } +jetscii = "0.5" +memchr = "2.5" +nom = { version = "7.1", default-features = false, features = ["std"] } +rowan = "0.15" +tracing = "0.1" + +[dev-dependencies] +criterion = "0.5" +insta = "1.29" +slugify = "0.1" +tracing-subscriber = { version = "0.3", features = ["fmt"] } + +[[bench]] +name = "parse" +harness = false diff --git a/orgize/README.md b/orgize/README.md new file mode 100644 index 0000000..5e7988d --- /dev/null +++ b/orgize/README.md @@ -0,0 +1,71 @@ +# Orgize + +[![Crates.io](https://img.shields.io/crates/v/orgize.svg)](https://crates.io/crates/orgize) +[![Documentation](https://docs.rs/orgize/badge.svg)](https://docs.rs/orgize) +[![Build status](https://img.shields.io/github/actions/workflow/status/PoiScript/orgize/ci.yml)](https://github.com/PoiScript/orgize/actions/workflows/ci.yml) +![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) + +A Rust library for parsing org-mode files. + +Live Demo: + +## Parse + +To parse a org-mode string, simply invoking the `Org::parse` function: + +```rust +use orgize::{Org, rowan::ast::AstNode}; + +let org = Org::parse("* DONE Title :tag:"); +assert_eq!( + format!("{:#?}", org.document().syntax()), + r#"DOCUMENT@0..18 + HEADLINE@0..18 + HEADLINE_STARS@0..1 "*" + WHITESPACE@1..2 " " + HEADLINE_KEYWORD_DONE@2..6 "DONE" + WHITESPACE@6..7 " " + HEADLINE_TITLE@7..13 + TEXT@7..13 "Title " + HEADLINE_TAGS@13..18 + COLON@13..14 ":" + TEXT@14..17 "tag" + COLON@17..18 ":" +"#); +``` + +use `ParseConfig::parse` to specific a custom parse config + +```rust +use orgize::{Org, ParseConfig, ast::Headline}; + +let config = ParseConfig { + // custom todo keywords + todo_keywords: (vec!["TASK".to_string()], vec![]), + ..Default::default() +}; +let org = config.parse("* TASK Title 1"); +let hdl = org.first_node::().unwrap(); +assert_eq!(hdl.todo_keyword().unwrap(), "TASK"); +``` + +## Render to html + +Call the `Org::to_html` function to export org element tree to html: + +```rust +use orgize::Org; + +assert_eq!( + Org::parse("* title\n*section*").to_html(), + "

title

section

" +); +``` + +Checkout `examples/html-slugify.rs` on how to customizing html export process. + +## Features + +- **`chrono`**: adds the ability to convert `Timestamp` into `chrono::NaiveDateTime`, disabled by default. + +- **`indexmap`**: adds the ability to convert `PropertyDrawer` properties into `IndexMap`, disabled by default. diff --git a/orgize/benches/.gitignore b/orgize/benches/.gitignore new file mode 100644 index 0000000..448d1fb --- /dev/null +++ b/orgize/benches/.gitignore @@ -0,0 +1 @@ +*.org \ No newline at end of file diff --git a/benches/parse.rs b/orgize/benches/parse.rs similarity index 100% rename from benches/parse.rs rename to orgize/benches/parse.rs diff --git a/examples/html-slugify.rs b/orgize/examples/html-slugify.rs similarity index 100% rename from examples/html-slugify.rs rename to orgize/examples/html-slugify.rs diff --git a/examples/parse.rs b/orgize/examples/parse.rs similarity index 100% rename from examples/parse.rs rename to orgize/examples/parse.rs diff --git a/fuzz/.gitignore b/orgize/fuzz/.gitignore similarity index 100% rename from fuzz/.gitignore rename to orgize/fuzz/.gitignore diff --git a/fuzz/Cargo.toml b/orgize/fuzz/Cargo.toml similarity index 100% rename from fuzz/Cargo.toml rename to orgize/fuzz/Cargo.toml diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/orgize/fuzz/fuzz_targets/fuzz_target_1.rs similarity index 100% rename from fuzz/fuzz_targets/fuzz_target_1.rs rename to orgize/fuzz/fuzz_targets/fuzz_target_1.rs diff --git a/src/ast/affiliated_keyword.rs b/orgize/src/ast/affiliated_keyword.rs similarity index 100% rename from src/ast/affiliated_keyword.rs rename to orgize/src/ast/affiliated_keyword.rs diff --git a/src/ast/block.rs b/orgize/src/ast/block.rs similarity index 100% rename from src/ast/block.rs rename to orgize/src/ast/block.rs diff --git a/src/ast/clock.rs b/orgize/src/ast/clock.rs similarity index 100% rename from src/ast/clock.rs rename to orgize/src/ast/clock.rs diff --git a/src/ast/comment.rs b/orgize/src/ast/comment.rs similarity index 100% rename from src/ast/comment.rs rename to orgize/src/ast/comment.rs diff --git a/src/ast/drawer.rs b/orgize/src/ast/drawer.rs similarity index 100% rename from src/ast/drawer.rs rename to orgize/src/ast/drawer.rs diff --git a/src/ast/entity.rs b/orgize/src/ast/entity.rs similarity index 100% rename from src/ast/entity.rs rename to orgize/src/ast/entity.rs diff --git a/src/ast/fixed_width.rs b/orgize/src/ast/fixed_width.rs similarity index 100% rename from src/ast/fixed_width.rs rename to orgize/src/ast/fixed_width.rs diff --git a/src/ast/generate.js b/orgize/src/ast/generate.js similarity index 100% rename from src/ast/generate.js rename to orgize/src/ast/generate.js diff --git a/src/ast/generated.rs b/orgize/src/ast/generated.rs similarity index 100% rename from src/ast/generated.rs rename to orgize/src/ast/generated.rs diff --git a/src/ast/headline.rs b/orgize/src/ast/headline.rs similarity index 100% rename from src/ast/headline.rs rename to orgize/src/ast/headline.rs diff --git a/src/ast/inline_call.rs b/orgize/src/ast/inline_call.rs similarity index 100% rename from src/ast/inline_call.rs rename to orgize/src/ast/inline_call.rs diff --git a/src/ast/inline_src.rs b/orgize/src/ast/inline_src.rs similarity index 100% rename from src/ast/inline_src.rs rename to orgize/src/ast/inline_src.rs diff --git a/src/ast/keyword.rs b/orgize/src/ast/keyword.rs similarity index 100% rename from src/ast/keyword.rs rename to orgize/src/ast/keyword.rs diff --git a/src/ast/link.rs b/orgize/src/ast/link.rs similarity index 100% rename from src/ast/link.rs rename to orgize/src/ast/link.rs diff --git a/src/ast/list.rs b/orgize/src/ast/list.rs similarity index 100% rename from src/ast/list.rs rename to orgize/src/ast/list.rs diff --git a/src/ast/macros.rs b/orgize/src/ast/macros.rs similarity index 100% rename from src/ast/macros.rs rename to orgize/src/ast/macros.rs diff --git a/src/ast/mod.rs b/orgize/src/ast/mod.rs similarity index 100% rename from src/ast/mod.rs rename to orgize/src/ast/mod.rs diff --git a/src/ast/planning.rs b/orgize/src/ast/planning.rs similarity index 100% rename from src/ast/planning.rs rename to orgize/src/ast/planning.rs diff --git a/src/ast/snippet.rs b/orgize/src/ast/snippet.rs similarity index 100% rename from src/ast/snippet.rs rename to orgize/src/ast/snippet.rs diff --git a/src/ast/table.rs b/orgize/src/ast/table.rs similarity index 100% rename from src/ast/table.rs rename to orgize/src/ast/table.rs diff --git a/src/ast/timestamp.rs b/orgize/src/ast/timestamp.rs similarity index 100% rename from src/ast/timestamp.rs rename to orgize/src/ast/timestamp.rs diff --git a/src/config.rs b/orgize/src/config.rs similarity index 100% rename from src/config.rs rename to orgize/src/config.rs diff --git a/src/entities.rs b/orgize/src/entities.rs similarity index 100% rename from src/entities.rs rename to orgize/src/entities.rs diff --git a/src/export/event.rs b/orgize/src/export/event.rs similarity index 100% rename from src/export/event.rs rename to orgize/src/export/event.rs diff --git a/src/export/html.rs b/orgize/src/export/html.rs similarity index 100% rename from src/export/html.rs rename to orgize/src/export/html.rs diff --git a/src/export/mod.rs b/orgize/src/export/mod.rs similarity index 100% rename from src/export/mod.rs rename to orgize/src/export/mod.rs diff --git a/src/export/traverse.rs b/orgize/src/export/traverse.rs similarity index 100% rename from src/export/traverse.rs rename to orgize/src/export/traverse.rs diff --git a/src/lib.rs b/orgize/src/lib.rs similarity index 100% rename from src/lib.rs rename to orgize/src/lib.rs diff --git a/src/org.rs b/orgize/src/org.rs similarity index 100% rename from src/org.rs rename to orgize/src/org.rs diff --git a/src/syntax/block.rs b/orgize/src/syntax/block.rs similarity index 100% rename from src/syntax/block.rs rename to orgize/src/syntax/block.rs diff --git a/src/syntax/clock.rs b/orgize/src/syntax/clock.rs similarity index 100% rename from src/syntax/clock.rs rename to orgize/src/syntax/clock.rs diff --git a/src/syntax/combinator.rs b/orgize/src/syntax/combinator.rs similarity index 100% rename from src/syntax/combinator.rs rename to orgize/src/syntax/combinator.rs diff --git a/src/syntax/comment.rs b/orgize/src/syntax/comment.rs similarity index 100% rename from src/syntax/comment.rs rename to orgize/src/syntax/comment.rs diff --git a/src/syntax/cookie.rs b/orgize/src/syntax/cookie.rs similarity index 100% rename from src/syntax/cookie.rs rename to orgize/src/syntax/cookie.rs diff --git a/src/syntax/document.rs b/orgize/src/syntax/document.rs similarity index 100% rename from src/syntax/document.rs rename to orgize/src/syntax/document.rs diff --git a/src/syntax/drawer.rs b/orgize/src/syntax/drawer.rs similarity index 100% rename from src/syntax/drawer.rs rename to orgize/src/syntax/drawer.rs diff --git a/src/syntax/dyn_block.rs b/orgize/src/syntax/dyn_block.rs similarity index 100% rename from src/syntax/dyn_block.rs rename to orgize/src/syntax/dyn_block.rs diff --git a/src/syntax/element.rs b/orgize/src/syntax/element.rs similarity index 100% rename from src/syntax/element.rs rename to orgize/src/syntax/element.rs diff --git a/src/syntax/emphasis.rs b/orgize/src/syntax/emphasis.rs similarity index 100% rename from src/syntax/emphasis.rs rename to orgize/src/syntax/emphasis.rs diff --git a/src/syntax/entity.rs b/orgize/src/syntax/entity.rs similarity index 100% rename from src/syntax/entity.rs rename to orgize/src/syntax/entity.rs diff --git a/src/syntax/fixed_width.rs b/orgize/src/syntax/fixed_width.rs similarity index 100% rename from src/syntax/fixed_width.rs rename to orgize/src/syntax/fixed_width.rs diff --git a/src/syntax/fn_def.rs b/orgize/src/syntax/fn_def.rs similarity index 100% rename from src/syntax/fn_def.rs rename to orgize/src/syntax/fn_def.rs diff --git a/src/syntax/fn_ref.rs b/orgize/src/syntax/fn_ref.rs similarity index 100% rename from src/syntax/fn_ref.rs rename to orgize/src/syntax/fn_ref.rs diff --git a/src/syntax/headline.rs b/orgize/src/syntax/headline.rs similarity index 100% rename from src/syntax/headline.rs rename to orgize/src/syntax/headline.rs diff --git a/src/syntax/inline_call.rs b/orgize/src/syntax/inline_call.rs similarity index 100% rename from src/syntax/inline_call.rs rename to orgize/src/syntax/inline_call.rs diff --git a/src/syntax/inline_src.rs b/orgize/src/syntax/inline_src.rs similarity index 100% rename from src/syntax/inline_src.rs rename to orgize/src/syntax/inline_src.rs diff --git a/src/syntax/input.rs b/orgize/src/syntax/input.rs similarity index 100% rename from src/syntax/input.rs rename to orgize/src/syntax/input.rs diff --git a/src/syntax/keyword.rs b/orgize/src/syntax/keyword.rs similarity index 100% rename from src/syntax/keyword.rs rename to orgize/src/syntax/keyword.rs diff --git a/src/syntax/latex_environment.rs b/orgize/src/syntax/latex_environment.rs similarity index 100% rename from src/syntax/latex_environment.rs rename to orgize/src/syntax/latex_environment.rs diff --git a/src/syntax/latex_fragment.rs b/orgize/src/syntax/latex_fragment.rs similarity index 100% rename from src/syntax/latex_fragment.rs rename to orgize/src/syntax/latex_fragment.rs diff --git a/src/syntax/line_break.rs b/orgize/src/syntax/line_break.rs similarity index 100% rename from src/syntax/line_break.rs rename to orgize/src/syntax/line_break.rs diff --git a/src/syntax/link.rs b/orgize/src/syntax/link.rs similarity index 100% rename from src/syntax/link.rs rename to orgize/src/syntax/link.rs diff --git a/src/syntax/list.rs b/orgize/src/syntax/list.rs similarity index 100% rename from src/syntax/list.rs rename to orgize/src/syntax/list.rs diff --git a/src/syntax/macros.rs b/orgize/src/syntax/macros.rs similarity index 100% rename from src/syntax/macros.rs rename to orgize/src/syntax/macros.rs diff --git a/src/syntax/mod.rs b/orgize/src/syntax/mod.rs similarity index 100% rename from src/syntax/mod.rs rename to orgize/src/syntax/mod.rs diff --git a/src/syntax/object.rs b/orgize/src/syntax/object.rs similarity index 100% rename from src/syntax/object.rs rename to orgize/src/syntax/object.rs diff --git a/src/syntax/paragraph.rs b/orgize/src/syntax/paragraph.rs similarity index 100% rename from src/syntax/paragraph.rs rename to orgize/src/syntax/paragraph.rs diff --git a/src/syntax/planning.rs b/orgize/src/syntax/planning.rs similarity index 100% rename from src/syntax/planning.rs rename to orgize/src/syntax/planning.rs diff --git a/src/syntax/radio_target.rs b/orgize/src/syntax/radio_target.rs similarity index 100% rename from src/syntax/radio_target.rs rename to orgize/src/syntax/radio_target.rs diff --git a/src/syntax/rule.rs b/orgize/src/syntax/rule.rs similarity index 100% rename from src/syntax/rule.rs rename to orgize/src/syntax/rule.rs diff --git a/src/syntax/snippet.rs b/orgize/src/syntax/snippet.rs similarity index 100% rename from src/syntax/snippet.rs rename to orgize/src/syntax/snippet.rs diff --git a/src/syntax/subscript_superscript.rs b/orgize/src/syntax/subscript_superscript.rs similarity index 100% rename from src/syntax/subscript_superscript.rs rename to orgize/src/syntax/subscript_superscript.rs diff --git a/src/syntax/table.rs b/orgize/src/syntax/table.rs similarity index 100% rename from src/syntax/table.rs rename to orgize/src/syntax/table.rs diff --git a/src/syntax/target.rs b/orgize/src/syntax/target.rs similarity index 100% rename from src/syntax/target.rs rename to orgize/src/syntax/target.rs diff --git a/src/syntax/timestamp.rs b/orgize/src/syntax/timestamp.rs similarity index 100% rename from src/syntax/timestamp.rs rename to orgize/src/syntax/timestamp.rs diff --git a/src/tests.rs b/orgize/src/tests.rs similarity index 100% rename from src/tests.rs rename to orgize/src/tests.rs diff --git a/tests/html.rs b/orgize/tests/html.rs similarity index 100% rename from tests/html.rs rename to orgize/tests/html.rs diff --git a/tests/parse.rs b/orgize/tests/parse.rs similarity index 100% rename from tests/parse.rs rename to orgize/tests/parse.rs diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml deleted file mode 100644 index 97d0a01..0000000 --- a/wasm/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "orgize-wasm" -version = "0.1.0" -publish = false -edition = "2018" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -orgize = { path = ".." } -wasm-bindgen = "0.2"