1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00

fix(lsp): pass fmt options to completion requests (#20184)

Fixes https://github.com/denoland/vscode_deno/issues/856.
This commit is contained in:
Nayeem Rahman 2023-08-17 15:46:11 +01:00 committed by Divy Srivastava
parent 7f8ceeb928
commit 98165dcee3
4 changed files with 213 additions and 19 deletions

View file

@ -69,6 +69,7 @@ use super::text;
use super::tsc; use super::tsc;
use super::tsc::Assets; use super::tsc::Assets;
use super::tsc::AssetsSnapshot; use super::tsc::AssetsSnapshot;
use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer; use super::tsc::TsServer;
use super::urls; use super::urls;
use super::urls::LspClientUrl; use super::urls::LspClientUrl;
@ -1899,6 +1900,7 @@ impl Inner {
line_index.offset_tsc(diagnostic.range.start)? line_index.offset_tsc(diagnostic.range.start)?
..line_index.offset_tsc(diagnostic.range.end)?, ..line_index.offset_tsc(diagnostic.range.end)?,
codes, codes,
(&self.fmt_options.options).into(),
) )
.await; .await;
for action in actions { for action in actions {
@ -2007,7 +2009,11 @@ impl Inner {
})?; })?;
let combined_code_actions = self let combined_code_actions = self
.ts_server .ts_server
.get_combined_code_fix(self.snapshot(), &code_action_data) .get_combined_code_fix(
self.snapshot(),
&code_action_data,
(&self.fmt_options.options).into(),
)
.await?; .await?;
if combined_code_actions.commands.is_some() { if combined_code_actions.commands.is_some() {
error!("Deno does not support code actions with commands."); error!("Deno does not support code actions with commands.");
@ -2047,6 +2053,7 @@ impl Inner {
.get_edits_for_refactor( .get_edits_for_refactor(
self.snapshot(), self.snapshot(),
action_data.specifier, action_data.specifier,
(&self.fmt_options.options).into(),
line_index.offset_tsc(action_data.range.start)? line_index.offset_tsc(action_data.range.start)?
..line_index.offset_tsc(action_data.range.end)?, ..line_index.offset_tsc(action_data.range.end)?,
action_data.refactor_name, action_data.refactor_name,
@ -2402,6 +2409,7 @@ impl Inner {
trigger_character, trigger_character,
trigger_kind, trigger_kind,
}, },
(&self.fmt_options.options).into(),
) )
.await; .await;
@ -2436,9 +2444,13 @@ impl Inner {
})?; })?;
if let Some(data) = &data.tsc { if let Some(data) = &data.tsc {
let specifier = &data.specifier; let specifier = &data.specifier;
let args = GetCompletionDetailsArgs {
format_code_settings: Some((&self.fmt_options.options).into()),
..data.into()
};
let result = self let result = self
.ts_server .ts_server
.get_completion_details(self.snapshot(), data.into()) .get_completion_details(self.snapshot(), args)
.await; .await;
match result { match result {
Ok(maybe_completion_info) => { Ok(maybe_completion_info) => {

View file

@ -21,6 +21,7 @@ use super::urls::LspClientUrl;
use super::urls::LspUrlMap; use super::urls::LspUrlMap;
use super::urls::INVALID_SPECIFIER; use super::urls::INVALID_SPECIFIER;
use crate::args::FmtOptionsConfig;
use crate::args::TsConfig; use crate::args::TsConfig;
use crate::cache::HttpCache; use crate::cache::HttpCache;
use crate::lsp::cache::CacheMetadata; use crate::lsp::cache::CacheMetadata;
@ -95,6 +96,35 @@ type Request = (
CancellationToken, CancellationToken,
); );
/// Relevant subset of https://github.com/denoland/deno/blob/80331d1fe5b85b829ac009fdc201c128b3427e11/cli/tsc/dts/typescript.d.ts#L6658.
#[derive(Clone, Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FormatCodeSettings {
convert_tabs_to_spaces: Option<bool>,
indent_size: Option<u8>,
semicolons: Option<SemicolonPreference>,
}
impl From<&FmtOptionsConfig> for FormatCodeSettings {
fn from(config: &FmtOptionsConfig) -> Self {
FormatCodeSettings {
convert_tabs_to_spaces: Some(!config.use_tabs.unwrap_or(false)),
indent_size: Some(config.indent_width.unwrap_or(2)),
semicolons: match config.semi_colons {
Some(false) => Some(SemicolonPreference::Remove),
_ => Some(SemicolonPreference::Insert),
},
}
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum SemicolonPreference {
Insert,
Remove,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TsServer(mpsc::UnboundedSender<Request>); pub struct TsServer(mpsc::UnboundedSender<Request>);
@ -202,9 +232,15 @@ impl TsServer {
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
range: Range<u32>, range: Range<u32>,
codes: Vec<String>, codes: Vec<String>,
format_code_settings: FormatCodeSettings,
) -> Vec<CodeFixAction> { ) -> Vec<CodeFixAction> {
let req = let req = RequestMethod::GetCodeFixes((
RequestMethod::GetCodeFixes((specifier, range.start, range.end, codes)); specifier,
range.start,
range.end,
codes,
format_code_settings,
));
match self.request(snapshot, req).await { match self.request(snapshot, req).await {
Ok(items) => items, Ok(items) => items,
Err(err) => { Err(err) => {
@ -243,10 +279,12 @@ impl TsServer {
&self, &self,
snapshot: Arc<StateSnapshot>, snapshot: Arc<StateSnapshot>,
code_action_data: &CodeActionData, code_action_data: &CodeActionData,
format_code_settings: FormatCodeSettings,
) -> Result<CombinedCodeActions, LspError> { ) -> Result<CombinedCodeActions, LspError> {
let req = RequestMethod::GetCombinedCodeFix(( let req = RequestMethod::GetCombinedCodeFix((
code_action_data.specifier.clone(), code_action_data.specifier.clone(),
json!(code_action_data.fix_id.clone()), json!(code_action_data.fix_id.clone()),
format_code_settings,
)); ));
self.request(snapshot, req).await.map_err(|err| { self.request(snapshot, req).await.map_err(|err| {
log::error!("Unable to get combined fix from TypeScript: {}", err); log::error!("Unable to get combined fix from TypeScript: {}", err);
@ -258,12 +296,14 @@ impl TsServer {
&self, &self,
snapshot: Arc<StateSnapshot>, snapshot: Arc<StateSnapshot>,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
format_code_settings: FormatCodeSettings,
range: Range<u32>, range: Range<u32>,
refactor_name: String, refactor_name: String,
action_name: String, action_name: String,
) -> Result<RefactorEditInfo, LspError> { ) -> Result<RefactorEditInfo, LspError> {
let req = RequestMethod::GetEditsForRefactor(( let req = RequestMethod::GetEditsForRefactor((
specifier, specifier,
format_code_settings,
TextSpan { TextSpan {
start: range.start, start: range.start,
length: range.end - range.start, length: range.end - range.start,
@ -330,8 +370,14 @@ impl TsServer {
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
position: u32, position: u32,
options: GetCompletionsAtPositionOptions, options: GetCompletionsAtPositionOptions,
format_code_settings: FormatCodeSettings,
) -> Option<CompletionInfo> { ) -> Option<CompletionInfo> {
let req = RequestMethod::GetCompletions((specifier, position, options)); let req = RequestMethod::GetCompletions((
specifier,
position,
options,
format_code_settings,
));
match self.request(snapshot, req).await { match self.request(snapshot, req).await {
Ok(maybe_info) => maybe_info, Ok(maybe_info) => maybe_info,
Err(err) => { Err(err) => {
@ -3542,6 +3588,8 @@ pub struct GetCompletionDetailsArgs {
pub position: u32, pub position: u32,
pub name: String, pub name: String,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub format_code_settings: Option<FormatCodeSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>, pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub preferences: Option<UserPreferences>, pub preferences: Option<UserPreferences>,
@ -3557,6 +3605,7 @@ impl From<&CompletionItemData> for GetCompletionDetailsArgs {
name: item_data.name.clone(), name: item_data.name.clone(),
source: item_data.source.clone(), source: item_data.source.clone(),
preferences: None, preferences: None,
format_code_settings: None,
data: item_data.data.clone(), data: item_data.data.clone(),
} }
} }
@ -3586,15 +3635,30 @@ enum RequestMethod {
/// Retrieve the possible refactor info for a range of a file. /// Retrieve the possible refactor info for a range of a file.
GetApplicableRefactors((ModuleSpecifier, TextSpan, String)), GetApplicableRefactors((ModuleSpecifier, TextSpan, String)),
/// Retrieve the refactor edit info for a range. /// Retrieve the refactor edit info for a range.
GetEditsForRefactor((ModuleSpecifier, TextSpan, String, String)), GetEditsForRefactor(
(
ModuleSpecifier,
FormatCodeSettings,
TextSpan,
String,
String,
),
),
/// Retrieve code fixes for a range of a file with the provided error codes. /// Retrieve code fixes for a range of a file with the provided error codes.
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>)), GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>, FormatCodeSettings)),
/// Get completion information at a given position (IntelliSense). /// Get completion information at a given position (IntelliSense).
GetCompletions((ModuleSpecifier, u32, GetCompletionsAtPositionOptions)), GetCompletions(
(
ModuleSpecifier,
u32,
GetCompletionsAtPositionOptions,
FormatCodeSettings,
),
),
/// Get details about a specific completion entry. /// Get details about a specific completion entry.
GetCompletionDetails(GetCompletionDetailsArgs), GetCompletionDetails(GetCompletionDetailsArgs),
/// Retrieve the combined code fixes for a fix id for a module. /// Retrieve the combined code fixes for a fix id for a module.
GetCombinedCodeFix((ModuleSpecifier, Value)), GetCombinedCodeFix((ModuleSpecifier, Value, FormatCodeSettings)),
/// Get declaration information for a specific position. /// Get declaration information for a specific position.
GetDefinition((ModuleSpecifier, u32)), GetDefinition((ModuleSpecifier, u32)),
/// Return diagnostics for given file. /// Return diagnostics for given file.
@ -3680,6 +3744,7 @@ impl RequestMethod {
}), }),
RequestMethod::GetEditsForRefactor(( RequestMethod::GetEditsForRefactor((
specifier, specifier,
format_code_settings,
span, span,
refactor_name, refactor_name,
action_name, action_name,
@ -3687,6 +3752,7 @@ impl RequestMethod {
"id": id, "id": id,
"method": "getEditsForRefactor", "method": "getEditsForRefactor",
"specifier": state.denormalize_specifier(specifier), "specifier": state.denormalize_specifier(specifier),
"formatCodeSettings": format_code_settings,
"range": { "pos": span.start, "end": span.start + span.length}, "range": { "pos": span.start, "end": span.start + span.length},
"refactorName": refactor_name, "refactorName": refactor_name,
"actionName": action_name, "actionName": action_name,
@ -3696,6 +3762,7 @@ impl RequestMethod {
start_pos, start_pos,
end_pos, end_pos,
error_codes, error_codes,
format_code_settings,
)) => json!({ )) => json!({
"id": id, "id": id,
"method": "getCodeFixes", "method": "getCodeFixes",
@ -3703,25 +3770,37 @@ impl RequestMethod {
"startPosition": start_pos, "startPosition": start_pos,
"endPosition": end_pos, "endPosition": end_pos,
"errorCodes": error_codes, "errorCodes": error_codes,
"formatCodeSettings": format_code_settings,
}), }),
RequestMethod::GetCombinedCodeFix((specifier, fix_id)) => json!({ RequestMethod::GetCombinedCodeFix((
specifier,
fix_id,
format_code_settings,
)) => json!({
"id": id, "id": id,
"method": "getCombinedCodeFix", "method": "getCombinedCodeFix",
"specifier": state.denormalize_specifier(specifier), "specifier": state.denormalize_specifier(specifier),
"fixId": fix_id, "fixId": fix_id,
"formatCodeSettings": format_code_settings,
}), }),
RequestMethod::GetCompletionDetails(args) => json!({ RequestMethod::GetCompletionDetails(args) => json!({
"id": id, "id": id,
"method": "getCompletionDetails", "method": "getCompletionDetails",
"args": args "args": args
}), }),
RequestMethod::GetCompletions((specifier, position, preferences)) => { RequestMethod::GetCompletions((
specifier,
position,
preferences,
format_code_settings,
)) => {
json!({ json!({
"id": id, "id": id,
"method": "getCompletions", "method": "getCompletions",
"specifier": state.denormalize_specifier(specifier), "specifier": state.denormalize_specifier(specifier),
"position": position, "position": position,
"preferences": preferences, "preferences": preferences,
"formatCodeSettings": format_code_settings,
}) })
} }
RequestMethod::GetDefinition((specifier, position)) => json!({ RequestMethod::GetDefinition((specifier, position)) => json!({
@ -4589,6 +4668,7 @@ mod tests {
trigger_character: Some(".".to_string()), trigger_character: Some(".".to_string()),
trigger_kind: None, trigger_kind: None,
}, },
Default::default(),
)), )),
Default::default(), Default::default(),
); );
@ -4605,6 +4685,7 @@ mod tests {
name: "log".to_string(), name: "log".to_string(),
source: None, source: None,
preferences: None, preferences: None,
format_code_settings: None,
data: None, data: None,
}), }),
Default::default(), Default::default(),
@ -4700,6 +4781,105 @@ mod tests {
); );
} }
#[test]
fn test_completions_fmt() {
let fixture_a = r#"
console.log(someLongVaria)
"#;
let fixture_b = r#"
export const someLongVariable = 1
"#;
let line_index = LineIndex::new(fixture_a);
let position = line_index
.offset_tsc(lsp::Position {
line: 1,
character: 33,
})
.unwrap();
let temp_dir = TempDir::new();
let (mut runtime, state_snapshot, _) = setup(
&temp_dir,
false,
json!({
"target": "esnext",
"module": "esnext",
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
&[
("file:///a.ts", fixture_a, 1, LanguageId::TypeScript),
("file:///b.ts", fixture_b, 1, LanguageId::TypeScript),
],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
&mut runtime,
state_snapshot.clone(),
RequestMethod::GetDiagnostics(vec![specifier.clone()]),
Default::default(),
);
assert!(result.is_ok());
let fmt_options_config = FmtOptionsConfig {
semi_colons: Some(false),
..Default::default()
};
let result = request(
&mut runtime,
state_snapshot.clone(),
RequestMethod::GetCompletions((
specifier.clone(),
position,
GetCompletionsAtPositionOptions {
user_preferences: UserPreferences {
allow_incomplete_completions: Some(true),
allow_text_changes_in_new_files: Some(true),
include_completions_for_module_exports: Some(true),
include_completions_with_insert_text: Some(true),
..Default::default()
},
..Default::default()
},
(&fmt_options_config).into(),
)),
Default::default(),
)
.unwrap();
let info: CompletionInfo = serde_json::from_value(result).unwrap();
let entry = info
.entries
.iter()
.find(|e| &e.name == "someLongVariable")
.unwrap();
let result = request(
&mut runtime,
state_snapshot,
RequestMethod::GetCompletionDetails(GetCompletionDetailsArgs {
specifier,
position,
name: entry.name.clone(),
source: entry.source.clone(),
preferences: None,
format_code_settings: Some((&fmt_options_config).into()),
data: entry.data.clone(),
}),
Default::default(),
)
.unwrap();
let details: CompletionEntryDetails =
serde_json::from_value(result).unwrap();
let actions = details.code_actions.unwrap();
let action = actions
.iter()
.find(|a| &a.description == r#"Add import from "./b.ts""#)
.unwrap();
let changes = action.changes.first().unwrap();
let change = changes.text_changes.first().unwrap();
assert_eq!(
change.new_text,
"import { someLongVariable } from \"./b.ts\"\n"
);
}
#[test] #[test]
fn test_update_import_statement() { fn test_update_import_statement() {
let fixtures = vec![ let fixtures = vec![

View file

@ -1035,10 +1035,8 @@ delete Object.prototype.__proto__;
languageService.getEditsForRefactor( languageService.getEditsForRefactor(
request.specifier, request.specifier,
{ {
indentSize: 2, ...request.formatCodeSettings,
indentStyle: ts.IndentStyle.Smart, indentStyle: ts.IndentStyle.Smart,
semicolons: ts.SemicolonPreference.Insert,
convertTabsToSpaces: true,
insertSpaceBeforeAndAfterBinaryOperators: true, insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterCommaDelimiter: true, insertSpaceAfterCommaDelimiter: true,
}, },
@ -1060,9 +1058,8 @@ delete Object.prototype.__proto__;
request.endPosition, request.endPosition,
request.errorCodes.map((v) => Number(v)), request.errorCodes.map((v) => Number(v)),
{ {
indentSize: 2, ...request.formatCodeSettings,
indentStyle: ts.IndentStyle.Block, indentStyle: ts.IndentStyle.Block,
semicolons: ts.SemicolonPreference.Insert,
}, },
{ {
quotePreference: "double", quotePreference: "double",
@ -1080,9 +1077,8 @@ delete Object.prototype.__proto__;
}, },
request.fixId, request.fixId,
{ {
indentSize: 2, ...request.formatCodeSettings,
indentStyle: ts.IndentStyle.Block, indentStyle: ts.IndentStyle.Block,
semicolons: ts.SemicolonPreference.Insert,
}, },
{ {
quotePreference: "double", quotePreference: "double",
@ -1100,7 +1096,7 @@ delete Object.prototype.__proto__;
request.args.specifier, request.args.specifier,
request.args.position, request.args.position,
request.args.name, request.args.name,
{}, request.args.formatCodeSettings ?? {},
request.args.source, request.args.source,
request.args.preferences, request.args.preferences,
request.args.data, request.args.data,
@ -1114,6 +1110,7 @@ delete Object.prototype.__proto__;
request.specifier, request.specifier,
request.position, request.position,
request.preferences, request.preferences,
request.formatCodeSettings,
), ),
); );
} }

View file

@ -121,6 +121,7 @@ declare global {
interface GetEditsForRefactor extends BaseLanguageServerRequest { interface GetEditsForRefactor extends BaseLanguageServerRequest {
method: "getEditsForRefactor"; method: "getEditsForRefactor";
specifier: string; specifier: string;
formatCodeSettings: ts.FormatCodeSettings;
range: ts.TextRange; range: ts.TextRange;
refactorName: string; refactorName: string;
actionName: string; actionName: string;
@ -132,6 +133,7 @@ declare global {
startPosition: number; startPosition: number;
endPosition: number; endPosition: number;
errorCodes: string[]; errorCodes: string[];
formatCodeSettings: ts.FormatCodeSettings;
} }
interface GetCombinedCodeFix extends BaseLanguageServerRequest { interface GetCombinedCodeFix extends BaseLanguageServerRequest {
@ -139,6 +141,7 @@ declare global {
specifier: string; specifier: string;
// deno-lint-ignore ban-types // deno-lint-ignore ban-types
fixId: {}; fixId: {};
formatCodeSettings: ts.FormatCodeSettings;
} }
interface GetCompletionDetails extends BaseLanguageServerRequest { interface GetCompletionDetails extends BaseLanguageServerRequest {
@ -147,6 +150,7 @@ declare global {
specifier: string; specifier: string;
position: number; position: number;
name: string; name: string;
formatCodeSettings: ts.FormatCodeSettings;
source?: string; source?: string;
preferences?: ts.UserPreferences; preferences?: ts.UserPreferences;
data?: ts.CompletionEntryData; data?: ts.CompletionEntryData;
@ -158,6 +162,7 @@ declare global {
specifier: string; specifier: string;
position: number; position: number;
preferences: ts.GetCompletionsAtPositionOptions; preferences: ts.GetCompletionsAtPositionOptions;
formatCodeSettings: ts.FormatCodeSettings;
} }
interface GetDiagnosticsRequest extends BaseLanguageServerRequest { interface GetDiagnosticsRequest extends BaseLanguageServerRequest {