Auto generate docs for language support

This commit is contained in:
Gokul Soumya 2021-11-22 00:25:08 +05:30 committed by Blaž Hrastnik
parent 71292f9f11
commit a78b789406
11 changed files with 311 additions and 22 deletions

2
Cargo.lock generated
View file

@ -1264,5 +1264,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
name = "xtask"
version = "0.5.0"
dependencies = [
"helix-core",
"helix-term",
"toml",
]

View file

@ -4,6 +4,7 @@
- [Usage](./usage.md)
- [Keymap](./keymap.md)
- [Commands](./commands.md)
- [Language Support](./lang-support.md)
- [Migrating from Vim](./from-vim.md)
- [Configuration](./configuration.md)
- [Themes](./themes.md)

View file

@ -0,0 +1,41 @@
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
| --- | --- | --- | --- | --- |
| Bash | ✓ | | | `bash-language-server` |
| C | ✓ | | | `clangd` |
| C# | ✓ | | | |
| CMake | ✓ | | | `cmake-language-server` |
| C++ | ✓ | | | `clangd` |
| CSS | ✓ | | | |
| Elixir | ✓ | | | `elixir-ls` |
| GLSL | ✓ | | ✓ | |
| Go | ✓ | ✓ | ✓ | `gopls` |
| HTML | ✓ | | | |
| Java | ✓ | | | |
| JavaScript | ✓ | | ✓ | |
| JSON | ✓ | | ✓ | |
| Julia | ✓ | | | `julia` |
| LaTeX | ✓ | | | |
| Ledger | ✓ | | | |
| LLVM | ✓ | | | |
| Lua | ✓ | | ✓ | |
| Mint | | | | `mint` |
| Nix | ✓ | | ✓ | `rnix-lsp` |
| OCaml | ✓ | | ✓ | |
| OCaml-Interface | ✓ | | | |
| Perl | ✓ | ✓ | | |
| PHP | ✓ | | ✓ | |
| Prolog | | | | `swipl` |
| Protobuf | ✓ | | ✓ | |
| Python | ✓ | ✓ | ✓ | `pylsp` |
| Racket | | | | `racket` |
| Ruby | ✓ | | | `solargraph` |
| Rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| Svelte | ✓ | | ✓ | `svelteserver` |
| TOML | ✓ | | | |
| TSQ | ✓ | | | |
| TSX | ✓ | | | `typescript-language-server` |
| TypeScript | ✓ | | ✓ | `typescript-language-server` |
| Vue | ✓ | | | |
| WGSL | ✓ | | | |
| YAML | ✓ | | ✓ | |
| Zig | ✓ | | ✓ | `zls` |

View file

@ -1,5 +1,5 @@
| Name | Description |
| --- | --- |
| --- | --- |
| `:quit`, `:q` | Close the current view. |
| `:quit!`, `:q!` | Close the current view forcefully (ignoring unsaved changes). |
| `:open`, `:o` | Open a file from disk into the current view. |

10
book/src/lang-support.md Normal file
View file

