// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::lsp_extensions; use super::state::ServerState; use super::state::ServerStateSnapshot; use super::text; use super::tsc; use super::utils; use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::ModuleSpecifier; use dprint_plugin_typescript as dprint; use lsp_types::CompletionParams; use lsp_types::CompletionResponse; use lsp_types::DocumentFormattingParams; use lsp_types::DocumentHighlight; use lsp_types::DocumentHighlightParams; use lsp_types::GotoDefinitionParams; use lsp_types::GotoDefinitionResponse; use lsp_types::Hover; use lsp_types::HoverParams; use lsp_types::Location; use lsp_types::ReferenceParams; use lsp_types::TextEdit; use std::path::PathBuf; fn get_line_index( state: &mut ServerState, specifier: &ModuleSpecifier, ) -> Result, AnyError> { let line_index = if specifier.as_url().scheme() == "asset" { let server_state = state.snapshot(); if let Some(source) = tsc::get_asset(specifier, &mut state.ts_runtime, &server_state)? { text::index_lines(&source) } else { return Err(custom_error( "NotFound", format!("asset source missing: {}", specifier), )); } } else { let file_cache = state.file_cache.read().unwrap(); if let Some(file_id) = file_cache.lookup(specifier) { let file_text = file_cache.get_contents(file_id)?; text::index_lines(&file_text) } else { let mut sources = state.sources.write().unwrap(); if let Some(line_index) = sources.get_line_index(specifier) { line_index } else { return Err(custom_error( "NotFound", format!("source for specifier not found: {}", specifier), )); } } }; Ok(line_index) } pub fn handle_formatting( state: ServerStateSnapshot, params: DocumentFormattingParams, ) -> Result>, AnyError> { let specifier = utils::normalize_url(params.text_document.uri.clone()); let file_cache = state.file_cache.read().unwrap(); let file_id = file_cache.lookup(&specifier).unwrap(); let file_text = file_cache.get_contents(file_id)?; let file_path = if let Ok(file_path) = params.text_document.uri.to_file_path() { file_path } else { PathBuf::from(params.text_document.uri.path()) }; let config = dprint::configuration::ConfigurationBuilder::new() .deno() .build(); // TODO(@kitsonk) this could be handled better in `cli/tools/fmt.rs` in the // future. let new_text = dprint::format_text(&file_path, &file_text, &config) .map_err(|e| custom_error("FormatError", e))?; let text_edits = text::get_edits(&file_text, &new_text); if text_edits.is_empty() { Ok(None) } else { Ok(Some(text_edits)) } } pub fn handle_document_highlight( state: &mut ServerState, params: DocumentHighlightParams, ) -> Result>, AnyError> { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); let line_index = get_line_index(state, &specifier)?; let server_state = state.snapshot(); let files_to_search = vec![specifier.clone()]; let maybe_document_highlights: Option> = serde_json::from_value(tsc::request( &mut state.ts_runtime, &server_state, tsc::RequestMethod::GetDocumentHighlights(( specifier, text::to_char_pos( &line_index, params.text_document_position_params.position, ), files_to_search, )), )?)?; if let Some(document_highlights) = maybe_document_highlights { Ok(Some( document_highlights .into_iter() .map(|dh| dh.to_highlight(&line_index)) .flatten() .collect(), )) } else { Ok(None) } } pub fn handle_goto_definition( state: &mut ServerState, params: GotoDefinitionParams, ) -> Result, AnyError> { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); let line_index = get_line_index(state, &specifier)?; let server_state = state.snapshot(); let maybe_definition: Option = serde_json::from_value(tsc::request( &mut state.ts_runtime, &server_state, tsc::RequestMethod::GetDefinition(( specifier, text::to_char_pos( &line_index, params.text_document_position_params.position, ), )), )?)?; if let Some(definition) = maybe_definition { Ok( definition .to_definition(&line_index, |s| get_line_index(state, &s).unwrap()), ) } else { Ok(None) } } pub fn handle_hover( state: &mut ServerState, params: HoverParams, ) -> Result, AnyError> { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); let line_index = get_line_index(state, &specifier)?; let server_state = state.snapshot(); let maybe_quick_info: Option = serde_json::from_value(tsc::request( &mut state.ts_runtime, &server_state, tsc::RequestMethod::GetQuickInfo(( specifier, text::to_char_pos( &line_index, params.text_document_position_params.position, ), )), )?)?; if let Some(quick_info) = maybe_quick_info { Ok(Some(quick_info.to_hover(&line_index))) } else { Ok(None) } } pub fn handle_completion( state: &mut ServerState, params: CompletionParams, ) -> Result, AnyError> { let specifier = utils::normalize_url(params.text_document_position.text_document.uri); let line_index = get_line_index(state, &specifier)?; let server_state = state.snapshot(); let maybe_completion_info: Option = serde_json::from_value(tsc::request( &mut state.ts_runtime, &server_state, tsc::RequestMethod::GetCompletions(( specifier, text::to_char_pos(&line_index, params.text_document_position.position), tsc::UserPreferences { // TODO(lucacasonato): enable this. see https://github.com/denoland/deno/pull/8651 include_completions_with_insert_text: Some(false), ..Default::default() }, )), )?)?; if let Some(completions) = maybe_completion_info { Ok(Some(completions.into_completion_response(&line_index))) } else { Ok(None) } } pub fn handle_references( state: &mut ServerState, params: ReferenceParams, ) -> Result>, AnyError> { let specifier = utils::normalize_url(params.text_document_position.text_document.uri); let line_index = get_line_index(state, &specifier)?; let server_state = state.snapshot(); let maybe_references: Option> = serde_json::from_value(tsc::request( &mut state.ts_runtime, &server_state, tsc::RequestMethod::GetReferences(( specifier, text::to_char_pos(&line_index, params.text_document_position.position), )), )?)?; if let Some(references) = maybe_references { let mut results = Vec::new(); for reference in references { if !params.context.include_declaration && reference.is_definition { continue; } let reference_specifier = ModuleSpecifier::resolve_url(&reference.file_name).unwrap(); let line_index = get_line_index(state, &reference_specifier)?; results.push(reference.to_location(&line_index)); } Ok(Some(results)) } else { Ok(None) } } pub fn handle_virtual_text_document( state: &mut ServerState, params: lsp_extensions::VirtualTextDocumentParams, ) -> Result { let specifier = utils::normalize_url(params.text_document.uri); let url = specifier.as_url(); let contents = if url.as_str() == "deno:///status.md" { let file_cache = state.file_cache.read().unwrap(); format!( r#"# Deno Language Server Status - Documents in memory: {} "#, file_cache.len() ) } else { match url.scheme() { "asset" => { let server_state = state.snapshot(); if let Some(text) = tsc::get_asset(&specifier, &mut state.ts_runtime, &server_state)? { text } else { error!("Missing asset: {}", specifier); "".to_string() } } _ => { let mut sources = state.sources.write().unwrap(); if let Some(text) = sources.get_text(&specifier) { text } else { return Err(custom_error( "NotFound", format!("The cached sources was not found: {}", specifier), )); } } } }; Ok(contents) }