mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Add --health command for troubleshooting (#1669)
* Move runtime file location definitions to core * Add basic --health command * Add language specific --health * Show summary for all langs with bare --health * Use TsFeature from xtask for --health * cargo fmt Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>
This commit is contained in:
parent
f31e85aca4
commit
194b09fbc1
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -457,6 +457,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"toml",
|
"toml",
|
||||||
|
"which",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -124,6 +124,18 @@ pub fn cache_dir() -> std::path::PathBuf {
|
|||||||
path
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn config_file() -> std::path::PathBuf {
|
||||||
|
config_dir().join("config.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lang_config_file() -> std::path::PathBuf {
|
||||||
|
config_dir().join("languages.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_file() -> std::path::PathBuf {
|
||||||
|
cache_dir().join("helix.log")
|
||||||
|
}
|
||||||
|
|
||||||
// right overrides left
|
// right overrides left
|
||||||
pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
|
pub fn merge_toml_values(left: toml::Value, right: toml::Value) -> toml::Value {
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
|
@ -335,7 +335,7 @@ pub fn capture_nodes_any<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
|
pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
|
||||||
let path = crate::RUNTIME_DIR
|
let path = crate::RUNTIME_DIR
|
||||||
.join("queries")
|
.join("queries")
|
||||||
.join(language)
|
.join(language)
|
||||||
|
@ -30,6 +30,8 @@ helix-dap = { version = "0.6", path = "../helix-dap" }
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
once_cell = "1.10"
|
once_cell = "1.10"
|
||||||
|
|
||||||
|
which = "4.2"
|
||||||
|
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||||
|
@ -66,7 +66,6 @@ pub fn new(args: Args, mut config: Config) -> Result<Self, Error> {
|
|||||||
let theme_loader =
|
let theme_loader =
|
||||||
std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir()));
|
std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_core::runtime_dir()));
|
||||||
|
|
||||||
// load default and user config, and merge both
|
|
||||||
let true_color = config.editor.true_color || crate::true_color();
|
let true_color = config.editor.true_color || crate::true_color();
|
||||||
let theme = config
|
let theme = config
|
||||||
.theme
|
.theme
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use anyhow::{Error, Result};
|
use anyhow::Result;
|
||||||
use helix_core::Position;
|
use helix_core::Position;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -6,6 +6,8 @@
|
|||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub display_help: bool,
|
pub display_help: bool,
|
||||||
pub display_version: bool,
|
pub display_version: bool,
|
||||||
|
pub health: bool,
|
||||||
|
pub health_arg: Option<String>,
|
||||||
pub load_tutor: bool,
|
pub load_tutor: bool,
|
||||||
pub verbosity: u64,
|
pub verbosity: u64,
|
||||||
pub files: Vec<(PathBuf, Position)>,
|
pub files: Vec<(PathBuf, Position)>,
|
||||||
@ -14,22 +16,22 @@ pub struct Args {
|
|||||||
impl Args {
|
impl Args {
|
||||||
pub fn parse_args() -> Result<Args> {
|
pub fn parse_args() -> Result<Args> {
|
||||||
let mut args = Args::default();
|
let mut args = Args::default();
|
||||||
let argv: Vec<String> = std::env::args().collect();
|
let mut argv = std::env::args().peekable();
|
||||||
let mut iter = argv.iter();
|
|
||||||
|
|
||||||
iter.next(); // skip the program, we don't care about that
|
argv.next(); // skip the program, we don't care about that
|
||||||
|
|
||||||
for arg in &mut iter {
|
while let Some(arg) = argv.next() {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"--" => break, // stop parsing at this point treat the remaining as files
|
"--" => break, // stop parsing at this point treat the remaining as files
|
||||||
"--version" => args.display_version = true,
|
"--version" => args.display_version = true,
|
||||||
"--help" => args.display_help = true,
|
"--help" => args.display_help = true,
|
||||||
"--tutor" => args.load_tutor = true,
|
"--tutor" => args.load_tutor = true,
|
||||||
|
"--health" => {
|
||||||
|
args.health = true;
|
||||||
|
args.health_arg = argv.next_if(|opt| !opt.starts_with('-'));
|
||||||
|
}
|
||||||
arg if arg.starts_with("--") => {
|
arg if arg.starts_with("--") => {
|
||||||
return Err(Error::msg(format!(
|
anyhow::bail!("unexpected double dash argument: {}", arg)
|
||||||
"unexpected double dash argument: {}",
|
|
||||||
arg
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
arg if arg.starts_with('-') => {
|
arg if arg.starts_with('-') => {
|
||||||
let arg = arg.get(1..).unwrap().chars();
|
let arg = arg.get(1..).unwrap().chars();
|
||||||
@ -38,7 +40,7 @@ pub fn parse_args() -> Result<Args> {
|
|||||||
'v' => args.verbosity += 1,
|
'v' => args.verbosity += 1,
|
||||||
'V' => args.display_version = true,
|
'V' => args.display_version = true,
|
||||||
'h' => args.display_help = true,
|
'h' => args.display_help = true,
|
||||||
_ => return Err(Error::msg(format!("unexpected short arg {}", chr))),
|
_ => anyhow::bail!("unexpected short arg {}", chr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,8 +49,8 @@ pub fn parse_args() -> Result<Args> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// push the remaining args, if any to the files
|
// push the remaining args, if any to the files
|
||||||
for arg in iter {
|
for arg in argv {
|
||||||
args.files.push(parse_file(arg));
|
args.files.push(parse_file(&arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(args)
|
Ok(args)
|
||||||
|
221
helix-term/src/health.rs
Normal file
221
helix-term/src/health.rs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
use crossterm::style::{Color, Print, Stylize};
|
||||||
|
use helix_core::{
|
||||||
|
config::{default_syntax_loader, user_syntax_loader},
|
||||||
|
syntax::load_runtime_file,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum TsFeature {
|
||||||
|
Highlight,
|
||||||
|
TextObject,
|
||||||
|
AutoIndent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TsFeature {
|
||||||
|
pub fn all() -> &'static [Self] {
|
||||||
|
&[Self::Highlight, Self::TextObject, Self::AutoIndent]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtime_filename(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
Self::Highlight => "highlights.scm",
|
||||||
|
Self::TextObject => "textobjects.scm",
|
||||||
|
Self::AutoIndent => "indents.toml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn long_title(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
Self::Highlight => "Syntax Highlighting",
|
||||||
|
Self::TextObject => "Treesitter Textobjects",
|
||||||
|
Self::AutoIndent => "Auto Indent",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn short_title(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
Self::Highlight => "Highlight",
|
||||||
|
Self::TextObject => "Textobject",
|
||||||
|
Self::AutoIndent => "Indent",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display general diagnostics.
|
||||||
|
pub fn general() {
|
||||||
|
let config_file = helix_core::config_file();
|
||||||
|
let lang_file = helix_core::lang_config_file();
|
||||||
|
let log_file = helix_core::log_file();
|
||||||
|
let rt_dir = helix_core::runtime_dir();
|
||||||
|
|
||||||
|
if config_file.exists() {
|
||||||
|
println!("Config file: {}", config_file.display());
|
||||||
|
} else {
|
||||||
|
println!("Config file: default")
|
||||||
|
}
|
||||||
|
if lang_file.exists() {
|
||||||
|
println!("Language file: {}", lang_file.display());
|
||||||
|
} else {
|
||||||
|
println!("Language file: default")
|
||||||
|
}
|
||||||
|
println!("Log file: {}", log_file.display());
|
||||||
|
println!("Runtime directory: {}", rt_dir.display());
|
||||||
|
|
||||||
|
if let Ok(path) = std::fs::read_link(&rt_dir) {
|
||||||
|
let msg = format!("Runtime directory is symlinked to {}", path.display());
|
||||||
|
println!("{}", msg.yellow());
|
||||||
|
}
|
||||||
|
if !rt_dir.exists() {
|
||||||
|
println!("{}", "Runtime directory does not exist.".red());
|
||||||
|
}
|
||||||
|
if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) {
|
||||||
|
println!("{}", "Runtime directory is empty.".red());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn languages_all() {
|
||||||
|
let mut syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
|
||||||
|
eprintln!("{}: {}", "Error parsing user language config".red(), err);
|
||||||
|
eprintln!("{}", "Using default language config".yellow());
|
||||||
|
default_syntax_loader()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut headings = vec!["Language", "LSP", "DAP"];
|
||||||
|
|
||||||
|
for feat in TsFeature::all() {
|
||||||
|
headings.push(feat.short_title())
|
||||||
|
}
|
||||||
|
|
||||||
|
let terminal_cols = crossterm::terminal::size().map(|(c, _)| c).unwrap_or(80);
|
||||||
|
let column_width = terminal_cols as usize / headings.len();
|
||||||
|
|
||||||
|
let column = |item: &str, color: Color| {
|
||||||
|
let data = format!(
|
||||||
|
"{:column_width$}",
|
||||||
|
item.get(..column_width - 2)
|
||||||
|
.map(|s| format!("{s}…"))
|
||||||
|
.unwrap_or_else(|| item.to_string())
|
||||||
|
)
|
||||||
|
.stylize()
|
||||||
|
.with(color);
|
||||||
|
|
||||||
|
// We can't directly use println!() because of
|
||||||
|
// https://github.com/crossterm-rs/crossterm/issues/589
|
||||||
|
let _ = crossterm::execute!(std::io::stdout(), Print(data));
|
||||||
|
};
|
||||||
|
|
||||||
|
for heading in headings {
|
||||||
|
column(heading, Color::White);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
syn_loader_conf
|
||||||
|
.language
|
||||||
|
.sort_unstable_by_key(|l| l.language_id.clone());
|
||||||
|
|
||||||
|
let check_binary = |cmd: Option<String>| match cmd {
|
||||||
|
Some(cmd) => match which::which(&cmd) {
|
||||||
|
Ok(_) => column(&cmd, Color::Green),
|
||||||
|
Err(_) => column(&cmd, Color::Red),
|
||||||
|
},
|
||||||
|
None => column("None", Color::Yellow),
|
||||||
|
};
|
||||||
|
|
||||||
|
for lang in &syn_loader_conf.language {
|
||||||
|
column(&lang.language_id, Color::Reset);
|
||||||
|
|
||||||
|
let lsp = lang
|
||||||
|
.language_server
|
||||||
|
.as_ref()
|
||||||
|
.map(|lsp| lsp.command.to_string());
|
||||||
|
check_binary(lsp);
|
||||||
|
|
||||||
|
let dap = lang.debugger.as_ref().map(|dap| dap.command.to_string());
|
||||||
|
check_binary(dap);
|
||||||
|
|
||||||
|
for ts_feat in TsFeature::all() {
|
||||||
|
match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() {
|
||||||
|
true => column("Found", Color::Green),
|
||||||
|
false => column("Not Found", Color::Red),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display diagnostics pertaining to a particular language (LSP,
|
||||||
|
/// highlight queries, etc).
|
||||||
|
pub fn language(lang_str: String) {
|
||||||
|
let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
|
||||||
|
eprintln!("{}: {}", "Error parsing user language config".red(), err);
|
||||||
|
eprintln!("{}", "Using default language config".yellow());
|
||||||
|
default_syntax_loader()
|
||||||
|
});
|
||||||
|
|
||||||
|
let lang = match syn_loader_conf
|
||||||
|
.language
|
||||||
|
.iter()
|
||||||
|
.find(|l| l.language_id == lang_str)
|
||||||
|
{
|
||||||
|
Some(l) => l,
|
||||||
|
None => {
|
||||||
|
let msg = format!("Language '{lang_str}' not found");
|
||||||
|
println!("{}", msg.red());
|
||||||
|
let suggestions: Vec<&str> = syn_loader_conf
|
||||||
|
.language
|
||||||
|
.iter()
|
||||||
|
.filter(|l| l.language_id.starts_with(lang_str.chars().next().unwrap()))
|
||||||
|
.map(|l| l.language_id.as_str())
|
||||||
|
.collect();
|
||||||
|
if !suggestions.is_empty() {
|
||||||
|
let suggestions = suggestions.join(", ");
|
||||||
|
println!("Did you mean one of these: {} ?", suggestions.yellow());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
probe_protocol(
|
||||||
|
"language server",
|
||||||
|
lang.language_server
|
||||||
|
.as_ref()
|
||||||
|
.map(|lsp| lsp.command.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
probe_protocol(
|
||||||
|
"debug adapter",
|
||||||
|
lang.debugger.as_ref().map(|dap| dap.command.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
for ts_feat in TsFeature::all() {
|
||||||
|
probe_treesitter_feature(&lang_str, *ts_feat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display diagnostics about LSP and DAP.
|
||||||
|
fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) {
|
||||||
|
let cmd_name = match server_cmd {
|
||||||
|
Some(ref cmd) => cmd.as_str().green(),
|
||||||
|
None => "None".yellow(),
|
||||||
|
};
|
||||||
|
println!("Configured {}: {}", protocol_name, cmd_name);
|
||||||
|
|
||||||
|
if let Some(cmd) = server_cmd {
|
||||||
|
let path = match which::which(&cmd) {
|
||||||
|
Ok(path) => path.display().to_string().green(),
|
||||||
|
Err(_) => "Not found in $PATH".to_string().red(),
|
||||||
|
};
|
||||||
|
println!("Binary for {}: {}", protocol_name, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Display diagnostics about a feature that requires tree-sitter
|
||||||
|
/// query files (highlights, textobjects, etc).
|
||||||
|
fn probe_treesitter_feature(lang: &str, feature: TsFeature) {
|
||||||
|
let found = match load_runtime_file(lang, feature.runtime_filename()).is_ok() {
|
||||||
|
true => "Found".green(),
|
||||||
|
false => "Not found".red(),
|
||||||
|
};
|
||||||
|
println!("{} queries: {}", feature.short_title(), found);
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod compositor;
|
pub mod compositor;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod health;
|
||||||
pub mod job;
|
pub mod job;
|
||||||
pub mod keymap;
|
pub mod keymap;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
@ -40,12 +40,12 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main_impl() -> Result<i32> {
|
async fn main_impl() -> Result<i32> {
|
||||||
let cache_dir = helix_core::cache_dir();
|
let logpath = helix_core::log_file();
|
||||||
if !cache_dir.exists() {
|
let parent = logpath.parent().unwrap();
|
||||||
std::fs::create_dir_all(&cache_dir).ok();
|
if !parent.exists() {
|
||||||
|
std::fs::create_dir_all(parent).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let logpath = cache_dir.join("helix.log");
|
|
||||||
let help = format!(
|
let help = format!(
|
||||||
"\
|
"\
|
||||||
{} {}
|
{} {}
|
||||||
@ -61,6 +61,8 @@ async fn main_impl() -> Result<i32> {
|
|||||||
FLAGS:
|
FLAGS:
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
--tutor Loads the tutorial
|
--tutor Loads the tutorial
|
||||||
|
--health [LANG] Checks for potential errors in editor setup
|
||||||
|
If given, checks for config errors in language LANG
|
||||||
-v Increases logging verbosity each use for up to 3 times
|
-v Increases logging verbosity each use for up to 3 times
|
||||||
(default file: {})
|
(default file: {})
|
||||||
-V, --version Prints version information
|
-V, --version Prints version information
|
||||||
@ -85,12 +87,26 @@ async fn main_impl() -> Result<i32> {
|
|||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.health {
|
||||||
|
if let Some(lang) = args.health_arg {
|
||||||
|
match lang.as_str() {
|
||||||
|
"all" => helix_term::health::languages_all(),
|
||||||
|
_ => helix_term::health::language(lang),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
helix_term::health::general();
|
||||||
|
println!();
|
||||||
|
helix_term::health::languages_all();
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
let conf_dir = helix_core::config_dir();
|
let conf_dir = helix_core::config_dir();
|
||||||
if !conf_dir.exists() {
|
if !conf_dir.exists() {
|
||||||
std::fs::create_dir_all(&conf_dir).ok();
|
std::fs::create_dir_all(&conf_dir).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = match std::fs::read_to_string(conf_dir.join("config.toml")) {
|
let config = match std::fs::read_to_string(helix_core::config_file()) {
|
||||||
Ok(config) => toml::from_str(&config)
|
Ok(config) => toml::from_str(&config)
|
||||||
.map(merge_keys)
|
.map(merge_keys)
|
||||||
.unwrap_or_else(|err| {
|
.unwrap_or_else(|err| {
|
||||||
|
@ -3,48 +3,11 @@
|
|||||||
type DynError = Box<dyn Error>;
|
type DynError = Box<dyn Error>;
|
||||||
|
|
||||||
pub mod helpers {
|
pub mod helpers {
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
fmt::Display,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::path;
|
use crate::path;
|
||||||
use helix_core::syntax::Configuration as LangConfig;
|
use helix_core::syntax::Configuration as LangConfig;
|
||||||
|
use helix_term::health::TsFeature;
|
||||||
#[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
|
/// Get the list of languages that support a particular tree-sitter
|
||||||
/// based feature.
|
/// based feature.
|
||||||
@ -105,9 +68,9 @@ pub mod md_gen {
|
|||||||
|
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::path;
|
use crate::path;
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use helix_term::commands::TYPABLE_COMMAND_LIST;
|
use helix_term::commands::TYPABLE_COMMAND_LIST;
|
||||||
|
use helix_term::health::TsFeature;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
|
pub const TYPABLE_COMMANDS_MD_OUTPUT: &str = "typable-cmd.md";
|
||||||
pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
|
pub const LANG_SUPPORT_MD_OUTPUT: &str = "lang-support.md";
|
||||||
@ -151,13 +114,13 @@ pub fn typable_commands() -> Result<String, DynError> {
|
|||||||
|
|
||||||
pub fn lang_features() -> Result<String, DynError> {
|
pub fn lang_features() -> Result<String, DynError> {
|
||||||
let mut md = String::new();
|
let mut md = String::new();
|
||||||
let ts_features = helpers::TsFeature::all();
|
let ts_features = TsFeature::all();
|
||||||
|
|
||||||
let mut cols = vec!["Language".to_owned()];
|
let mut cols = vec!["Language".to_owned()];
|
||||||
cols.append(
|
cols.append(
|
||||||
&mut ts_features
|
&mut ts_features
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| t.to_string())
|
.map(|t| t.long_title().to_string())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
cols.push("Default LSP".to_owned());
|
cols.push("Default LSP".to_owned());
|
||||||
|
Loading…
Reference in New Issue
Block a user