mirror of
https://github.com/nushell/nushell.git
synced 2025-05-01 16:44:27 +02:00
feat(lsp): use lsp-textdocument to handle utf16 position (#14742)
<!-- if this PR closes one or more issues, you can automatically link the PR with them by using one of the [*linking keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword), e.g. - this PR should close #xxxx - fixes #xxxx you can also mention related issues, PRs or discussions! --> # Description <!-- Thank you for improving Nushell. Please, check our [contributing guide](../CONTRIBUTING.md) and talk to the core team before making major changes. Description of your pull request goes here. **Provide examples and/or screenshots** if your changes affect the user experience. --> This PR replaces `ropey` with `lsp-textdocument` for easier utf16 position handling. As a side effect, if fixes the following crashing bug: 1. create a `foo.nu` file with errors in it 2. in `bar.nu`, add code `use foo.nu *` # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> * <s>Diagnostics are now triggered only with document open/save, that's my personal preference. Changing back to previous behavior is easy if you guys have other concerns.</s> * UTF-8 position encoding is not supported by lsp-textdocument, but that's not an issue, since the previous utf-8 ropey implementation is buggy when used in real scenarios in a text editor. # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> No new tests added, removed some utf-8 related ones. # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
8b086d3613
commit
b5ff46db6a
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -1964,6 +1964,15 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluent-uri"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@ -3195,16 +3204,26 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-textdocument"
|
||||||
version = "0.95.1"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365"
|
checksum = "d8dc223af95101fe950a871d4d567b6f98a1ecfcee5861f4b57644581aaa980d"
|
||||||
|
dependencies = [
|
||||||
|
"lsp-types",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsp-types"
|
||||||
|
version = "0.97.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
|
"fluent-uri",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"url",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3924,6 +3943,7 @@ dependencies = [
|
|||||||
"assert-json-diff",
|
"assert-json-diff",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
|
"lsp-textdocument",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"miette",
|
"miette",
|
||||||
"nu-cli",
|
"nu-cli",
|
||||||
@ -3933,9 +3953,9 @@ dependencies = [
|
|||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-test-support",
|
"nu-test-support",
|
||||||
"reedline",
|
"reedline",
|
||||||
"ropey",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6247,16 +6267,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ropey"
|
|
||||||
version = "1.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
|
|
||||||
dependencies = [
|
|
||||||
"smallvec",
|
|
||||||
"str_indices",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
@ -7015,12 +7025,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "str_indices"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "streaming-decompression"
|
name = "streaming-decompression"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -7800,7 +7804,6 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -103,7 +103,8 @@ log = "0.4"
|
|||||||
lru = "0.12"
|
lru = "0.12"
|
||||||
lscolors = { version = "0.17", default-features = false }
|
lscolors = { version = "0.17", default-features = false }
|
||||||
lsp-server = "0.7.5"
|
lsp-server = "0.7.5"
|
||||||
lsp-types = { version = "0.95.0", features = ["proposed"] }
|
lsp-types = { version = "0.97.0", features = ["proposed"] }
|
||||||
|
lsp-textdocument = "0.4.0"
|
||||||
mach2 = "0.4"
|
mach2 = "0.4"
|
||||||
md5 = { version = "0.10", package = "md-5" }
|
md5 = { version = "0.10", package = "md-5" }
|
||||||
miette = "7.3"
|
miette = "7.3"
|
||||||
@ -139,10 +140,8 @@ rand_chacha = "0.3.1"
|
|||||||
ratatui = "0.26"
|
ratatui = "0.26"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
reedline = "0.38.0"
|
reedline = "0.38.0"
|
||||||
regex = "1.9.5"
|
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
ropey = "1.6.1"
|
|
||||||
roxmltree = "0.20"
|
roxmltree = "0.20"
|
||||||
rstest = { version = "0.23", default-features = false }
|
rstest = { version = "0.23", default-features = false }
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
|
@ -15,12 +15,13 @@ nu-protocol = { path = "../nu-protocol", version = "0.101.1" }
|
|||||||
reedline = { workspace = true }
|
reedline = { workspace = true }
|
||||||
|
|
||||||
crossbeam-channel = { workspace = true }
|
crossbeam-channel = { workspace = true }
|
||||||
lsp-types = { workspace = true }
|
|
||||||
lsp-server = { workspace = true }
|
lsp-server = { workspace = true }
|
||||||
|
lsp-types = { workspace = true }
|
||||||
|
lsp-textdocument = { workspace = true }
|
||||||
miette = { workspace = true }
|
miette = { workspace = true }
|
||||||
ropey = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.1" }
|
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.101.1" }
|
||||||
|
@ -1,41 +1,24 @@
|
|||||||
use crate::LanguageServer;
|
use crate::LanguageServer;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{Notification, PublishDiagnostics},
|
notification::{Notification, PublishDiagnostics},
|
||||||
Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Url,
|
Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams, Uri,
|
||||||
};
|
};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_parser::parse;
|
use nu_protocol::Value;
|
||||||
use nu_protocol::{
|
|
||||||
engine::{EngineState, StateWorkingSet},
|
|
||||||
Span, Value,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub(crate) fn publish_diagnostics_for_file(
|
pub(crate) fn publish_diagnostics_for_file(&mut self, uri: Uri) -> Result<()> {
|
||||||
&self,
|
let mut engine_state = self.engine_state.clone();
|
||||||
uri: Url,
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
) -> Result<()> {
|
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
engine_state.generate_nu_constant();
|
engine_state.generate_nu_constant();
|
||||||
|
|
||||||
let mut working_set = StateWorkingSet::new(engine_state);
|
let Some((_, offset, working_set, file)) =
|
||||||
|
self.update_engine_state(&mut engine_state, &uri)
|
||||||
let Some((rope_of_file, file_path)) = self.rope(&uri) else {
|
else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let contents = rope_of_file.bytes().collect::<Vec<u8>>();
|
|
||||||
let offset = working_set.next_span_start();
|
|
||||||
working_set.files.push(file_path.into(), Span::unknown())?;
|
|
||||||
parse(
|
|
||||||
&mut working_set,
|
|
||||||
Some(&file_path.to_string_lossy()),
|
|
||||||
&contents,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut diagnostics = PublishDiagnosticsParams {
|
let mut diagnostics = PublishDiagnosticsParams {
|
||||||
uri,
|
uri,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
@ -46,12 +29,7 @@ impl LanguageServer {
|
|||||||
let message = err.to_string();
|
let message = err.to_string();
|
||||||
|
|
||||||
diagnostics.diagnostics.push(Diagnostic {
|
diagnostics.diagnostics.push(Diagnostic {
|
||||||
range: Self::span_to_range(
|
range: Self::span_to_range(&err.span(), file, offset),
|
||||||
&err.span(),
|
|
||||||
rope_of_file,
|
|
||||||
offset,
|
|
||||||
&self.position_encoding,
|
|
||||||
),
|
|
||||||
severity: Some(DiagnosticSeverity::ERROR),
|
severity: Some(DiagnosticSeverity::ERROR),
|
||||||
message,
|
message,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -70,20 +48,20 @@ impl LanguageServer {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_types::Url;
|
|
||||||
use nu_test_support::fs::fixtures;
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
|
use crate::path_to_uri;
|
||||||
use crate::tests::{initialize_language_server, open_unchecked, update};
|
use crate::tests::{initialize_language_server, open_unchecked, update};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn publish_diagnostics_variable_does_not_exists() {
|
fn publish_diagnostics_variable_does_not_exists() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("diagnostics");
|
script.push("diagnostics");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
let notification = open_unchecked(&client_connection, script.clone());
|
let notification = open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -108,13 +86,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn publish_diagnostics_fixed_unknown_variable() {
|
fn publish_diagnostics_fixed_unknown_variable() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("diagnostics");
|
script.push("diagnostics");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
let notification = update(
|
let notification = update(
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
|
||||||
|
use lsp_textdocument::{FullTextDocument, TextDocuments};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
request::{Completion, GotoDefinition, HoverRequest, Request},
|
request::{Completion, GotoDefinition, HoverRequest, Request},
|
||||||
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
|
||||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location,
|
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location,
|
||||||
MarkupContent, MarkupKind, OneOf, Position, PositionEncodingKind, Range, ServerCapabilities,
|
MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit,
|
||||||
TextDocumentSyncKind, TextEdit, Url,
|
Uri,
|
||||||
};
|
};
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_cli::{NuCompleter, SuggestionKind};
|
use nu_cli::{NuCompleter, SuggestionKind};
|
||||||
use nu_parser::{flatten_block, parse, FlatShape};
|
use nu_parser::{flatten_block, parse, FlatShape};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{CachedFile, EngineState, Stack, StateWorkingSet},
|
ast::Block,
|
||||||
|
engine::{EngineState, Stack, StateWorkingSet},
|
||||||
DeclId, Span, Value, VarId,
|
DeclId, Span, Value, VarId,
|
||||||
};
|
};
|
||||||
use ropey::Rope;
|
|
||||||
use serde_json::json;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::BTreeMap,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
mod notification;
|
mod notification;
|
||||||
@ -36,40 +37,49 @@ enum Id {
|
|||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
io_threads: Option<IoThreads>,
|
io_threads: Option<IoThreads>,
|
||||||
ropes: BTreeMap<PathBuf, Rope>,
|
docs: TextDocuments,
|
||||||
position_encoding: PositionEncodingKind,
|
engine_state: EngineState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_to_uri<P>(path: P) -> Uri
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
Uri::from_str(
|
||||||
|
Url::from_file_path(path)
|
||||||
|
.expect("Failed to convert path to Url")
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.expect("Failed to convert Url to lsp_types::Uri.")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uri_to_path(uri: &Uri) -> PathBuf {
|
||||||
|
Url::from_str(uri.as_str())
|
||||||
|
.expect("Failed to convert Uri to Url")
|
||||||
|
.to_file_path()
|
||||||
|
.expect("Failed to convert Url to path")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn initialize_stdio_connection() -> Result<Self> {
|
pub fn initialize_stdio_connection(engine_state: EngineState) -> Result<Self> {
|
||||||
let (connection, io_threads) = Connection::stdio();
|
let (connection, io_threads) = Connection::stdio();
|
||||||
Self::initialize_connection(connection, Some(io_threads))
|
Self::initialize_connection(connection, Some(io_threads), engine_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_connection(
|
fn initialize_connection(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
io_threads: Option<IoThreads>,
|
io_threads: Option<IoThreads>,
|
||||||
|
engine_state: EngineState,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
connection,
|
connection,
|
||||||
io_threads,
|
io_threads,
|
||||||
ropes: BTreeMap::new(),
|
docs: TextDocuments::new(),
|
||||||
position_encoding: PositionEncodingKind::UTF16,
|
engine_state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_offset_encoding(&self, initialization_params: serde_json::Value) -> String {
|
pub fn serve_requests(mut self) -> Result<()> {
|
||||||
initialization_params
|
|
||||||
.pointer("/capabilities/offsetEncoding/0")
|
|
||||||
.unwrap_or(
|
|
||||||
initialization_params
|
|
||||||
.pointer("/capabilities/offset_encoding/0")
|
|
||||||
.unwrap_or(&json!("utf-16")),
|
|
||||||
)
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serve_requests(mut self, engine_state: EngineState) -> Result<()> {
|
|
||||||
let server_capabilities = serde_json::to_value(ServerCapabilities {
|
let server_capabilities = serde_json::to_value(ServerCapabilities {
|
||||||
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
|
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind(
|
||||||
TextDocumentSyncKind::INCREMENTAL,
|
TextDocumentSyncKind::INCREMENTAL,
|
||||||
@ -81,16 +91,14 @@ impl LanguageServer {
|
|||||||
})
|
})
|
||||||
.expect("Must be serializable");
|
.expect("Must be serializable");
|
||||||
|
|
||||||
let initialization_params = self
|
let _ = self
|
||||||
.connection
|
.connection
|
||||||
.initialize_while(server_capabilities, || {
|
.initialize_while(server_capabilities, || {
|
||||||
!engine_state.signals().interrupted()
|
!self.engine_state.signals().interrupted()
|
||||||
})
|
})
|
||||||
.into_diagnostic()?;
|
.into_diagnostic()?;
|
||||||
self.position_encoding =
|
|
||||||
PositionEncodingKind::from(self.get_offset_encoding(initialization_params));
|
|
||||||
|
|
||||||
while !engine_state.signals().interrupted() {
|
while !self.engine_state.signals().interrupted() {
|
||||||
let msg = match self
|
let msg = match self
|
||||||
.connection
|
.connection
|
||||||
.receiver
|
.receiver
|
||||||
@ -113,23 +121,16 @@ impl LanguageServer {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut engine_state = engine_state.clone();
|
|
||||||
let resp = match request.method.as_str() {
|
let resp = match request.method.as_str() {
|
||||||
GotoDefinition::METHOD => Self::handle_lsp_request(
|
GotoDefinition::METHOD => {
|
||||||
&mut engine_state,
|
Self::handle_lsp_request(request, |params| self.goto_definition(params))
|
||||||
request,
|
}
|
||||||
|engine_state, params| self.goto_definition(engine_state, params),
|
HoverRequest::METHOD => {
|
||||||
),
|
Self::handle_lsp_request(request, |params| self.hover(params))
|
||||||
HoverRequest::METHOD => Self::handle_lsp_request(
|
}
|
||||||
&mut engine_state,
|
Completion::METHOD => {
|
||||||
request,
|
Self::handle_lsp_request(request, |params| self.complete(params))
|
||||||
|engine_state, params| self.hover(engine_state, params),
|
}
|
||||||
),
|
|
||||||
Completion::METHOD => Self::handle_lsp_request(
|
|
||||||
&mut engine_state,
|
|
||||||
request,
|
|
||||||
|engine_state, params| self.complete(engine_state, params),
|
|
||||||
),
|
|
||||||
_ => {
|
_ => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -143,8 +144,7 @@ impl LanguageServer {
|
|||||||
Message::Response(_) => {}
|
Message::Response(_) => {}
|
||||||
Message::Notification(notification) => {
|
Message::Notification(notification) => {
|
||||||
if let Some(updated_file) = self.handle_lsp_notification(notification) {
|
if let Some(updated_file) = self.handle_lsp_notification(notification) {
|
||||||
let mut engine_state = engine_state.clone();
|
self.publish_diagnostics_for_file(updated_file)?;
|
||||||
self.publish_diagnostics_for_file(updated_file, &mut engine_state)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,21 +157,38 @@ impl LanguageServer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_lsp_request<P, H, R>(
|
pub fn update_engine_state<'a>(
|
||||||
engine_state: &mut EngineState,
|
&mut self,
|
||||||
req: lsp_server::Request,
|
engine_state: &'a mut EngineState,
|
||||||
mut param_handler: H,
|
uri: &Uri,
|
||||||
) -> Response
|
) -> Option<(Arc<Block>, usize, StateWorkingSet<'a>, &FullTextDocument)> {
|
||||||
|
let mut working_set = StateWorkingSet::new(engine_state);
|
||||||
|
let file = self.docs.get_document(uri)?;
|
||||||
|
let file_path = uri_to_path(uri);
|
||||||
|
let file_path_str = file_path.to_str()?;
|
||||||
|
let contents = file.get_content(None).as_bytes();
|
||||||
|
let _ = working_set.files.push(file_path.clone(), Span::unknown());
|
||||||
|
let block = parse(&mut working_set, Some(file_path_str), contents, false);
|
||||||
|
let offset = working_set
|
||||||
|
.get_span_for_filename(file_path_str)
|
||||||
|
.unwrap_or_else(|| panic!("Failed at get_span_for_filename {}", file_path_str))
|
||||||
|
.start;
|
||||||
|
// TODO: merge delta back to engine_state?
|
||||||
|
// self.engine_state.merge_delta(working_set.render());
|
||||||
|
Some((block, offset, working_set, file))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_lsp_request<P, H, R>(req: lsp_server::Request, mut param_handler: H) -> Response
|
||||||
where
|
where
|
||||||
P: serde::de::DeserializeOwned,
|
P: serde::de::DeserializeOwned,
|
||||||
H: FnMut(&mut EngineState, &P) -> Option<R>,
|
H: FnMut(&P) -> Option<R>,
|
||||||
R: serde::ser::Serialize,
|
R: serde::ser::Serialize,
|
||||||
{
|
{
|
||||||
match serde_json::from_value::<P>(req.params) {
|
match serde_json::from_value::<P>(req.params) {
|
||||||
Ok(params) => Response {
|
Ok(params) => Response {
|
||||||
id: req.id,
|
id: req.id,
|
||||||
result: Some(
|
result: Some(
|
||||||
param_handler(engine_state, ¶ms)
|
param_handler(¶ms)
|
||||||
.and_then(|response| serde_json::to_value(response).ok())
|
.and_then(|response| serde_json::to_value(response).ok())
|
||||||
.unwrap_or(serde_json::Value::Null),
|
.unwrap_or(serde_json::Value::Null),
|
||||||
),
|
),
|
||||||
@ -190,97 +207,17 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn span_to_range(
|
fn span_to_range(span: &Span, file: &FullTextDocument, offset: usize) -> Range {
|
||||||
span: &Span,
|
let start = file.position_at(span.start.saturating_sub(offset) as u32);
|
||||||
rope_of_file: &Rope,
|
let end = file.position_at(span.end.saturating_sub(offset) as u32);
|
||||||
offset: usize,
|
|
||||||
position_encoding: &PositionEncodingKind,
|
|
||||||
) -> Range {
|
|
||||||
let start = Self::lsp_byte_offset_to_utf_cu_position(
|
|
||||||
span.start.saturating_sub(offset),
|
|
||||||
rope_of_file,
|
|
||||||
position_encoding,
|
|
||||||
);
|
|
||||||
let end = Self::lsp_byte_offset_to_utf_cu_position(
|
|
||||||
span.end.saturating_sub(offset),
|
|
||||||
rope_of_file,
|
|
||||||
position_encoding,
|
|
||||||
);
|
|
||||||
Range { start, end }
|
Range { start, end }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lsp_byte_offset_to_utf_cu_position(
|
|
||||||
offset: usize,
|
|
||||||
rope_of_file: &Rope,
|
|
||||||
position_encoding: &PositionEncodingKind,
|
|
||||||
) -> Position {
|
|
||||||
let line = rope_of_file.try_byte_to_line(offset).unwrap_or(0);
|
|
||||||
match position_encoding.as_str() {
|
|
||||||
"\"utf-8\"" => {
|
|
||||||
let character = offset - rope_of_file.line_to_byte(line);
|
|
||||||
Position {
|
|
||||||
line: line as u32,
|
|
||||||
character: character as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let character = rope_of_file.char_to_utf16_cu(rope_of_file.byte_to_char(offset))
|
|
||||||
- rope_of_file.char_to_utf16_cu(rope_of_file.line_to_char(line));
|
|
||||||
Position {
|
|
||||||
line: line as u32,
|
|
||||||
character: character as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn utf16_cu_position_to_char(rope_of_file: &Rope, position: &Position) -> usize {
|
|
||||||
let line_utf_idx =
|
|
||||||
rope_of_file.char_to_utf16_cu(rope_of_file.line_to_char(position.line as usize));
|
|
||||||
rope_of_file.utf16_cu_to_char(line_utf_idx + position.character as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lsp_position_to_location(
|
|
||||||
position: &Position,
|
|
||||||
rope_of_file: &Rope,
|
|
||||||
position_encoding: &PositionEncodingKind,
|
|
||||||
) -> usize {
|
|
||||||
match position_encoding.as_str() {
|
|
||||||
"\"utf-8\"" => rope_of_file.byte_to_char(
|
|
||||||
rope_of_file.line_to_byte(position.line as usize) + position.character as usize,
|
|
||||||
),
|
|
||||||
_ => Self::utf16_cu_position_to_char(rope_of_file, position),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lsp_position_to_byte_offset(&self, position: &Position, rope_of_file: &Rope) -> usize {
|
|
||||||
match self.position_encoding.as_str() {
|
|
||||||
"\"utf-8\"" => {
|
|
||||||
rope_of_file.line_to_byte(position.line as usize) + position.character as usize
|
|
||||||
}
|
|
||||||
_ => rope_of_file
|
|
||||||
.try_char_to_byte(Self::utf16_cu_position_to_char(rope_of_file, position))
|
|
||||||
.expect("Character index out of range!"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_id(
|
fn find_id(
|
||||||
working_set: &mut StateWorkingSet,
|
flattened: Vec<(Span, FlatShape)>,
|
||||||
path: &Path,
|
|
||||||
file: &Rope,
|
|
||||||
location: usize,
|
location: usize,
|
||||||
|
offset: usize,
|
||||||
) -> Option<(Id, usize, Span)> {
|
) -> Option<(Id, usize, Span)> {
|
||||||
let file_path = path.to_string_lossy();
|
|
||||||
|
|
||||||
// TODO: think about passing down the rope into the working_set
|
|
||||||
let contents = file.bytes().collect::<Vec<u8>>();
|
|
||||||
let _ = working_set
|
|
||||||
.files
|
|
||||||
.push(file_path.as_ref().into(), Span::unknown());
|
|
||||||
let block = parse(working_set, Some(&file_path), &contents, false);
|
|
||||||
let flattened = flatten_block(working_set, &block);
|
|
||||||
|
|
||||||
let offset = working_set.get_span_for_filename(&file_path)?.start;
|
|
||||||
let location = location + offset;
|
let location = location + offset;
|
||||||
|
|
||||||
for (span, shape) in flattened {
|
for (span, shape) in flattened {
|
||||||
@ -299,122 +236,88 @@ impl LanguageServer {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rope<'a, 'b: 'a>(&'b self, file_url: &Url) -> Option<(&'a Rope, &'a PathBuf)> {
|
fn get_location_by_span(&self, working_set: &StateWorkingSet, span: &Span) -> Option<Location> {
|
||||||
let file_path = file_url.to_file_path().ok()?;
|
|
||||||
|
|
||||||
self.ropes
|
|
||||||
.get_key_value(&file_path)
|
|
||||||
.map(|(path, rope)| (rope, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_in_file<'a>(
|
|
||||||
&self,
|
|
||||||
engine_state: &'a mut EngineState,
|
|
||||||
file_url: &Url,
|
|
||||||
) -> Option<(&Rope, &PathBuf, StateWorkingSet<'a>)> {
|
|
||||||
let (file, path) = self.rope(file_url)?;
|
|
||||||
|
|
||||||
engine_state.file = Some(path.to_owned());
|
|
||||||
|
|
||||||
let working_set = StateWorkingSet::new(engine_state);
|
|
||||||
|
|
||||||
Some((file, path, working_set))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rope_file_from_cached_file(&mut self, cached_file: &CachedFile) -> Result<(Url, &Rope), ()> {
|
|
||||||
let uri = Url::from_file_path(&*cached_file.name)?;
|
|
||||||
let rope_of_file = self.ropes.entry(uri.to_file_path()?).or_insert_with(|| {
|
|
||||||
let raw_string = String::from_utf8_lossy(&cached_file.content);
|
|
||||||
Rope::from_str(&raw_string)
|
|
||||||
});
|
|
||||||
Ok((uri, rope_of_file))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn goto_definition(
|
|
||||||
&mut self,
|
|
||||||
engine_state: &mut EngineState,
|
|
||||||
params: &GotoDefinitionParams,
|
|
||||||
) -> Option<GotoDefinitionResponse> {
|
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
|
||||||
|
|
||||||
let (file, path, mut working_set) = self.read_in_file(
|
|
||||||
engine_state,
|
|
||||||
¶ms.text_document_position_params.text_document.uri,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (id, _, _) = Self::find_id(
|
|
||||||
&mut working_set,
|
|
||||||
path,
|
|
||||||
file,
|
|
||||||
self.lsp_position_to_byte_offset(¶ms.text_document_position_params.position, file),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match id {
|
|
||||||
Id::Declaration(decl_id) => {
|
|
||||||
if let Some(block_id) = working_set.get_decl(decl_id).block_id() {
|
|
||||||
let block = working_set.get_block(block_id);
|
|
||||||
if let Some(span) = &block.span {
|
|
||||||
for cached_file in working_set.files() {
|
for cached_file in working_set.files() {
|
||||||
if cached_file.covered_span.contains(span.start) {
|
if cached_file.covered_span.contains(span.start) {
|
||||||
let position_encoding = self.position_encoding.clone();
|
let path = Path::new(&*cached_file.name);
|
||||||
let (uri, rope_of_file) =
|
if !(path.exists() && path.is_file()) {
|
||||||
self.rope_file_from_cached_file(cached_file).ok()?;
|
return None;
|
||||||
return Some(GotoDefinitionResponse::Scalar(Location {
|
}
|
||||||
uri,
|
let target_uri = path_to_uri(path);
|
||||||
range: Self::span_to_range(
|
if let Some(doc) = self.docs.get_document(&target_uri) {
|
||||||
span,
|
return Some(Location {
|
||||||
rope_of_file,
|
uri: target_uri,
|
||||||
cached_file.covered_span.start,
|
range: Self::span_to_range(span, doc, cached_file.covered_span.start),
|
||||||
&position_encoding,
|
});
|
||||||
),
|
} else {
|
||||||
}));
|
// in case where the document is not opened yet, typically included by `nu -I`
|
||||||
|
let temp_doc = FullTextDocument::new(
|
||||||
|
"Unk".to_string(),
|
||||||
|
0,
|
||||||
|
String::from_utf8((*cached_file.content).to_vec()).expect("Invalid UTF-8"),
|
||||||
|
);
|
||||||
|
return Some(Location {
|
||||||
|
uri: target_uri,
|
||||||
|
range: Self::span_to_range(span, &temp_doc, cached_file.covered_span.start),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Id::Variable(var_id) => {
|
|
||||||
let var = working_set.get_variable(var_id);
|
|
||||||
for cached_file in working_set.files() {
|
|
||||||
if cached_file
|
|
||||||
.covered_span
|
|
||||||
.contains(var.declaration_span.start)
|
|
||||||
{
|
|
||||||
let position_encoding = self.position_encoding.clone();
|
|
||||||
let (uri, rope_of_file) =
|
|
||||||
self.rope_file_from_cached_file(cached_file).ok()?;
|
|
||||||
return Some(GotoDefinitionResponse::Scalar(Location {
|
|
||||||
uri,
|
|
||||||
range: Self::span_to_range(
|
|
||||||
&var.declaration_span,
|
|
||||||
rope_of_file,
|
|
||||||
cached_file.covered_span.start,
|
|
||||||
&position_encoding,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Id::Value(_) => {}
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hover(&mut self, engine_state: &mut EngineState, params: &HoverParams) -> Option<Hover> {
|
fn goto_definition(&mut self, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||||
|
let mut engine_state = self.engine_state.clone();
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
let (file, path, mut working_set) = self.read_in_file(
|
let path_uri = params
|
||||||
engine_state,
|
.text_document_position_params
|
||||||
¶ms.text_document_position_params.text_document.uri,
|
.text_document
|
||||||
|
.uri
|
||||||
|
.to_owned();
|
||||||
|
let (block, file_offset, working_set, file) =
|
||||||
|
self.update_engine_state(&mut engine_state, &path_uri)?;
|
||||||
|
let flattened = flatten_block(&working_set, &block);
|
||||||
|
let (id, _, _) = Self::find_id(
|
||||||
|
flattened,
|
||||||
|
file.offset_at(params.text_document_position_params.position) as usize,
|
||||||
|
file_offset,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let span = match id {
|
||||||
|
Id::Declaration(decl_id) => {
|
||||||
|
let block_id = working_set.get_decl(decl_id).block_id()?;
|
||||||
|
working_set.get_block(block_id).span
|
||||||
|
}
|
||||||
|
Id::Variable(var_id) => {
|
||||||
|
let var = working_set.get_variable(var_id);
|
||||||
|
Some(var.declaration_span)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}?;
|
||||||
|
Some(GotoDefinitionResponse::Scalar(
|
||||||
|
self.get_location_by_span(&working_set, &span)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover(&mut self, params: &HoverParams) -> Option<Hover> {
|
||||||
|
let mut engine_state = self.engine_state.clone();
|
||||||
|
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||||
|
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
||||||
|
|
||||||
|
let path_uri = params
|
||||||
|
.text_document_position_params
|
||||||
|
.text_document
|
||||||
|
.uri
|
||||||
|
.to_owned();
|
||||||
|
let (block, file_offset, working_set, file) =
|
||||||
|
self.update_engine_state(&mut engine_state, &path_uri)?;
|
||||||
|
let flattened = flatten_block(&working_set, &block);
|
||||||
let (id, _, _) = Self::find_id(
|
let (id, _, _) = Self::find_id(
|
||||||
&mut working_set,
|
flattened,
|
||||||
path,
|
file.offset_at(params.text_document_position_params.position) as usize,
|
||||||
file,
|
file_offset,
|
||||||
self.lsp_position_to_byte_offset(¶ms.text_document_position_params.position, file),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match id {
|
match id {
|
||||||
@ -615,26 +518,15 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(
|
fn complete(&mut self, params: &CompletionParams) -> Option<CompletionResponse> {
|
||||||
&mut self,
|
let path_uri = params.text_document_position.text_document.uri.to_owned();
|
||||||
engine_state: &mut EngineState,
|
let file = self.docs.get_document(&path_uri)?;
|
||||||
params: &CompletionParams,
|
|
||||||
) -> Option<CompletionResponse> {
|
|
||||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
|
||||||
engine_state.add_env_var("PWD".into(), Value::test_string(cwd.to_string_lossy()));
|
|
||||||
|
|
||||||
let (rope_of_file, _, _) = self.read_in_file(
|
|
||||||
engine_state,
|
|
||||||
¶ms.text_document_position.text_document.uri,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut completer =
|
let mut completer =
|
||||||
NuCompleter::new(Arc::new(engine_state.clone()), Arc::new(Stack::new()));
|
NuCompleter::new(Arc::new(self.engine_state.clone()), Arc::new(Stack::new()));
|
||||||
|
|
||||||
let location =
|
let location = file.offset_at(params.text_document_position.position) as usize;
|
||||||
self.lsp_position_to_byte_offset(¶ms.text_document_position.position, rope_of_file);
|
let results = completer.fetch_completions_at(&file.get_content(None)[..location], location);
|
||||||
let results =
|
|
||||||
completer.fetch_completions_at(&rope_of_file.to_string()[..location], location);
|
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -691,26 +583,23 @@ mod tests {
|
|||||||
},
|
},
|
||||||
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
|
request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown},
|
||||||
CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
CompletionParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
||||||
GotoDefinitionParams, InitializeParams, InitializedParams, PartialResultParams,
|
GotoDefinitionParams, InitializeParams, InitializedParams, PartialResultParams, Position,
|
||||||
TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
|
TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem,
|
||||||
TextDocumentPositionParams, Url, WorkDoneProgressParams,
|
TextDocumentPositionParams, WorkDoneProgressParams,
|
||||||
};
|
};
|
||||||
use nu_test_support::fs::{fixtures, root};
|
use nu_test_support::fs::{fixtures, root};
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
|
|
||||||
pub fn initialize_language_server(
|
pub fn initialize_language_server() -> (Connection, Receiver<Result<()>>) {
|
||||||
client_offset_encoding: Option<Vec<String>>,
|
|
||||||
) -> (Connection, Receiver<Result<()>>) {
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
let (client_connection, server_connection) = Connection::memory();
|
let (client_connection, server_connection) = Connection::memory();
|
||||||
let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap();
|
|
||||||
|
|
||||||
let (send, recv) = mpsc::channel();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
let engine_state = nu_cmd_lang::create_default_context();
|
let engine_state = nu_cmd_lang::create_default_context();
|
||||||
let engine_state = nu_command::add_shell_command_context(engine_state);
|
let engine_state = nu_command::add_shell_command_context(engine_state);
|
||||||
send.send(lsp_server.serve_requests(engine_state))
|
let lsp_server =
|
||||||
});
|
LanguageServer::initialize_connection(server_connection, None, engine_state).unwrap();
|
||||||
|
|
||||||
|
let (send, recv) = mpsc::channel();
|
||||||
|
std::thread::spawn(move || send.send(lsp_server.serve_requests()));
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
@ -719,7 +608,6 @@ mod tests {
|
|||||||
method: Initialize::METHOD.to_string(),
|
method: Initialize::METHOD.to_string(),
|
||||||
params: serde_json::to_value(InitializeParams {
|
params: serde_json::to_value(InitializeParams {
|
||||||
capabilities: lsp_types::ClientCapabilities {
|
capabilities: lsp_types::ClientCapabilities {
|
||||||
offset_encoding: client_offset_encoding,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -745,7 +633,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shutdown_on_request() {
|
fn shutdown_on_request() {
|
||||||
let (client_connection, recv) = initialize_language_server(None);
|
let (client_connection, recv) = initialize_language_server();
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
@ -771,7 +659,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_for_none_existing_file() {
|
fn goto_definition_for_none_existing_file() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut none_existent_path = root();
|
let mut none_existent_path = root();
|
||||||
none_existent_path.push("none-existent.nu");
|
none_existent_path.push("none-existent.nu");
|
||||||
@ -784,7 +672,7 @@ mod tests {
|
|||||||
params: serde_json::to_value(GotoDefinitionParams {
|
params: serde_json::to_value(GotoDefinitionParams {
|
||||||
text_document_position_params: TextDocumentPositionParams {
|
text_document_position_params: TextDocumentPositionParams {
|
||||||
text_document: TextDocumentIdentifier {
|
text_document: TextDocumentIdentifier {
|
||||||
uri: Url::from_file_path(none_existent_path).unwrap(),
|
uri: path_to_uri(&none_existent_path),
|
||||||
},
|
},
|
||||||
position: Position {
|
position: Position {
|
||||||
line: 0,
|
line: 0,
|
||||||
@ -811,16 +699,15 @@ mod tests {
|
|||||||
assert_json_eq!(result, serde_json::json!(null));
|
assert_json_eq!(result, serde_json::json!(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_unchecked(client_connection: &Connection, uri: Url) -> lsp_server::Notification {
|
pub fn open_unchecked(client_connection: &Connection, uri: Uri) -> lsp_server::Notification {
|
||||||
open(client_connection, uri).unwrap()
|
open(client_connection, uri).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(
|
pub fn open(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Url,
|
uri: Uri,
|
||||||
) -> Result<lsp_server::Notification, String> {
|
) -> Result<lsp_server::Notification, String> {
|
||||||
let text =
|
let text = std::fs::read_to_string(uri_to_path(&uri)).map_err(|e| e.to_string())?;
|
||||||
std::fs::read_to_string(uri.to_file_path().unwrap()).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
@ -852,7 +739,7 @@ mod tests {
|
|||||||
|
|
||||||
pub fn update(
|
pub fn update(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Url,
|
uri: Uri,
|
||||||
text: String,
|
text: String,
|
||||||
range: Option<Range>,
|
range: Option<Range>,
|
||||||
) -> lsp_server::Notification {
|
) -> lsp_server::Notification {
|
||||||
@ -863,7 +750,7 @@ mod tests {
|
|||||||
method: DidChangeTextDocument::METHOD.to_string(),
|
method: DidChangeTextDocument::METHOD.to_string(),
|
||||||
params: serde_json::to_value(DidChangeTextDocumentParams {
|
params: serde_json::to_value(DidChangeTextDocumentParams {
|
||||||
text_document: lsp_types::VersionedTextDocumentIdentifier {
|
text_document: lsp_types::VersionedTextDocumentIdentifier {
|
||||||
uri,
|
uri: uri.clone(),
|
||||||
version: 2,
|
version: 2,
|
||||||
},
|
},
|
||||||
content_changes: vec![TextDocumentContentChangeEvent {
|
content_changes: vec![TextDocumentContentChangeEvent {
|
||||||
@ -891,7 +778,7 @@ mod tests {
|
|||||||
|
|
||||||
fn goto_definition(
|
fn goto_definition(
|
||||||
client_connection: &Connection,
|
client_connection: &Connection,
|
||||||
uri: Url,
|
uri: Uri,
|
||||||
line: u32,
|
line: u32,
|
||||||
character: u32,
|
character: u32,
|
||||||
) -> Message {
|
) -> Message {
|
||||||
@ -920,13 +807,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_variable() {
|
fn goto_definition_of_variable() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -951,13 +838,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_command() {
|
fn goto_definition_of_command() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -981,50 +868,18 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_command_utf8() {
|
fn goto_definition_of_command_unicode() {
|
||||||
let (client_connection, _recv) =
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
initialize_language_server(Some(vec!["utf-8".to_string(), "utf-16".to_string()]));
|
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("command_unicode.nu");
|
script.push("command_unicode.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 4, 1);
|
let resp = goto_definition(&client_connection, script.clone(), 4, 2);
|
||||||
let result = if let Message::Response(response) = resp {
|
|
||||||
response.result
|
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_json_eq!(
|
|
||||||
result,
|
|
||||||
serde_json::json!({
|
|
||||||
"uri": script,
|
|
||||||
"range": {
|
|
||||||
"start": { "line": 0, "character": 28 },
|
|
||||||
"end": { "line": 2, "character": 1 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn goto_definition_of_command_utf16() {
|
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
|
||||||
|
|
||||||
let mut script = fixtures();
|
|
||||||
script.push("lsp");
|
|
||||||
script.push("goto");
|
|
||||||
script.push("command_unicode.nu");
|
|
||||||
let script = Url::from_file_path(script).unwrap();
|
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
|
||||||
|
|
||||||
let resp = goto_definition(&client_connection, script.clone(), 4, 1);
|
|
||||||
let result = if let Message::Response(response) = resp {
|
let result = if let Message::Response(response) = resp {
|
||||||
response.result
|
response.result
|
||||||
} else {
|
} else {
|
||||||
@ -1045,13 +900,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn goto_definition_of_command_parameter() {
|
fn goto_definition_of_command_parameter() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("goto");
|
script.push("goto");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1074,7 +929,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hover(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
|
pub fn hover(client_connection: &Connection, uri: Uri, line: u32, character: u32) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
@ -1099,13 +954,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_variable() {
|
fn hover_on_variable() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1126,13 +981,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_custom_command() {
|
fn hover_on_custom_command() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1156,13 +1011,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_str_join() {
|
fn hover_on_str_join() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1184,7 +1039,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message {
|
fn complete(client_connection: &Connection, uri: Uri, line: u32, character: u32) -> Message {
|
||||||
client_connection
|
client_connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Request(lsp_server::Request {
|
.send(Message::Request(lsp_server::Request {
|
||||||
@ -1211,13 +1066,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_on_variable() {
|
fn complete_on_variable() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1248,13 +1103,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_command_with_space() {
|
fn complete_command_with_space() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1285,54 +1140,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_command_with_utf8_line() {
|
fn complete_command_with_line() {
|
||||||
let (client_connection, _recv) =
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
initialize_language_server(Some(vec!["utf-8".to_string()]));
|
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("utf_pipeline.nu");
|
script.push("utf_pipeline.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
|
||||||
|
|
||||||
let resp = complete(&client_connection, script, 0, 14);
|
|
||||||
let result = if let Message::Response(response) = resp {
|
|
||||||
response.result
|
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_json_eq!(
|
|
||||||
result,
|
|
||||||
serde_json::json!([
|
|
||||||
{
|
|
||||||
"label": "str trim",
|
|
||||||
"detail": "Trim whitespace or specific character.",
|
|
||||||
"textEdit": {
|
|
||||||
"range": {
|
|
||||||
"start": { "line": 0, "character": 9 },
|
|
||||||
"end": { "line": 0, "character": 14 },
|
|
||||||
},
|
|
||||||
"newText": "str trim"
|
|
||||||
},
|
|
||||||
"kind": 3
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn complete_command_with_utf16_line() {
|
|
||||||
let (client_connection, _recv) =
|
|
||||||
initialize_language_server(Some(vec!["utf-16".to_string()]));
|
|
||||||
|
|
||||||
let mut script = fixtures();
|
|
||||||
script.push("lsp");
|
|
||||||
script.push("completion");
|
|
||||||
script.push("utf_pipeline.nu");
|
|
||||||
let script = Url::from_file_path(script).unwrap();
|
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -1364,13 +1179,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn complete_keyword() {
|
fn complete_keyword() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("completion");
|
script.push("completion");
|
||||||
script.push("keyword.nu");
|
script.push("keyword.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::{
|
notification::{DidChangeTextDocument, DidOpenTextDocument, DidSaveTextDocument, Notification},
|
||||||
DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification,
|
DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Uri,
|
||||||
},
|
|
||||||
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Url,
|
|
||||||
};
|
};
|
||||||
use ropey::Rope;
|
|
||||||
|
|
||||||
use crate::LanguageServer;
|
use crate::LanguageServer;
|
||||||
|
|
||||||
@ -12,85 +9,29 @@ impl LanguageServer {
|
|||||||
pub(crate) fn handle_lsp_notification(
|
pub(crate) fn handle_lsp_notification(
|
||||||
&mut self,
|
&mut self,
|
||||||
notification: lsp_server::Notification,
|
notification: lsp_server::Notification,
|
||||||
) -> Option<Url> {
|
) -> Option<Uri> {
|
||||||
|
self.docs
|
||||||
|
.listen(notification.method.as_str(), ¬ification.params);
|
||||||
match notification.method.as_str() {
|
match notification.method.as_str() {
|
||||||
DidOpenTextDocument::METHOD => Self::handle_notification_payload::<
|
DidOpenTextDocument::METHOD => {
|
||||||
DidOpenTextDocumentParams,
|
let params: DidOpenTextDocumentParams =
|
||||||
_,
|
serde_json::from_value(notification.params.clone())
|
||||||
>(notification, |param| {
|
.expect("Expect receive DidOpenTextDocumentParams");
|
||||||
if let Ok(file_path) = param.text_document.uri.to_file_path() {
|
|
||||||
let rope = Rope::from_str(¶m.text_document.text);
|
|
||||||
self.ropes.insert(file_path, rope);
|
|
||||||
Some(param.text_document.uri)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
DidChangeTextDocument::METHOD => {
|
|
||||||
Self::handle_notification_payload::<DidChangeTextDocumentParams, _>(
|
|
||||||
notification,
|
|
||||||
|params| self.update_rope(params),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
DidCloseTextDocument::METHOD => Self::handle_notification_payload::<
|
|
||||||
DidCloseTextDocumentParams,
|
|
||||||
_,
|
|
||||||
>(notification, |param| {
|
|
||||||
if let Ok(file_path) = param.text_document.uri.to_file_path() {
|
|
||||||
self.ropes.remove(&file_path);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_notification_payload<P, H>(
|
|
||||||
notification: lsp_server::Notification,
|
|
||||||
mut param_handler: H,
|
|
||||||
) -> Option<Url>
|
|
||||||
where
|
|
||||||
P: serde::de::DeserializeOwned,
|
|
||||||
H: FnMut(P) -> Option<Url>,
|
|
||||||
{
|
|
||||||
if let Ok(params) = serde_json::from_value::<P>(notification.params) {
|
|
||||||
param_handler(params)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_rope(&mut self, params: DidChangeTextDocumentParams) -> Option<Url> {
|
|
||||||
if let Ok(file_path) = params.text_document.uri.to_file_path() {
|
|
||||||
for content_change in params.content_changes.into_iter() {
|
|
||||||
let entry = self.ropes.entry(file_path.clone());
|
|
||||||
match content_change.range {
|
|
||||||
Some(range) => {
|
|
||||||
entry.and_modify(|rope| {
|
|
||||||
let start = Self::lsp_position_to_location(
|
|
||||||
&range.start,
|
|
||||||
rope,
|
|
||||||
&self.position_encoding,
|
|
||||||
);
|
|
||||||
let end = Self::lsp_position_to_location(
|
|
||||||
&range.end,
|
|
||||||
rope,
|
|
||||||
&self.position_encoding,
|
|
||||||
);
|
|
||||||
|
|
||||||
rope.remove(start..end);
|
|
||||||
rope.insert(start, &content_change.text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
entry.and_modify(|r| *r = Rope::from_str(&content_change.text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(params.text_document.uri)
|
Some(params.text_document.uri)
|
||||||
} else {
|
}
|
||||||
None
|
DidSaveTextDocument::METHOD => {
|
||||||
|
let params: DidSaveTextDocumentParams =
|
||||||
|
serde_json::from_value(notification.params.clone())
|
||||||
|
.expect("Expect receive DidSaveTextDocumentParams");
|
||||||
|
Some(params.text_document.uri)
|
||||||
|
}
|
||||||
|
DidChangeTextDocument::METHOD => {
|
||||||
|
let params: DidChangeTextDocumentParams =
|
||||||
|
serde_json::from_value(notification.params.clone())
|
||||||
|
.expect("Expect receive DidChangeTextDocumentParams");
|
||||||
|
Some(params.text_document.uri)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,20 +40,21 @@ impl LanguageServer {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use assert_json_diff::assert_json_eq;
|
use assert_json_diff::assert_json_eq;
|
||||||
use lsp_server::Message;
|
use lsp_server::Message;
|
||||||
use lsp_types::{Range, Url};
|
use lsp_types::Range;
|
||||||
use nu_test_support::fs::fixtures;
|
use nu_test_support::fs::fixtures;
|
||||||
|
|
||||||
|
use crate::path_to_uri;
|
||||||
use crate::tests::{hover, initialize_language_server, open, open_unchecked, update};
|
use crate::tests::{hover, initialize_language_server, open, open_unchecked, update};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_correct_documentation_on_let() {
|
fn hover_correct_documentation_on_let() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("var.nu");
|
script.push("var.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
|
|
||||||
@ -136,13 +78,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_command_after_full_content_change() {
|
fn hover_on_command_after_full_content_change() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
update(
|
update(
|
||||||
@ -177,13 +119,13 @@ hello"#,
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_on_command_after_partial_content_change() {
|
fn hover_on_command_after_partial_content_change() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("hover");
|
script.push("hover");
|
||||||
script.push("command.nu");
|
script.push("command.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
open_unchecked(&client_connection, script.clone());
|
open_unchecked(&client_connection, script.clone());
|
||||||
update(
|
update(
|
||||||
@ -222,13 +164,13 @@ hello"#,
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_document_with_utf_char() {
|
fn open_document_with_utf_char() {
|
||||||
let (client_connection, _recv) = initialize_language_server(None);
|
let (client_connection, _recv) = initialize_language_server();
|
||||||
|
|
||||||
let mut script = fixtures();
|
let mut script = fixtures();
|
||||||
script.push("lsp");
|
script.push("lsp");
|
||||||
script.push("notifications");
|
script.push("notifications");
|
||||||
script.push("issue_11522.nu");
|
script.push("issue_11522.nu");
|
||||||
let script = Url::from_file_path(script).unwrap();
|
let script = path_to_uri(&script);
|
||||||
|
|
||||||
let result = open(&client_connection, script);
|
let result = open(&client_connection, script);
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ fn main() -> Result<()> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state)?
|
LanguageServer::initialize_stdio_connection(engine_state)?.serve_requests()?
|
||||||
} else if let Some(commands) = parsed_nu_cli_args.commands.clone() {
|
} else if let Some(commands) = parsed_nu_cli_args.commands.clone() {
|
||||||
run_commands(
|
run_commands(
|
||||||
&mut engine_state,
|
&mut engine_state,
|
||||||
|
Loading…
Reference in New Issue
Block a user