1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -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::Assets;
use super::tsc::AssetsSnapshot;
use super::tsc::GetCompletionDetailsArgs;
use super::tsc::TsServer;
use super::urls;
use super::urls::LspClientUrl;
@ -1899,6 +1900,7 @@ impl Inner {
line_index.offset_tsc(diagnostic.range.start)?
..line_index.offset_tsc(diagnostic.range.end)?,
codes,
(&self.fmt_options.options).into(),
)
.await;
for action in actions {
@ -2007,7 +2009,11 @@ impl Inner {
})?;
let combined_code_actions = self
.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?;
if combined_code_actions.commands.is_some() {
error!("Deno does not support code actions with commands.");
@ -2047,6 +2053,7 @@ impl Inner {
.get_edits_for_refactor(
self.snapshot(),
action_data.specifier,
(&self.fmt_options.options).into(),
line_index.offset_tsc(action_data.range.start)?
..line_index.offset_tsc(action_data.range.end)?,
action_data.refactor_name,
@ -2402,6 +2409,7 @@ impl Inner {
trigger_character,
trigger_kind,
},
(&self.fmt_options.options).into(),
)
.await;
@ -2436,9 +2444,13 @@ impl Inner {
})?;
if let Some(data) = &data.tsc {
let specifier = &data.specifier;
let args = GetCompletionDetailsArgs {
format_code_settings: Some((&self.fmt_options.options).into()),
..data.into()
};
let result = self
.ts_server
.get_completion_details(self.snapshot(), data.into())
.get_completion_details(self.snapshot(), args)
.await;
match result {
Ok(maybe_completion_info) => {

View file

@ -21,6 +21,7 @@ use super::urls::LspClientUrl;
use super::urls::LspUrlMap;
use super::urls::INVALID_SPECIFIER;
use crate::args::FmtOptionsConfig;
use crate::args::TsConfig;
use crate::cache::HttpCache;
use crate::lsp::cache::CacheMetadata;
@ -95,6 +96,35 @@ type Request = (
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)]
pub struct TsServer(mpsc::UnboundedSender<Request>);
@ -202,9 +232,15 @@ impl TsServer {
specifier: ModuleSpecifier,
range: Range<u32>,
codes: Vec<String>,
format_code_settings: FormatCodeSettings,
) -> Vec<CodeFixAction> {
let req =
RequestMethod::GetCodeFixes((specifier, range.start, range.end, codes));
let req = RequestMethod::GetCodeFixes((
specifier,
range.start,
range.end,
codes,
format_code_settings,
));
match self.request(snapshot, req).await {
Ok(items) => items,
Err(err) => {
@ -243,10 +279,12 @@ impl TsServer {
&self,
snapshot: Arc<StateSnapshot>,
code_action_data: &CodeActionData,
format_code_settings: FormatCodeSettings,
) -> Result<CombinedCodeActions, LspError> {
let req = RequestMethod::GetCombinedCodeFix((
code_action_data.specifier.clone(),
json!(code_action_data.fix_id.clone()),
format_code_settings,
));
self.request(snapshot, req).await.map_err(|err| {
log::error!("Unable to get combined fix from TypeScript: {}", err);
@ -258,12 +296,14 @@ impl TsServer {
&self,
snapshot: Arc<StateSnapshot>,
specifier: ModuleSpecifier,
format_code_settings: FormatCodeSettings,
range: Range<u32>,
refactor_name: String,
action_name: String,
) -> Result<RefactorEditInfo, LspError> {
let req = RequestMethod::GetEditsForRefactor((
specifier,
format_code_settings,
TextSpan {
start: range.start,
length: range.end - range.start,
@ -330,8 +370,14 @@ impl TsServer {
specifier: ModuleSpecifier,
position: u32,
options: GetCompletionsAtPositionOptions,
format_code_settings: FormatCodeSettings,
) -> 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 {
Ok(maybe_info) => maybe_info,
Err(err) => {
@ -3542,6 +3588,8 @@ pub struct GetCompletionDetailsArgs {
pub position: u32,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub format_code_settings: Option<FormatCodeSettings>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preferences: Option<UserPreferences>,
@ -3557,6 +3605,7 @@ impl From<&CompletionItemData> for GetCompletionDetailsArgs {
name: item_data.name.clone(),
source: item_data.source.clone(),
preferences: None,
format_code_settings: None,
data: item_data.data.clone(),
}
}
@ -3586,15 +3635,30 @@ enum RequestMethod {
/// Retrieve the possible refactor info for a range of a file.
GetApplicableRefactors((ModuleSpecifier, TextSpan, String)),
/// 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.
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>)),
GetCodeFixes((ModuleSpecifier, u32, u32, Vec<String>, FormatCodeSettings)),
/// Get completion information at a given position (IntelliSense).
GetCompletions((ModuleSpecifier, u32, GetCompletionsAtPositionOptions)),
GetCompletions(
(
ModuleSpecifier,
u32,
GetCompletionsAtPositionOptions,
FormatCodeSettings,
),
),
/// Get details about a specific completion entry.
GetCompletionDetails(GetCompletionDetailsArgs),
/// 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.
GetDefinition((ModuleSpecifier, u32)),
/// Return diagnostics for given file.
@ -3680,6 +3744,7 @@ impl RequestMethod {
}),
RequestMethod::GetEditsForRefactor((
specifier,
format_code_settings,
span,
refactor_name,
action_name,
@ -3687,6 +3752,7 @@ impl RequestMethod {
"id": id,
"method": "getEditsForRefactor",
"specifier": state.denormalize_specifier(specifier),
"formatCodeSettings": format_code_settings,
"range": { "pos": span.start, "end": span.start + span.length},
"refactorName": refactor_name,
"actionName": action_name,
@ -3696,6 +3762,7 @@ impl RequestMethod {
start_pos,
end_pos,
error_codes,
format_code_settings,
)) => json!({
"id": id,
"method": "getCodeFixes",
@ -3703,25 +3770,37 @@ impl RequestMethod {
"startPosition": start_pos,
"endPosition": end_pos,
"errorCodes": error_codes,
"formatCodeSettings": format_code_settings,
}),
RequestMethod::GetCombinedCodeFix((specifier, fix_id)) => json!({
RequestMethod::GetCombinedCodeFix((
specifier,
fix_id,
format_code_settings,
)) => json!({
"id": id,
"method": "getCombinedCodeFix",
"specifier": state.denormalize_specifier(specifier),
"fixId": fix_id,
"formatCodeSettings": format_code_settings,
}),
RequestMethod::GetCompletionDetails(args) => json!({
"id": id,
"method": "getCompletionDetails",
"args": args
}),
RequestMethod::GetCompletions((specifier, position, preferences)) => {
RequestMethod::GetCompletions((
specifier,
position,
preferences,
format_code_settings,
)) => {
json!({
"id": id,
"method": "getCompletions",
"specifier": state.denormalize_specifier(specifier),
"position": position,
"preferences": preferences,
"formatCodeSettings": format_code_settings,
})
}
RequestMethod::GetDefinition((specifier, position)) => json!({
@ -4589,6 +4668,7 @@ mod tests {
trigger_character: Some(".".to_string()),
trigger_kind: None,
},
Default::default(),
)),
Default::default(),
);
@ -4605,6 +4685,7 @@ mod tests {
name: "log".to_string(),
source: None,
preferences: None,
format_code_settings: None,
data: None,
}),
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]
fn test_update_import_statement() {
let fixtures = vec![

View file

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

View file

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