Merge branch 'master' of github.com:helix-editor/helix

imgbot
trivernis 2 years ago
commit beeca2d823
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

28
Cargo.lock generated

@ -22,9 +22,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.64" version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@ -1107,9 +1107,9 @@ dependencies = [
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.15.0" version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
dependencies = [ dependencies = [
"smawk", "smawk",
"unicode-linebreak", "unicode-linebreak",
@ -1118,18 +1118,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.34" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.34" version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1171,9 +1171,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.21.0" version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -1278,15 +1278,15 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.9" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]] [[package]]
name = "url" name = "url"

@ -44,6 +44,7 @@
| `:show-directory`, `:pwd` | Show the current working directory. | | `:show-directory`, `:pwd` | Show the current working directory. |
| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. | | `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
| `:reload` | Discard changes and reload from the source file. | | `:reload` | Discard changes and reload from the source file. |
| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. | | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. | | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. | | `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |

@ -16,7 +16,7 @@ _hx() {
COMPREPLY=($(compgen -W "$languages" -- $2)) COMPREPLY=($(compgen -W "$languages" -- $2))
;; ;;
*) *)
COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config" -- $2)) COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- $2))
;; ;;
esac esac
} && complete -F _hx hx } && complete -F _hx hx

@ -36,6 +36,11 @@ set edit:completion:arg-completer[hx] = {|@args|
edit:complete-filename $args[-1] | each { |v| put $v[stem] } edit:complete-filename $args[-1] | each { |v| put $v[stem] }
return return
} }
# When we have --log, we need a file
if (has-values "log" $args[-2]) {
edit:complete-filename $args[-1] | each { |v| put $v[stem] }
return
}
} }
edit:complete-filename $args[-1] | each { |v| put $v[stem]} edit:complete-filename $args[-1] | each { |v| put $v[stem]}
$candidate "--help" "(Prints help information)" $candidate "--help" "(Prints help information)"
@ -46,4 +51,5 @@ set edit:completion:arg-completer[hx] = {|@args|
$candidate "--vsplit" "(Splits all given files vertically)" $candidate "--vsplit" "(Splits all given files vertically)"
$candidate "--hsplit" "(Splits all given files horizontally)" $candidate "--hsplit" "(Splits all given files horizontally)"
$candidate "--config" "(Specifies a file to use for configuration)" $candidate "--config" "(Specifies a file to use for configuration)"
} $candidate "--log" "(Specifies a file to write log data into)"
}

@ -11,4 +11,5 @@ complete -c hx -s v -o vv -o vvv -d "Increases logging verbosity"
complete -c hx -s V -l version -d "Prints version information" complete -c hx -s V -l version -d "Prints version information"
complete -c hx -l vsplit -d "Splits all given files vertically into different windows" complete -c hx -l vsplit -d "Splits all given files vertically into different windows"
complete -c hx -l hsplit -d "Splits all given files horizontally into different windows" complete -c hx -l hsplit -d "Splits all given files horizontally into different windows"
complete -c hx -s c -l config -d "Specifies a file to use for completion" complete -c hx -s c -l config -r -d "Specifies a file to use for completion"
complete -c hx -l log -r -d "Specifies a file to write log data into"

@ -18,6 +18,7 @@ _hx() {
"--hsplit[Splits all given files horizontally into different windows]" \ "--hsplit[Splits all given files horizontally into different windows]" \
"-c[Specifies a file to use for configuration]" \ "-c[Specifies a file to use for configuration]" \
"--config[Specifies a file to use for configuration]" \ "--config[Specifies a file to use for configuration]" \
"--log[Specifies a file to write log data into]" \
"*:file:_files" "*:file:_files"
case "$state" in case "$state" in

@ -20,7 +20,7 @@ helix-loader = { version = "0.6", path = "../helix-loader" }
ropey = { version = "1.5", default-features = false, features = ["simd"] } ropey = { version = "1.5", default-features = false, features = ["simd"] }
smallvec = "1.9" smallvec = "1.9"
smartstring = "1.0.1" smartstring = "1.0.1"
unicode-segmentation = "1.9" unicode-segmentation = "1.10"
unicode-width = "0.1" unicode-width = "0.1"
unicode-general-category = "0.5" unicode-general-category = "0.5"
# slab = "0.4.2" # slab = "0.4.2"
@ -42,7 +42,7 @@ encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
etcetera = "0.4" etcetera = "0.4"
textwrap = "0.15.0" textwrap = "0.15.1"
[dev-dependencies] [dev-dependencies]
quickcheck = { version = "1", default-features = false } quickcheck = { version = "1", default-features = false }

@ -29,6 +29,12 @@ pub enum NumberOrString {
String(String), String(String),
} }
#[derive(Debug, Clone)]
pub enum DiagnosticTag {
Unnecessary,
Deprecated,
}
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) /// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Diagnostic { pub struct Diagnostic {
@ -37,4 +43,6 @@ pub struct Diagnostic {
pub message: String, pub message: String,
pub severity: Option<Severity>, pub severity: Option<Severity>,
pub code: Option<NumberOrString>, pub code: Option<NumberOrString>,
pub tags: Vec<DiagnosticTag>,
pub source: Option<String>,
} }

