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:
parent
7f8ceeb928
commit
98165dcee3
4 changed files with 213 additions and 19 deletions
|
@ -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) => {
|
||||
|
|
198
cli/lsp/tsc.rs
198
cli/lsp/tsc.rs
|
@ -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![
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
5
cli/tsc/compiler.d.ts
vendored
5
cli/tsc/compiler.d.ts
vendored
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue