Skip to content

Commit abbdaf7

Browse files
Use correct span when requesting code actions from OmniSharp
Fixes #1605 When calling CodeActionProvider.provideCodeActions(...), VS Code passes the range of the word to the left of the editor caret if there's no selection. This ends up causing refactorings like Extract Method to be offered even when the user hasn't selected any code. Now, we'll check to see if there's actually a selection and use that instead of the range that's given. To fully fix this issue, I've made several changes in OmniSharp: OmniSharp/omnisharp-roslyn#899. Once that goes in, we'll need to update the version of OmniSharp used by C# for VS Code.
1 parent 4dcfa8e commit abbdaf7

File tree

2 files changed

+70
-36
lines changed

2 files changed

+70
-36
lines changed

src/features/codeActionProvider.ts

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55

66
'use strict';
77

8-
import {CodeActionProvider, CodeActionContext, Command, CancellationToken, TextDocument, WorkspaceEdit, TextEdit, Range, Uri, workspace, commands} from 'vscode';
9-
import {OmniSharpServer} from '../omnisharp/server';
8+
import * as vscode from 'vscode';
9+
import { OmniSharpServer } from '../omnisharp/server';
1010
import AbstractProvider from './abstractProvider';
1111
import * as protocol from '../omnisharp/protocol';
12-
import {toRange2} from '../omnisharp/typeConvertion';
12+
import { toRange2 } from '../omnisharp/typeConvertion';
1313
import * as serverUtils from '../omnisharp/utils';
1414

15-
export default class OmnisharpCodeActionProvider extends AbstractProvider implements CodeActionProvider {
15+
export default class CodeActionProvider extends AbstractProvider implements vscode.CodeActionProvider {
1616

1717
private _disabled: boolean;
1818
private _commandId: string;
@@ -21,38 +21,80 @@ export default class OmnisharpCodeActionProvider extends AbstractProvider implem
2121
super(server);
2222
this._commandId = 'omnisharp.runCodeAction';
2323

24-
this._updateEnablement();
25-
let d1 = workspace.onDidChangeConfiguration(this._updateEnablement, this);
26-
let d2 = commands.registerCommand(this._commandId, this._runCodeAction, this);
24+
this._checkOption();
25+
26+
let d1 = vscode.workspace.onDidChangeConfiguration(this._checkOption, this);
27+
let d2 = vscode.commands.registerCommand(this._commandId, this._runCodeAction, this);
2728
this._disposables.push(d1, d2);
2829
}
2930

30-
private _updateEnablement(): void {
31-
let value = workspace.getConfiguration().get('csharp.disableCodeActions', false);
31+
private _checkOption(): void {
32+
let value = vscode.workspace.getConfiguration().get('csharp.disableCodeActions', false);
3233
this._disabled = value;
3334
}
3435

35-
public provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Promise<Command[]> {
36+
public provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): Promise<vscode.Command[]> {
3637
if (this._disabled) {
3738
return;
3839
}
3940

40-
let req: protocol.V2.GetCodeActionsRequest = {
41+
let line: number;
42+
let column: number;
43+
let selection: protocol.V2.Range;
44+
45+
// VS Code will pass the range of the word at the editor caret, even if there isn't a selection.
46+
// To ensure that we don't suggest selection-based refactorings when there isn't a selection, we first
47+
// find the text editor for this document and verify that there is a selection.
48+
let editor = vscode.window.visibleTextEditors.find(e => e.document === document);
49+
if (editor) {
50+
if (editor.selection.isEmpty) {
51+
// The editor does not have a selection. Use the active position of the selection (i.e. the caret).
52+
let active = editor.selection.active;
53+
54+
line = active.line + 1;
55+
column = active.character + 1;
56+
}
57+
else {
58+
// The editor has a selection. Use it.
59+
let start = editor.selection.start;
60+
let end = editor.selection.end;
61+
62+
selection = {
63+
Start: { Line: start.line + 1, Column: start.character + 1 },
64+
End: { Line: end.line + 1, Column: end.character + 1 }
65+
};
66+
}
67+
}
68+
else {
69+
// We couldn't find the editor, so just use the range we were provided.
70+
selection = {
71+
Start: { Line: range.start.line + 1, Column: range.start.character + 1 },
72+
End: { Line: range.end.line + 1, Column: range.end.character + 1 }
73+
};
74+
}
75+
76+
let request: protocol.V2.GetCodeActionsRequest = {
4177
FileName: document.fileName,
42-
Selection: OmnisharpCodeActionProvider._asRange(range)
78+
Line: line,
79+
Column: column,
80+
Selection: selection
4381
};
4482

45-
return serverUtils.getCodeActions(this._server, req, token).then(response => {
83+
return serverUtils.getCodeActions(this._server, request, token).then(response => {
4684
return response.CodeActions.map(codeAction => {
85+
let runRequest: protocol.V2.RunCodeActionRequest = {
86+
FileName: document.fileName,
87+
Line: line,
88+
Column: column,
89+
Selection: selection,
90+
Identifier: codeAction.Identifier,
91+
WantsTextChanges: true
92+
};
93+
4794
return {
4895
title: codeAction.Name,
4996
command: this._commandId,
50-
arguments: [<protocol.V2.RunCodeActionRequest>{
51-
FileName: document.fileName,
52-
Selection: OmnisharpCodeActionProvider._asRange(range),
53-
Identifier: codeAction.Identifier,
54-
WantsTextChanges: true
55-
}]
97+
arguments: [runRequest]
5698
};
5799
});
58100
}, (error) => {
@@ -66,31 +108,23 @@ export default class OmnisharpCodeActionProvider extends AbstractProvider implem
66108

67109
if (response && Array.isArray(response.Changes)) {
68110

69-
let edit = new WorkspaceEdit();
111+
let edit = new vscode.WorkspaceEdit();
70112

71113
for (let change of response.Changes) {
72-
let uri = Uri.file(change.FileName);
73-
let edits: TextEdit[] = [];
114+
let uri = vscode.Uri.file(change.FileName);
115+
let edits: vscode.TextEdit[] = [];
74116
for (let textChange of change.Changes) {
75-
edits.push(TextEdit.replace(toRange2(textChange), textChange.NewText));
117+
edits.push(vscode.TextEdit.replace(toRange2(textChange), textChange.NewText));
76118
}
77119

78120
edit.set(uri, edits);
79121
}
80122

81-
return workspace.applyEdit(edit);
123+
return vscode.workspace.applyEdit(edit);
82124
}
83125

84126
}, (error) => {
85-
return Promise.reject('Problem invoking \'RunCodeAction\' on OmniSharp server: ' + error);
127+
return Promise.reject(`Problem invoking 'RunCodeAction' on OmniSharp server: ${error}`);
86128
});
87129
}
88-
89-
private static _asRange(range: Range): protocol.V2.Range {
90-
let {start, end} = range;
91-
return {
92-
Start: { Line: start.line + 1, Column: start.character + 1 },
93-
End: { Line: end.line + 1, Column: end.character + 1 }
94-
};
95-
}
96-
}
130+
}

src/omnisharp/protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ export namespace V2 {
436436
}
437437

438438
export interface GetCodeActionsRequest extends Request {
439-
Selection: Range;
439+
Selection?: Range;
440440
}
441441

442442
export interface OmniSharpCodeAction {
@@ -450,7 +450,7 @@ export namespace V2 {
450450

451451
export interface RunCodeActionRequest extends Request {
452452
Identifier: string;
453-
Selection: Range;
453+
Selection?: Range;
454454
WantsTextChanges: boolean;
455455
}
456456

0 commit comments

Comments
 (0)