diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index fbea0b506..61ff1137e 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -8,8 +8,14 @@ import { VersionedTextDocumentIdentifier, WorkspaceEdit } from 'vscode-languageserver'; -import { Document, mapRangeToOriginal, isRangeInTag, isInTag } from '../../../lib/documents'; -import { pathToUrl, flatten, isNotNullOrUndefined } from '../../../utils'; +import { + Document, + mapRangeToOriginal, + isRangeInTag, + isInTag, + getLineAtPosition +} from '../../../lib/documents'; +import { pathToUrl, flatten, isNotNullOrUndefined, modifyLines } from '../../../utils'; import { CodeActionsProvider } from '../../interfaces'; import { SnapshotFragment, SvelteSnapshotFragment } from '../DocumentSnapshot'; import { LSAndTSDocResolver } from '../LSAndTSDocResolver'; @@ -84,7 +90,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { mapRangeToOriginal(fragment, convertRange(fragment, edit.span)) ); - return TextEdit.replace(range, edit.newText); + return TextEdit.replace( + range, + this.fixIndentationOfImports(edit.newText, range, document) + ); }) ); }) @@ -99,6 +108,22 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ]; } + private fixIndentationOfImports(edit: string, range: Range, document: Document): string { + // "Organize Imports" will have edits that delete all imports by return empty edits + // and one edit which contains all the organized imports. Fix indentation + // of that one by prepending all lines with the indentation of the first line. + if (!edit || range.start.character === 0) { + return edit; + } + + const line = getLineAtPosition(range.start, document.getText()); + const leadingChars = line.substring(0, range.start.character); + if (leadingChars.trim() !== '') { + return edit; + } + return modifyLines(edit, (line, idx) => (idx === 0 || !line ? line : leadingChars + line)); + } + private checkRemoveImportCodeActionRange( edit: ts.TextChange, fragment: SnapshotFragment, diff --git a/packages/language-server/src/utils.ts b/packages/language-server/src/utils.ts index a4a597f04..be736d82e 100644 --- a/packages/language-server/src/utils.ts +++ b/packages/language-server/src/utils.ts @@ -107,3 +107,22 @@ export function getRegExpMatches(regex: RegExp, str: string) { } return matches; } + +/** + * Function to modify each line of a text, preserving the line break style (`\n` or `\r\n`) + */ +export function modifyLines( + text: string, + replacementFn: (line: string, lineIdx: number) => string +): string { + let idx = 0; + return text + .split('\r\n') + .map((l1) => + l1 + .split('\n') + .map((line) => replacementFn(line, idx++)) + .join('\n') + ) + .join('\r\n'); +} diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index e6fa7c1cf..c55c75ed3 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -264,7 +264,7 @@ describe('CodeActionsProvider', () => { edits: [ { // eslint-disable-next-line max-len - newText: "import A from './A';\nimport { c } from './c';\n", + newText: "import A from './A';\n import { c } from './c';\n", range: { start: { line: 1, @@ -339,7 +339,7 @@ describe('CodeActionsProvider', () => { edits: [ { newText: - "import { _,_d } from 'svelte-i18n';\nimport { _e } from 'svelte-i18n1';\n", + "import { _,_d } from 'svelte-i18n';\n import { _e } from 'svelte-i18n1';\n", range: { end: { character: 0, diff --git a/packages/language-server/test/utils.test.ts b/packages/language-server/test/utils.test.ts index e5e0402be..f0e748e87 100644 --- a/packages/language-server/test/utils.test.ts +++ b/packages/language-server/test/utils.test.ts @@ -1,4 +1,4 @@ -import { isBeforeOrEqualToPosition, regexLastIndexOf } from '../src/utils'; +import { isBeforeOrEqualToPosition, modifyLines, regexLastIndexOf } from '../src/utils'; import { Position } from 'vscode-languageserver'; import * as assert from 'assert'; @@ -43,4 +43,22 @@ describe('utils', () => { assert.equal(regexLastIndexOf(' hello', /[\W\s]/g), 17); }); }); + + describe('#modifyLines', () => { + it('should work', () => { + assert.equal( + modifyLines('a\nb\r\nc\nd', (line) => 1 + line), + '1a\n1b\r\n1c\n1d' + ); + }); + + it('should pass correct line numbers', () => { + const idxs: number[] = []; + modifyLines('a\nb\r\nc\nd', (_, idx) => { + idxs.push(idx); + return _; + }); + assert.deepStrictEqual(idxs, [0, 1, 2, 3]); + }); + }); });