@ -9,7 +9,8 @@ pub use lsp::{Position, Url};
pub use lsp_types as lsp; pub use lsp_types as lsp;
use futures_util::stream::select_all::SelectAll; use futures_util::stream::select_all::SelectAll;
use helix_core::syntax::LanguageConfiguration; use helix_core::syntax::{LanguageConfiguration, LanguageServerConfiguration};
use tokio::sync::mpsc::UnboundedReceiver;
use std::{ use std::{
collections::{hash_map::Entry, HashMap}, collections::{hash_map::Entry, HashMap},
@ -86,15 +87,32 @@ pub mod util {
None => None, None => None,
}; };
let new_tags: Vec<_> = diag
.tags
.iter()
.map(|tag| match tag {
helix_core::diagnostic::DiagnosticTag::Unnecessary => {
lsp::DiagnosticTag::UNNECESSARY
}
helix_core::diagnostic::DiagnosticTag::Deprecated => lsp::DiagnosticTag::DEPRECATED,
})
.collect();
let tags = if !new_tags.is_empty() {
Some(new_tags)
} else {
None
};
// TODO: add support for Diagnostic.data // TODO: add support for Diagnostic.data
lsp::Diagnostic::new( lsp::Diagnostic::new(
range_to_lsp_range(doc, range, offset_encoding), range_to_lsp_range(doc, range, offset_encoding),
severity, severity,
code, code,
None, diag.source.clone(),
diag.message.to_owned(), diag.message.to_owned(),
None, None,
None, tags,
) )
} }
@ -320,6 +338,33 @@ impl Registry {
.map(|(_, client)| client.as_ref()) .map(|(_, client)| client.as_ref())
} }
pub fn restart(
&mut self,
language_config: &LanguageConfiguration,
) -> Result<Option<Arc<Client>>> {
let config = match &language_config.language_server {
Some(config) => config,
None => return Ok(None),
};
let scope = language_config.scope.clone();
match self.inner.entry(scope) {
Entry::Vacant(_) => Ok(None),
Entry::Occupied(mut entry) => {
// initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed);
let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));
entry.insert((id, client.clone()));
Ok(Some(client))
}
}
}
pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> { pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
let config = match &language_config.language_server { let config = match &language_config.language_server {
Some(config) => config, Some(config) => config,
@ -331,43 +376,9 @@ impl Registry {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
// initialize a new client // initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed); let id = self.counter.fetch_add(1, Ordering::Relaxed);
let (client, incoming, initialize_notify) = Client::start(
&config.command,
&config.args,
language_config.config.clone(),
&language_config.roots,
id,
config.timeout,
)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));
let client = Arc::new(client);
// Initialize the client asynchronously
let _client = client.clone();
tokio::spawn(async move {
use futures_util::TryFutureExt;
let value = _client
.capabilities
.get_or_try_init(|| {
_client
.initialize()
.map_ok(|response| response.capabilities)
})
.await;
if let Err(e) = value {
log::error!("failed to initialize language server: {}", e);
return;
}
// next up, notify<initialized>
_client
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
.await
.unwrap();
initialize_notify.notify_one(); let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
}); self.incoming.push(UnboundedReceiverStream::new(incoming));
entry.insert((id, client.clone())); entry.insert((id, client.clone()));
Ok(Some(client)) Ok(Some(client))
@ -524,6 +535,56 @@ impl LspProgressMap {
} }
} }
struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
/// it is only called when it makes sense.
fn start_client(
id: usize,
config: &LanguageConfiguration,
ls_config: &LanguageServerConfiguration,
) -> Result<NewClientResult> {
let (client, incoming, initialize_notify) = Client::start(
&ls_config.command,
&ls_config.args,
config.config.clone(),
&config.roots,
id,
ls_config.timeout,
)?;
let client = Arc::new(client);
// Initialize the client asynchronously
let _client = client.clone();
tokio::spawn(async move {
use futures_util::TryFutureExt;
let value = _client
.capabilities
.get_or_try_init(|| {
_client
.initialize()
.map_ok(|response| response.capabilities)
})
.await;
if let Err(e) = value {
log::error!("failed to initialize language server: {}", e);
return;
}
// next up, notify<initialized>
_client
.notify::<lsp::notification::Initialized>(lsp::InitializedParams {})
.await
.unwrap();
initialize_notify.notify_one();
});
Ok(NewClientResult(client, incoming))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{lsp, util::*, OffsetEncoding}; use super::{lsp, util::*, OffsetEncoding};

@ -2,7 +2,7 @@ use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream; use futures_util::Stream;
use helix_core::{ use helix_core::{
config::{default_syntax_loader, user_syntax_loader}, config::{default_syntax_loader, user_syntax_loader},
diagnostic::NumberOrString, diagnostic::{DiagnosticTag, NumberOrString},
pos_at_coords, syntax, Selection, pos_at_coords, syntax, Selection,
}; };
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
@ -605,13 +605,28 @@ impl Application {
None => None, None => None,
}; };
let tags = if let Some(ref tags) = diagnostic.tags {
let new_tags = tags.iter().filter_map(|tag| {
match *tag {
lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated),
lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary),
_ => None
}
}).collect();
new_tags
} else {
Vec::new()
};
Some(Diagnostic { Some(Diagnostic {
range: Range { start, end }, range: Range { start, end },
line: diagnostic.range.start.line as usize, line: diagnostic.range.start.line as usize,
message: diagnostic.message.clone(), message: diagnostic.message.clone(),
severity, severity,
code, code,
// source tags,
source: diagnostic.source.clone()
}) })
}) })
.collect(); .collect();

