Skip to content

Commit fbda94a

Browse files
authored
Do not allow client to modify whitespace for auto insert edits (#8588)
2 parents b7eb4e0 + 4cca94a commit fbda94a

File tree

5 files changed

+87
-18
lines changed

5 files changed

+87
-18
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
- Debug from .csproj and .sln [#5876](https://github.com/dotnet/vscode-csharp/issues/5876)
55

66
# 2.91.x
7-
* Bump Roslyn to 5.0.0-2.25458.1 (PR: [#8593](https://github.com/dotnet/vscode-csharp/pull/8593))
7+
* Bump Roslyn to 5.0.0-2.25458.10 (PR: [#8588](https://github.com/dotnet/vscode-csharp/pull/8588))
8+
* Move brace adjustment on enter to on auto insert in LSP(PR: [#80075](https://github.com/dotnet/roslyn/pull/80075))
9+
* Avoid throwing when obsolete overload of GetUpdatesAsync is invoked with empty array(PR: [#80161](https://github.com/dotnet/roslyn/pull/80161))
10+
* Bump patch version of MSBuild packages(PR: [#80156](https://github.com/dotnet/roslyn/pull/80156))
11+
* Include category in Hot Reload log messages(PR: [#80160](https://github.com/dotnet/roslyn/pull/80160))
812
* Store client's version for open docs (PR: [#80064](https://github.com/dotnet/roslyn/pull/80064))
913
* Pass global properties and the binary log path via RPC to BuildHost (PR: [#80094](https://github.com/dotnet/roslyn/pull/80094))
1014
* Don't switch runtime / design time solutions if cohosting is on (PR: [#80065](https://github.com/dotnet/roslyn/pull/80065))

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"workspace"
4141
],
4242
"defaults": {
43-
"roslyn": "5.0.0-2.25458.1",
43+
"roslyn": "5.0.0-2.25458.10",
4444
"omniSharp": "1.39.14",
4545
"razor": "10.0.0-preview.25454.5",
4646
"razorOmnisharp": "7.0.0-preview.23363.1",
@@ -128,7 +128,7 @@
128128
"@types/semver": "7.3.13",
129129
"@types/tmp": "0.0.33",
130130
"@types/uuid": "^9.0.1",
131-
"@types/vscode": "1.93.0",
131+
"@types/vscode": "1.98.0",
132132
"@types/yauzl": "2.10.0",
133133
"@typescript-eslint/eslint-plugin": "^8.19.0",
134134
"@typescript-eslint/parser": "^8.19.0",
@@ -688,7 +688,7 @@
688688
}
689689
],
690690
"engines": {
691-
"vscode": "^1.93.0"
691+
"vscode": "^1.98.0"
692692
},
693693
"activationEvents": [
694694
"onDebugInitialConfigurations",

src/lsptoolshost/autoInsert/onAutoInsert.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
import * as vscode from 'vscode';
77

8-
import { FormattingOptions, LanguageClient, TextDocumentIdentifier } from 'vscode-languageclient/node';
8+
import {
9+
FormattingOptions,
10+
InsertTextFormat,
11+
LanguageClient,
12+
TextDocumentIdentifier,
13+
} from 'vscode-languageclient/node';
914
import * as RoslynProtocol from '../server/roslynProtocol';
1015
import { RoslynLanguageServer } from '../server/roslynLanguageServer';
1116

@@ -50,7 +55,21 @@ export function registerOnAutoInsert(languageServer: RoslynLanguageServer, langu
5055
}
5156

5257
// Regular expression to match all whitespace characters except the newline character
53-
const changeTrimmed = change.text.replace(/[^\S\n]+/g, '');
58+
let changeTrimmed = change.text.replace(/[^\S\n]+/g, '');
59+
60+
// If the change is empty after removing whitespace, we don't need to process it.
61+
if (changeTrimmed.length === 0) {
62+
return;
63+
}
64+
65+
// When hitting enter between braces, we can end up with two new lines added (one to move the cursor down to an empty line,
66+
// and another to move the close brace to a new line below that). We want to detect that edit as a single new line trigger.
67+
//
68+
// Since we already removed all whitespace except new lines above, we can just trim the string to remove new lines as well
69+
// and check if there is anything left. If not, we know the change is just whitespace and new lines and can set the trigger to the new line character.
70+
if (changeTrimmed.trim() === '') {
71+
changeTrimmed = '\n';
72+
}
5473

5574
if (!vsTriggerCharacters.includes(changeTrimmed)) {
5675
return;
@@ -98,12 +117,20 @@ async function applyAutoInsertEdit(
98117
const textEdit = response._vs_textEdit;
99118
const startPosition = new vscode.Position(textEdit.range.start.line, textEdit.range.start.character);
100119
const endPosition = new vscode.Position(textEdit.range.end.line, textEdit.range.end.character);
101-
const docComment = new vscode.SnippetString(textEdit.newText);
102-
const code: any = vscode;
103-
const textEdits = [new code.SnippetTextEdit(new vscode.Range(startPosition, endPosition), docComment)];
120+
121+
let textEdits: (vscode.TextEdit | vscode.SnippetTextEdit)[] = [];
122+
if (response._vs_textEditFormat === InsertTextFormat.Snippet) {
123+
const docComment = new vscode.SnippetString(textEdit.newText);
124+
const edit = vscode.SnippetTextEdit.replace(new vscode.Range(startPosition, endPosition), docComment);
125+
// Roslyn already formats the snippet correctly - we don't want the client to try and change the whitespace.
126+
edit.keepWhitespace = true;
127+
textEdits = [edit];
128+
} else {
129+
textEdits = [vscode.TextEdit.replace(new vscode.Range(startPosition, endPosition), textEdit.newText)];
130+
}
131+
104132
const edit = new vscode.WorkspaceEdit();
105133
edit.set(uri, textEdits);
106-
107134
const applied = vscode.workspace.applyEdit(edit);
108135
if (!applied) {
109136
throw new Error('Tried to apply an edit but an error occurred.');

test/lsptoolshost/integrationTests/onAutoInsert.integration.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
waitForExpectedResult,
1616
} from './integrationHelpers';
1717
import { describe, beforeAll, beforeEach, afterAll, test, expect, afterEach } from '@jest/globals';
18+
import { EOL } from 'os';
1819

1920
describe(`OnAutoInsert Tests`, () => {
2021
beforeAll(async () => {
@@ -72,6 +73,43 @@ describe(`OnAutoInsert Tests`, () => {
7273
}
7374
);
7475
});
76+
77+
test('Enter inside braces fixes brace lines', async () => {
78+
await vscode.window.activeTextEditor!.edit((editBuilder) => {
79+
editBuilder.insert(new vscode.Position(11, 15), '\n');
80+
});
81+
82+
// OnAutoInsert is triggered by the change event but completes asynchronously, so wait for the buffer to be updated.
83+
84+
const expectedLines = [
85+
'class DocComments',
86+
'{',
87+
' //',
88+
' string M(int param1, string param2)',
89+
' {',
90+
' return null;',
91+
' }',
92+
'',
93+
' /// <summary>',
94+
'',
95+
' /// </summary>',
96+
' void M2()',
97+
' {',
98+
' ',
99+
' }',
100+
'}',
101+
'',
102+
];
103+
104+
await waitForExpectedResult<string | undefined>(
105+
async () => vscode.window.activeTextEditor?.document.getText(),
106+
10000,
107+
100,
108+
(input) => {
109+
expect(input).toBe(expectedLines.join(EOL));
110+
}
111+
);
112+
});
75113
});
76114

77115
function normalizeNewlines(text: string | undefined): string | undefined {

0 commit comments

Comments
 (0)