@ -0,0 +1,10 @@
# Language Support
For more information like arguments passed to default LSP server,
extensions assosciated with a filetype, custom LSP settings, filetype
specific indent settings, etc see the default
[`languages.toml`][languages.toml] file.
{{#include ./generated/lang-support.md}}
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml

View file

@ -23,7 +23,7 @@ like the list of `:commands` and supported languages. To generate these
files, run
```shell
cargo xtask bookgen
cargo xtask docgen
```
inside the project. We use [xtask][xtask] as an ad-hoc task runner and

View file

@ -452,6 +452,7 @@ where
file_types: vec!["rs".to_string()],
shebangs: vec![],
language_id: "Rust".to_string(),
display_name: "Rust".to_string(),
highlight_config: OnceCell::new(),
config: None,
//

View file

@ -50,7 +50,8 @@ pub struct Configuration {
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct LanguageConfiguration {
#[serde(rename = "name")]
pub language_id: String,
pub language_id: String, // c-sharp, rust
pub display_name: String, // C#, Rust
pub scope: String, // source.rust
pub file_types: Vec<String>, // filename ends_with? <Gemfile, rb, etc>
#[serde(default)]

View file

@ -1,5 +1,6 @@
[[language]]
name = "rust"
display-name = "Rust"
scope = "source.rust"
injection-regex = "rust"
file-types = ["rs"]
@ -14,6 +15,7 @@ procMacro = { enable = false }
[[language]]
name = "toml"
display-name = "TOML"
scope = "source.toml"
injection-regex = "toml"
file-types = ["toml"]
@ -24,6 +26,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "protobuf"
display-name = "Protobuf"
scope = "source.proto"
injection-regex = "protobuf"
file-types = ["proto"]
@ -34,6 +37,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "elixir"
display-name = "Elixir"
scope = "source.elixir"
injection-regex = "elixir"
file-types = ["ex", "exs"]
@ -46,6 +50,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "mint"
display-name = "Mint"
scope = "source.mint"
injection-regex = "mint"
file-types = ["mint"]
@ -58,6 +63,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "json"
display-name = "JSON"
scope = "source.json"
injection-regex = "json"
file-types = ["json"]
@ -67,6 +73,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "c"
display-name = "C"
scope = "source.c"
injection-regex = "c"
file-types = ["c"] # TODO: ["h"]
@ -78,6 +85,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "cpp"
display-name = "C++"
scope = "source.cpp"
injection-regex = "cpp"
file-types = ["cc", "hh", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino"]
@ -89,6 +97,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "c-sharp"
display-name = "C#"
scope = "source.csharp"
injection-regex = "c-?sharp"
file-types = ["cs"]
@ -99,6 +108,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "go"
display-name = "Go"
scope = "source.go"
injection-regex = "go"
file-types = ["go"]
@ -112,6 +122,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "javascript"
display-name = "JavaScript"
scope = "source.js"
injection-regex = "^(js|javascript)$"
file-types = ["js", "mjs"]
@ -124,6 +135,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "typescript"
display-name = "TypeScript"
scope = "source.ts"
injection-regex = "^(ts|typescript)$"
file-types = ["ts"]
@ -136,6 +148,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "tsx"
display-name = "TSX"
scope = "source.tsx"
injection-regex = "^(tsx)$" # |typescript
file-types = ["tsx"]
@ -147,6 +160,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "css"
display-name = "CSS"
scope = "source.css"
injection-regex = "css"
file-types = ["css"]
@ -156,6 +170,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "html"
display-name = "HTML"
scope = "text.html.basic"
injection-regex = "html"
file-types = ["html"]
@ -165,6 +180,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "python"
display-name = "Python"
scope = "source.python"
injection-regex = "python"
file-types = ["py"]
@ -178,6 +194,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "nix"
display-name = "Nix"
scope = "source.nix"
injection-regex = "nix"
file-types = ["nix"]
@ -190,6 +207,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "ruby"
display-name = "Ruby"
scope = "source.ruby"
injection-regex = "ruby"
file-types = ["rb"]
@ -202,6 +220,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "bash"
display-name = "Bash"
scope = "source.bash"
injection-regex = "bash"
file-types = ["sh", "bash"]
@ -214,6 +233,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "php"
display-name = "PHP"
scope = "source.php"
injection-regex = "php"
file-types = ["php"]
@ -224,6 +244,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "latex"
display-name = "LaTeX"
scope = "source.tex"
injection-regex = "tex"
file-types = ["tex"]
@ -234,6 +255,7 @@ indent = { tab-width = 4, unit = "\t" }
[[language]]
name = "julia"
display-name = "Julia"
scope = "source.julia"
injection-regex = "julia"
file-types = ["jl"]
@ -259,6 +281,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "java"
display-name = "Java"
scope = "source.java"
injection-regex = "java"
file-types = ["java"]
@ -267,6 +290,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "ledger"
display-name = "Ledger"
scope = "source.ledger"
injection-regex = "ledger"
file-types = ["ldg", "ledger", "journal"]
@ -276,6 +300,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "ocaml"
display-name = "OCaml"
scope = "source.ocaml"
injection-regex = "ocaml"
file-types = ["ml"]
@ -286,6 +311,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "ocaml-interface"
display-name = "OCaml-Interface"
scope = "source.ocaml.interface"
file-types = ["mli"]
shebangs = []
@ -295,6 +321,7 @@ indent = { tab-width = 2, unit = " "}
[[language]]
name = "lua"
display-name = "Lua"
scope = "source.lua"
file-types = ["lua"]
shebangs = ["lua"]
@ -304,6 +331,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "svelte"
display-name = "Svelte"
scope = "source.svelte"
injection-regex = "svelte"
file-types = ["svelte"]
@ -314,6 +342,7 @@ language-server = { command = "svelteserver", args = ["--stdio"] }
[[language]]
name = "vue"
display-name = "Vue"
scope = "source.vue"
injection-regex = "vue"
file-types = ["vue"]
@ -322,6 +351,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "yaml"
display-name = "YAML"
scope = "source.yaml"
file-types = ["yml", "yaml"]
roots = []
@ -330,6 +360,7 @@ indent = { tab-width = 2, unit = " " }
# [[language]]
# name = "haskell"
# display-name = "Haskell"
# scope = "source.haskell"
# injection-regex = "haskell"
# file-types = ["hs"]
@ -340,6 +371,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "zig"
display-name = "Zig"
scope = "source.zig"
injection-regex = "zig"
file-types = ["zig"]
@ -352,6 +384,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "prolog"
display-name = "Prolog"
scope = "source.prolog"
roots = []
file-types = ["pl", "prolog"]
@ -365,6 +398,7 @@ language-server = { command = "swipl", args = [
[[language]]
name = "tsq"
display-name = "TSQ"
scope = "source.tsq"
file-types = ["scm"]
roots = []
@ -373,6 +407,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "cmake"
display-name = "CMake"
scope = "source.cmake"
file-types = ["cmake", "CMakeLists.txt"]
roots = []
@ -382,6 +417,7 @@ language-server = { command = "cmake-language-server" }
[[language]]
name = "glsl"
display-name = "GLSL"
scope = "source.glsl"
file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
roots = []
@ -390,6 +426,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "perl"
display-name = "Perl"
scope = "source.perl"
file-types = ["pl", "pm"]
shebangs = ["perl"]
@ -399,6 +436,7 @@ indent = { tab-width = 2, unit = " " }
[[language]]
name = "racket"
display-name = "Racket"
scope = "source.rkt"
roots = []
file-types = ["rkt"]
@ -408,6 +446,7 @@ language-server = { command = "racket", args = ["-l", "racket-langserver"] }
[[language]]
name = "wgsl"
display-name = "WGSL"
scope = "source.wgsl"
file-types = ["wgsl"]
roots = []
@ -416,6 +455,7 @@ indent = { tab-width = 4, unit = " " }
[[language]]
name = "llvm"
display-name = "LLVM"
scope = "source.llvm"
roots = []
file-types = ["ll"]

View file

@ -7,3 +7,5 @@ edition = "2021"
[dependencies]
helix-term = { version = "0.5", path = "../helix-term" }
helix-core = { version = "0.5", path = "../helix-core" }
toml = "0.5"

View file

@ -1,17 +1,138 @@
use std::env;
use std::{env, error::Error};
type DynError = Box<dyn Error>;
pub mod helpers {
use std::{
fmt::Display,
path::{Path, PathBuf},
};
use crate::path;
use helix_core::syntax::Configuration as LangConfig;
#[derive(Copy, Clone)]
pub enum TsFeature {
Highlight,
TextObjects,
AutoIndent,
}
impl TsFeature {
pub fn all() -> &'static [Self] {
&[Self::Highlight, Self::TextObjects, Self::AutoIndent]
}
pub fn runtime_filename(&self) -> &'static str {
match *self {
Self::Highlight => "highlights.scm",
Self::TextObjects => "textobjects.scm",
Self::AutoIndent => "indents.toml",
}
}
}
impl Display for TsFeature {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match *self {
Self::Highlight => "Syntax Highlighting",
Self::TextObjects => "Treesitter Textobjects",
Self::AutoIndent => "Auto Indent",
}
)
}
}
/// Get the list of languages that support a particular tree-sitter
/// based feature.
pub fn ts_lang_support(feat: TsFeature) -> Vec<String> {
let queries_dir = path::ts_queries();
find_files(&queries_dir, feat.runtime_filename())
.iter()
.map(|f| {
// .../helix/runtime/queries/python/highlights.scm
let tail = f.strip_prefix(&queries_dir).unwrap(); // python/highlights.scm
let lang = tail.components().next().unwrap(); // python
lang.as_os_str().to_string_lossy().to_string()
})
.collect()
}
/// Get the list of languages that have any form of tree-sitter
/// queries defined in the runtime directory.
pub fn langs_with_ts_queries() -> Vec<String> {
std::fs::read_dir(path::ts_queries())
.unwrap()
.filter_map(|entry| {
let entry = entry.ok()?;
entry
.file_type()
.ok()?
.is_dir()
.then(|| entry.file_name().to_string_lossy().to_string())
})
.collect()
}
// naive implementation, but suffices for our needs
pub fn find_files(dir: &Path, filename: &str) -> Vec<PathBuf> {
std::fs::read_dir(dir)
.unwrap()
.filter_map(|entry| {
let path = entry.ok()?.path();
if path.is_dir() {
Some(find_files(&path, filename))
} else {
(path.file_name()?.to_string_lossy() == filename).then(|| vec![path])
}
})
.flatten()
.collect()
}
pub fn lang_config() -> LangConfig {
let bytes = std::fs::read(path::lang_config()).unwrap();
toml::from_slice(&bytes).unwrap()
}
}
pub mod md_gen {
use super::path;
use crate::DynError;
use crate::helpers;
use crate::path;
use std::fs;
use helix_term::commands::cmd::TYPABLE_COMMAND_LIST;
pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
pub fn typable_commands() -> String {
fn md_table_heading(cols: &[String]) -> String {
let mut header = String::new();
header += &md_table_row(cols);
header += &md_table_row(&vec!["---".to_string(); cols.len()]);
header
}
fn md_table_row(cols: &[String]) -> String {
"| ".to_owned() + &cols.join(" | ") + " |\n"
}
fn md_mono(s: &str) -> String {
format!("`{}`", s)
}
pub fn typable_commands() -> Result<String, DynError> {
let mut md = String::new();
md.push_str("| Name | Description |\n");
md.push_str("| --- | --- |\n");
md.push_str(&md_table_heading(&[
"Name".to_owned(),
"Description".to_owned(),
]));
let cmdify = |s: &str| format!("`:{}`", s);
@ -22,11 +143,72 @@ pub mod md_gen {
.collect::<Vec<_>>()
.join(", ");
let entry = format!("| {} | {} |\n", names, cmd.doc);
md.push_str(&entry);
md.push_str(&md_table_row(&[names.to_owned(), cmd.doc.to_owned()]));
}
md
Ok(md)
}
pub fn lang_features() -> Result<String, DynError> {
let mut md = String::new();
let ts_features = helpers::TsFeature::all();
let mut cols = vec!["Language".to_owned()];
cols.append(
&mut ts_features
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>(),
);
cols.push("Default LSP".to_owned());
md.push_str(&md_table_heading(&cols));
let config = helpers::lang_config();
let mut langs = config
.language
.iter()
.map(|l| l.language_id.clone())
.collect::<Vec<_>>();
langs.sort_unstable();
let mut ts_features_to_langs = Vec::new();
for &feat in ts_features {
ts_features_to_langs.push((feat, helpers::ts_lang_support(feat)));
}
let mut row = Vec::new();
for lang in langs {
let lc = config
.language
.iter()
.find(|l| l.language_id == lang)
.unwrap(); // lang comes from config
row.push(lc.display_name.clone());
for (_feat, support_list) in &ts_features_to_langs {
row.push(
if support_list.contains(&lang) {
""
} else {
""
}
.to_owned(),
);
}
row.push(
lc.language_server
.as_ref()
.map(|s| s.command.clone())
.map(|c| md_mono(&c))
.unwrap_or_default(),
);
md.push_str(&md_table_row(&row));
row.clear();
}
Ok(md)
}
pub fn write(filename: &str, data: &str) {
@ -49,37 +231,46 @@ pub mod path {
pub fn book_gen() -> PathBuf {
project_root().join("book/src/generated/")
}
pub fn ts_queries() -> PathBuf {
project_root().join("runtime/queries")
}
pub fn lang_config() -> PathBuf {
project_root().join("languages.toml")
}
}
pub mod tasks {
use super::md_gen;
use crate::md_gen;
use crate::DynError;
pub fn bookgen() {
md_gen::write(
md_gen::TYPABLE_COMMANDS_MD_OUTPUT,
&md_gen::typable_commands(),
);
pub fn docgen() -> Result<(), DynError> {
use md_gen::*;
write(TYPABLE_COMMANDS_MD_OUTPUT, &typable_commands()?);
write(LANG_SUPPORT_MD_OUTPUT, &lang_features()?);
Ok(())
}
pub fn print_help() {
println!(
"
Usage: Run with `cargo xtask <task>`, eg. `cargo xtask bookgen`.
Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
Tasks:
bookgen: Generate files to be included in the mdbook output.
docgen: Generate files to be included in the mdbook output.
"
);
}
}
fn main() -> Result<(), String> {
fn main() -> Result<(), DynError> {
let task = env::args().nth(1);
match task {
None => tasks::print_help(),
Some(t) => match t.as_str() {
"bookgen" => tasks::bookgen(),
invalid => return Err(format!("Invalid task name: {}", invalid)),
"docgen" => tasks::docgen()?,
invalid => return Err(format!("Invalid task name: {}", invalid).into()),
},
};
Ok(())