@ -14,6 +14,7 @@ pub struct Args {
pub build_grammars: bool, pub build_grammars: bool,
pub split: Option<Layout>, pub split: Option<Layout>,
pub verbosity: u64, pub verbosity: u64,
pub log_file: Option<PathBuf>,
pub config_file: Option<PathBuf>, pub config_file: Option<PathBuf>,
pub files: Vec<(PathBuf, Position)>, pub files: Vec<(PathBuf, Position)>,
} }
@ -48,6 +49,10 @@ impl Args {
Some(path) => args.config_file = Some(path.into()), Some(path) => args.config_file = Some(path.into()),
None => anyhow::bail!("--config must specify a path to read"), None => anyhow::bail!("--config must specify a path to read"),
}, },
"--log" => match argv.next().as_deref() {
Some(path) => args.log_file = Some(path.into()),
None => anyhow::bail!("--log must specify a path to write"),
},
arg if arg.starts_with("--") => { arg if arg.starts_with("--") => {
anyhow::bail!("unexpected double dash argument: {}", arg) anyhow::bail!("unexpected double dash argument: {}", arg)
} }

@ -715,7 +715,10 @@ fn theme(
cx.editor.unset_theme_preview(); cx.editor.unset_theme_preview();
} }
PromptEvent::Update => { PromptEvent::Update => {
if let Some(theme_name) = args.first() { if args.is_empty() {
// Ensures that a preview theme gets cleaned up if the user backspaces until the prompt is empty.
cx.editor.unset_theme_preview();
} else if let Some(theme_name) = args.first() {
if let Ok(theme) = cx.editor.theme_loader.load(theme_name) { if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
if !(true_color || theme.is_16_color()) { if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support"); bail!("Unsupported theme: theme requires true color support");
@ -982,6 +985,40 @@ fn reload(
}) })
} }
fn lsp_restart(
cx: &mut compositor::Context,
_args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let (_view, doc) = current!(cx.editor);
let config = doc
.language_config()
.context("LSP not defined for the current document")?;
let scope = config.scope.clone();
cx.editor.language_servers.restart(config)?;
// This collect is needed because refresh_language_server would need to re-borrow editor.
let document_ids_to_refresh: Vec<DocumentId> = cx
.editor
.documents()
.filter_map(|doc| match doc.language_config() {
Some(config) if config.scope.eq(&scope) => Some(doc.id()),
_ => None,
})
.collect();
for document_id in document_ids_to_refresh {
cx.editor.refresh_language_server(document_id);
}
Ok(())
}
fn tree_sitter_scopes( fn tree_sitter_scopes(
cx: &mut compositor::Context, cx: &mut compositor::Context,
_args: &[Cow<str>], _args: &[Cow<str>],
@ -1844,6 +1881,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
fun: reload, fun: reload,
completer: None, completer: None,
}, },
TypableCommand {
name: "lsp-restart",
aliases: &[],
doc: "Restarts the Language Server that is in use by the current doc",
fun: lsp_restart,
completer: None,
},
TypableCommand { TypableCommand {
name: "tree-sitter-scopes", name: "tree-sitter-scopes",
aliases: &[], aliases: &[],

@ -67,6 +67,7 @@ FLAGS:
-g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
-c, --config <file> Specifies a file to use for configuration -c, --config <file> Specifies a file to use for configuration
-v Increases logging verbosity each use for up to 3 times -v Increases logging verbosity each use for up to 3 times
--log Specifies a file to use for logging
(default file: {}) (default file: {})
-V, --version Prints version information -V, --version Prints version information
--vsplit Splits all given files vertically into different windows --vsplit Splits all given files vertically into different windows
@ -114,6 +115,7 @@ FLAGS:
return Ok(0); return Ok(0);
} }
let logpath = args.log_file.as_ref().cloned().unwrap_or(logpath);
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?; setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
let config_dir = helix_loader::config_dir(); let config_dir = helix_loader::config_dir();

@ -293,6 +293,7 @@ impl Prompt {
register: char, register: char,
direction: CompletionDirection, direction: CompletionDirection,
) { ) {
(self.callback_fn)(cx, &self.line, PromptEvent::Abort);
let register = cx.editor.registers.get_mut(register).read(); let register = cx.editor.registers.get_mut(register).read();
if register.is_empty() { if register.is_empty() {
@ -314,6 +315,7 @@ impl Prompt {
self.history_pos = Some(index); self.history_pos = Some(index);
self.move_end(); self.move_end();
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
self.recalculate_completion(cx.editor); self.recalculate_completion(cx.editor);
} }
@ -564,13 +566,11 @@ impl Component for Prompt {
ctrl!('p') | key!(Up) => { ctrl!('p') | key!(Up) => {
if let Some(register) = self.history_register { if let Some(register) = self.history_register {
self.change_history(cx, register, CompletionDirection::Backward); self.change_history(cx, register, CompletionDirection::Backward);
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
} }
} }
ctrl!('n') | key!(Down) => { ctrl!('n') | key!(Down) => {
if let Some(register) = self.history_register { if let Some(register) = self.history_register {
self.change_history(cx, register, CompletionDirection::Forward); self.change_history(cx, register, CompletionDirection::Forward);
(self.callback_fn)(cx, &self.line, PromptEvent::Update);
} }
} }
key!(Tab) => { key!(Tab) => {

@ -18,7 +18,7 @@ default = ["crossterm"]
[dependencies] [dependencies]
bitflags = "1.3" bitflags = "1.3"
cassowary = "0.3" cassowary = "0.3"
unicode-segmentation = "1.9" unicode-segmentation = "1.10"
crossterm = { version = "0.25", optional = true } crossterm = { version = "0.25", optional = true }
serde = { version = "1", "optional" = true, features = ["derive"]} serde = { version = "1", "optional" = true, features = ["derive"]}
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }

@ -788,6 +788,8 @@ impl Document {
diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After); diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
diagnostic.line = self.text.char_to_line(diagnostic.range.start); diagnostic.line = self.text.char_to_line(diagnostic.range.start);
} }
self.diagnostics
.sort_unstable_by_key(|diagnostic| diagnostic.range);
// emit lsp notification // emit lsp notification
if let Some(language_server) = self.language_server() { if let Some(language_server) = self.language_server() {

Loading…
Cancel
Save