diff --git a/Cargo.lock b/Cargo.lock index 4a7bc2f76f926..bbeef7738be4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2982,6 +2982,7 @@ name = "nu-lsp" version = "0.86.1" dependencies = [ "assert-json-diff", + "crossbeam-channel", "lsp-server", "lsp-types", "miette", diff --git a/crates/nu-lsp/Cargo.toml b/crates/nu-lsp/Cargo.toml index 8530a69dc9bdd..e5db76407df36 100644 --- a/crates/nu-lsp/Cargo.toml +++ b/crates/nu-lsp/Cargo.toml @@ -13,6 +13,7 @@ nu-parser = { path = "../nu-parser", version = "0.86.1" } nu-protocol = { path = "../nu-protocol", version = "0.86.1" } reedline = { version = "0.25" } +crossbeam-channel = "0.5.8" lsp-types = "0.94.1" lsp-server = "0.7.4" miette = "5.10" diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index 9c94f44d248dd..461a666a08425 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -1,11 +1,19 @@ -use std::{fs::File, io::Cursor, sync::Arc}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; use lsp_server::{Connection, IoThreads, Message, Response, ResponseError}; use lsp_types::{ request::{Completion, GotoDefinition, HoverRequest, Request}, CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind, - OneOf, Range, ServerCapabilities, TextEdit, Url, + OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url, }; use miette::{IntoDiagnostic, Result}; use nu_cli::NuCompleter; @@ -17,6 +25,8 @@ use nu_protocol::{ use reedline::Completer; use ropey::Rope; +mod notification; + #[derive(Debug)] enum Id { Variable(VarId), @@ -27,6 +37,7 @@ enum Id { pub struct LanguageServer { connection: Connection, io_threads: Option, + ropes: BTreeMap, } impl LanguageServer { @@ -42,11 +53,19 @@ impl LanguageServer { Ok(Self { connection, io_threads, + ropes: BTreeMap::new(), }) } - pub fn serve_requests(self, engine_state: EngineState) -> Result<()> { + pub fn serve_requests( + mut self, + engine_state: EngineState, + ctrlc: Arc, + ) -> Result<()> { let server_capabilities = serde_json::to_value(&ServerCapabilities { + text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Kind( + TextDocumentSyncKind::INCREMENTAL, + )), definition_provider: Some(OneOf::Left(true)), hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)), completion_provider: Some(lsp_types::CompletionOptions::default()), @@ -54,12 +73,26 @@ impl LanguageServer { }) .expect("Must be serializable"); + // TODO: raise a PR to add ctrlc support otherwise it is impossible to use ctrl-c to quit + // the process in the initialization stage let _initialization_params = self .connection .initialize(server_capabilities) .into_diagnostic()?; - for msg in &self.connection.receiver { + while !ctrlc.load(Ordering::SeqCst) { + let msg = match self + .connection + .receiver + .recv_timeout(Duration::from_secs(1)) + { + Ok(msg) => msg, + Err(crossbeam_channel::RecvTimeoutError::Timeout) => { + continue; + } + Err(_) => break, + }; + match msg { Message::Request(request) => { if self @@ -71,25 +104,34 @@ impl LanguageServer { } let mut engine_state = engine_state.clone(); - match request.method.as_str() { - GotoDefinition::METHOD => { - self.handle_lsp_request( - &mut engine_state, - request, - Self::goto_definition, - )?; - } - HoverRequest::METHOD => { - self.handle_lsp_request(&mut engine_state, request, Self::hover)?; + let resp = match request.method.as_str() { + GotoDefinition::METHOD => Self::handle_lsp_request( + &mut engine_state, + request, + |engine_state, params| self.goto_definition(engine_state, params), + ), + HoverRequest::METHOD => Self::handle_lsp_request( + &mut engine_state, + request, + |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; } - Completion::METHOD => { - self.handle_lsp_request(&mut engine_state, request, Self::complete)?; - } - _ => {} - } + }; + + self.connection + .sender + .send(Message::Response(resp)) + .into_diagnostic()?; } Message::Response(_) => {} - Message::Notification(_) => {} + Message::Notification(notification) => self.handle_lsp_notification(notification), } } @@ -101,41 +143,36 @@ impl LanguageServer { } fn handle_lsp_request( - &self, engine_state: &mut EngineState, req: lsp_server::Request, - param_handler: H, - ) -> Result<()> + mut param_handler: H, + ) -> Response where P: serde::de::DeserializeOwned, - H: Fn(&mut EngineState, &P) -> Option, + H: FnMut(&mut EngineState, &P) -> Option, R: serde::ser::Serialize, { - let resp = { - match serde_json::from_value::

(req.params) { - Ok(params) => Response { - id: req.id, - result: param_handler(engine_state, ¶ms) - .and_then(|response| serde_json::to_value(response).ok()), - error: None, - }, - - Err(err) => Response { - id: req.id, - result: None, - error: Some(ResponseError { - code: 1, - message: err.to_string(), - data: None, - }), - }, - } - }; - - self.connection - .sender - .send(Message::Response(resp)) - .into_diagnostic() + match serde_json::from_value::

(req.params) { + Ok(params) => Response { + id: req.id, + result: Some( + param_handler(engine_state, ¶ms) + .and_then(|response| serde_json::to_value(response).ok()) + .unwrap_or(serde_json::Value::Null), + ), + error: None, + }, + + Err(err) => Response { + id: req.id, + result: None, + error: Some(ResponseError { + code: 1, + message: err.to_string(), + data: None, + }), + }, + } } fn span_to_range(span: &Span, rope_of_file: &Rope, offset: usize) -> lsp_types::Range { @@ -158,79 +195,84 @@ impl LanguageServer { lsp_types::Range { start, end } } - fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize { + pub fn lsp_position_to_location(position: &lsp_types::Position, rope_of_file: &Rope) -> usize { let line_idx = rope_of_file.line_to_char(position.line as usize); line_idx + position.character as usize } fn find_id( working_set: &mut StateWorkingSet, - file_path: &str, - file: &[u8], + path: &Path, + file: &Rope, location: usize, ) -> Option<(Id, usize, Span)> { - let file_id = working_set.add_file(file_path.to_string(), file); - let offset = working_set.get_span_for_file(file_id).start; - let block = parse(working_set, Some(file_path), file, false); + let file_path = path.to_string_lossy(); + + // TODO: think about passing down the rope into the working_set + let contents = file.bytes().collect::>(); + 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; - for item in flattened { - if location >= item.0.start && location < item.0.end { - match &item.1 { + + for (span, shape) in flattened { + if location >= span.start && location < span.end { + match &shape { FlatShape::Variable(var_id) | FlatShape::VarDecl(var_id) => { - return Some((Id::Variable(*var_id), offset, item.0)); + return Some((Id::Variable(*var_id), offset, span)); } FlatShape::InternalCall(decl_id) => { - return Some((Id::Declaration(*decl_id), offset, item.0)); + return Some((Id::Declaration(*decl_id), offset, span)); } - _ => return Some((Id::Value(item.1), offset, item.0)), + _ => return Some((Id::Value(shape), offset, span)), } } } None } + fn rope<'a, 'b: 'a>(&'b mut self, file_url: &Url) -> Option<(&'a Rope, &'a PathBuf)> { + 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>( + &mut self, engine_state: &'a mut EngineState, - file_path: &str, - ) -> Result<(Vec, StateWorkingSet<'a>)> { - let file = std::fs::read(file_path).into_diagnostic()?; + file_url: &Url, + ) -> Option<(&Rope, &PathBuf, StateWorkingSet<'a>)> { + let (file, path) = self.rope(file_url)?; - engine_state.start_in_file(Some(file_path)); + // TODO: AsPath thingy + engine_state.start_in_file(Some(&path.to_string_lossy())); let working_set = StateWorkingSet::new(engine_state); - Ok((file, working_set)) + Some((file, path, working_set)) } fn goto_definition( + &mut self, engine_state: &mut EngineState, params: &GotoDefinitionParams, ) -> Option { 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 = params - .text_document_position_params - .text_document - .uri - .to_file_path() - .ok()?; - - let file_path = file_path.to_string_lossy(); - - let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?; - let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?; + 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, - &file_path, - &file, - Self::lsp_position_to_location( - ¶ms.text_document_position_params.position, - &rope_of_file, - ), + path, + file, + Self::lsp_position_to_location(¶ms.text_document_position_params.position, file), )?; match id { @@ -242,7 +284,7 @@ impl LanguageServer { if span.start >= *file_start && span.start < *file_end { return Some(GotoDefinitionResponse::Scalar(Location { uri: Url::from_file_path(file_path).ok()?, - range: Self::span_to_range(span, &rope_of_file, *file_start), + range: Self::span_to_range(span, file, *file_start), })); } } @@ -261,11 +303,7 @@ impl LanguageServer { .text_document .uri .clone(), - range: Self::span_to_range( - &var.declaration_span, - &rope_of_file, - *file_start, - ), + range: Self::span_to_range(&var.declaration_span, file, *file_start), })); } } @@ -275,30 +313,20 @@ impl LanguageServer { None } - fn hover(engine_state: &mut EngineState, params: &HoverParams) -> Option { + fn hover(&mut self, engine_state: &mut EngineState, params: &HoverParams) -> Option { 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 = params - .text_document_position_params - .text_document - .uri - .to_file_path() - .ok()?; - - let file_path = file_path.to_string_lossy(); - - let (file, mut working_set) = Self::read_in_file(engine_state, &file_path).ok()?; - let rope_of_file = Rope::from_reader(Cursor::new(&file)).ok()?; + 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, - &file_path, - &file, - Self::lsp_position_to_location( - ¶ms.text_document_position_params.position, - &rope_of_file, - ), + path, + file, + Self::lsp_position_to_location(¶ms.text_document_position_params.position, file), )?; match id { @@ -489,27 +517,23 @@ impl LanguageServer { } fn complete( + &mut self, engine_state: &mut EngineState, params: &CompletionParams, ) -> Option { 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 = params - .text_document_position - .text_document - .uri - .to_file_path() - .ok()?; - - let file_path = file_path.to_string_lossy(); - let rope_of_file = Rope::from_reader(File::open(file_path.as_ref()).ok()?).ok()?; + let (rope_of_file, _, _) = self.read_in_file( + engine_state, + ¶ms.text_document_position.text_document.uri, + )?; let stack = Stack::new(); let mut completer = NuCompleter::new(Arc::new(engine_state.clone()), stack); let location = - Self::lsp_position_to_location(¶ms.text_document_position.position, &rope_of_file); + Self::lsp_position_to_location(¶ms.text_document_position.position, rope_of_file); let results = completer.complete(&rope_of_file.to_string(), location); if results.is_empty() { None @@ -545,15 +569,16 @@ mod tests { use super::*; use assert_json_diff::assert_json_eq; use lsp_types::{ - notification::{Exit, Initialized, Notification}, + notification::{DidOpenTextDocument, Exit, Initialized, Notification}, request::{Completion, GotoDefinition, HoverRequest, Initialize, Request, Shutdown}, - CompletionParams, GotoDefinitionParams, InitializeParams, InitializedParams, - TextDocumentIdentifier, TextDocumentPositionParams, Url, + CompletionParams, DidOpenTextDocumentParams, GotoDefinitionParams, InitializeParams, + InitializedParams, TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, + Url, }; use nu_test_support::fs::{fixtures, root}; use std::sync::mpsc::Receiver; - fn initialize_language_server() -> (Connection, Receiver>) { + pub fn initialize_language_server() -> (Connection, Receiver>) { use std::sync::mpsc; let (client_connection, server_connection) = Connection::memory(); let lsp_server = LanguageServer::initialize_connection(server_connection, None).unwrap(); @@ -562,7 +587,7 @@ mod tests { std::thread::spawn(move || { let engine_state = nu_cmd_lang::create_default_context(); let engine_state = nu_command::add_shell_command_context(engine_state); - send.send(lsp_server.serve_requests(engine_state)) + send.send(lsp_server.serve_requests(engine_state, Arc::new(AtomicBool::new(false)))) }); client_connection @@ -651,16 +676,41 @@ mod tests { .receiver .recv_timeout(std::time::Duration::from_secs(2)) .unwrap(); + let result = if let Message::Response(response) = resp { + response.result + } else { + panic!() + }; - assert!(matches!( - resp, - Message::Response(response) if response.result.is_none() - )); + assert_json_eq!(result, serde_json::json!(null)); } - fn goto_definition(uri: Url, line: u32, character: u32) -> Message { - let (client_connection, _recv) = initialize_language_server(); + pub fn open(client_connection: &Connection, uri: Url) { + let text = std::fs::read_to_string(uri.to_file_path().unwrap()).unwrap(); + client_connection + .sender + .send(Message::Notification(lsp_server::Notification { + method: DidOpenTextDocument::METHOD.to_string(), + params: serde_json::to_value(DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri, + language_id: String::from("nu"), + version: 1, + text, + }, + }) + .unwrap(), + })) + .unwrap(); + } + + fn goto_definition( + client_connection: &Connection, + uri: Url, + line: u32, + character: u32, + ) -> Message { client_connection .sender .send(Message::Request(lsp_server::Request { @@ -686,13 +736,17 @@ mod tests { #[test] fn goto_definition_of_variable() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("goto"); script.push("var.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = goto_definition(script.clone(), 2, 12); + open(&client_connection, script.clone()); + + let resp = goto_definition(&client_connection, script.clone(), 2, 12); let result = if let Message::Response(response) = resp { response.result } else { @@ -713,13 +767,17 @@ mod tests { #[test] fn goto_definition_of_command() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("goto"); script.push("command.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = goto_definition(script.clone(), 4, 1); + open(&client_connection, script.clone()); + + let resp = goto_definition(&client_connection, script.clone(), 4, 1); let result = if let Message::Response(response) = resp { response.result } else { @@ -740,13 +798,17 @@ mod tests { #[test] fn goto_definition_of_command_parameter() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("goto"); script.push("command.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = goto_definition(script.clone(), 1, 14); + open(&client_connection, script.clone()); + + let resp = goto_definition(&client_connection, script.clone(), 1, 14); let result = if let Message::Response(response) = resp { response.result } else { @@ -765,9 +827,7 @@ mod tests { ); } - fn hover(uri: Url, line: u32, character: u32) -> Message { - let (client_connection, _recv) = initialize_language_server(); - + pub fn hover(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message { client_connection .sender .send(Message::Request(lsp_server::Request { @@ -792,13 +852,17 @@ mod tests { #[test] fn hover_on_variable() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("hover"); script.push("var.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = hover(script.clone(), 2, 0); + open(&client_connection, script.clone()); + + let resp = hover(&client_connection, script.clone(), 2, 0); let result = if let Message::Response(response) = resp { response.result } else { @@ -815,13 +879,17 @@ mod tests { #[test] fn hover_on_command() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("hover"); script.push("command.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = hover(script.clone(), 3, 0); + open(&client_connection, script.clone()); + + let resp = hover(&client_connection, script.clone(), 3, 0); let result = if let Message::Response(response) = resp { response.result } else { @@ -839,9 +907,7 @@ mod tests { ); } - fn complete(uri: Url, line: u32, character: u32) -> Message { - let (client_connection, _recv) = initialize_language_server(); - + fn complete(client_connection: &Connection, uri: Url, line: u32, character: u32) -> Message { client_connection .sender .send(Message::Request(lsp_server::Request { @@ -868,13 +934,17 @@ mod tests { #[test] fn complete_on_variable() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("completion"); script.push("var.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = complete(script, 2, 9); + open(&client_connection, script.clone()); + + let resp = complete(&client_connection, script, 2, 9); let result = if let Message::Response(response) = resp { response.result } else { @@ -900,13 +970,17 @@ mod tests { #[test] fn complete_command_with_space() { + let (client_connection, _recv) = initialize_language_server(); + let mut script = fixtures(); script.push("lsp"); script.push("completion"); script.push("command.nu"); let script = Url::from_file_path(script).unwrap(); - let resp = complete(script, 0, 8); + open(&client_connection, script.clone()); + + let resp = complete(&client_connection, script, 0, 8); let result = if let Message::Response(response) = resp { response.result } else { diff --git a/crates/nu-lsp/src/notification.rs b/crates/nu-lsp/src/notification.rs new file mode 100644 index 0000000000000..9a8abd6af78e2 --- /dev/null +++ b/crates/nu-lsp/src/notification.rs @@ -0,0 +1,195 @@ +use lsp_types::{ + notification::{ + DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Notification, + }, + DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, +}; +use ropey::Rope; + +use crate::LanguageServer; + +impl LanguageServer { + pub(crate) fn handle_lsp_notification(&mut self, notification: lsp_server::Notification) { + match notification.method.as_str() { + DidOpenTextDocument::METHOD => Self::handle_notification_payload::< + DidOpenTextDocumentParams, + _, + >(notification, |param| { + 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); + } + }), + DidChangeTextDocument::METHOD => { + Self::handle_notification_payload::( + 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); + } + }), + _ => {} + } + } + + fn handle_notification_payload( + notification: lsp_server::Notification, + mut param_handler: H, + ) where + P: serde::de::DeserializeOwned, + H: FnMut(P), + { + if let Ok(params) = serde_json::from_value::

(notification.params) { + param_handler(params); + } + } + + fn update_rope(&mut self, params: DidChangeTextDocumentParams) { + 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, content_change.range) { + (Some(range), _) => { + entry.and_modify(|rope| { + let start = Self::lsp_position_to_location(&range.start, rope); + let end = Self::lsp_position_to_location(&range.end, rope); + + rope.remove(start..end); + rope.insert(start, &content_change.text); + }); + } + (None, None) => { + entry.and_modify(|r| *r = Rope::from_str(&content_change.text)); + } + _ => {} + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use assert_json_diff::assert_json_eq; + use lsp_server::{Connection, Message}; + use lsp_types::{DidChangeTextDocumentParams, Range, TextDocumentContentChangeEvent, Url}; + use nu_test_support::fs::fixtures; + + use crate::tests::{hover, initialize_language_server, open}; + + fn update(client_connection: &Connection, uri: Url, text: String, range: Option) { + client_connection + .sender + .send(lsp_server::Message::Notification( + lsp_server::Notification { + method: DidChangeTextDocument::METHOD.to_string(), + params: serde_json::to_value(DidChangeTextDocumentParams { + text_document: lsp_types::VersionedTextDocumentIdentifier { + uri, + version: 2, + }, + content_changes: vec![TextDocumentContentChangeEvent { + range, + range_length: None, + text, + }], + }) + .unwrap(), + }, + )) + .unwrap(); + } + + #[test] + fn hover_on_command_after_full_content_change() { + let (client_connection, _recv) = initialize_language_server(); + + let mut script = fixtures(); + script.push("lsp"); + script.push("hover"); + script.push("command.nu"); + let script = Url::from_file_path(script).unwrap(); + + open(&client_connection, script.clone()); + update( + &client_connection, + script.clone(), + String::from( + r#"# Renders some updated greeting message +def hello [] {} + +hello"#, + ), + None, + ); + + let resp = hover(&client_connection, script.clone(), 3, 0); + let result = if let Message::Response(response) = resp { + response.result + } else { + panic!() + }; + + assert_json_eq!( + result, + serde_json::json!({ + "contents": { + "kind": "markdown", + "value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n" + } + }) + ); + } + + #[test] + fn hover_on_command_after_partial_content_change() { + let (client_connection, _recv) = initialize_language_server(); + + let mut script = fixtures(); + script.push("lsp"); + script.push("hover"); + script.push("command.nu"); + let script = Url::from_file_path(script).unwrap(); + + open(&client_connection, script.clone()); + update( + &client_connection, + script.clone(), + String::from("# Renders some updated greeting message"), + Some(Range { + start: lsp_types::Position { + line: 0, + character: 0, + }, + end: lsp_types::Position { + line: 0, + character: 31, + }, + }), + ); + + let resp = hover(&client_connection, script.clone(), 3, 0); + let result = if let Message::Response(response) = resp { + response.result + } else { + panic!() + }; + + assert_json_eq!( + result, + serde_json::json!({ + "contents": { + "kind": "markdown", + "value": "```\n### Signature\n```\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n### Usage\n Renders some updated greeting message\n" + } + }) + ); + } +} diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index 37cc7ff956a4d..679f201b48eab 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -317,6 +317,15 @@ impl<'a> StateWorkingSet<'a> { self.num_virtual_paths() - 1 } + pub fn get_span_for_filename(&self, filename: &str) -> Option { + let (file_id, ..) = self + .files() + .enumerate() + .find(|(_, (fname, _, _))| fname == filename)?; + + Some(self.get_span_for_file(file_id)) + } + pub fn get_span_for_file(&self, file_id: usize) -> Span { let result = self .files() diff --git a/src/main.rs b/src/main.rs index 0040707ecd66b..d842ceabe4999 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,7 +196,7 @@ fn main() -> Result<()> { } if parsed_nu_cli_args.lsp { - return LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state); + return LanguageServer::initialize_stdio_connection()?.serve_requests(engine_state, ctrlc); } // IDE commands