Skip to content

Commit

Permalink
Merge pull request #162 from atom-community/additionalTextEdits
Browse files Browse the repository at this point in the history
  • Loading branch information
aminya authored Jun 13, 2021
2 parents 2317a7d + bf63a49 commit 9b6a753
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 5 deletions.
22 changes: 21 additions & 1 deletion lib/adapters/autocomplete-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import {
ServerCapabilities,
TextEdit,
} from "../languageclient"
import ApplyEditAdapter from "./apply-edit-adapter"
import { Point, TextEditor } from "atom"
import * as ac from "atom/autocomplete-plus"
import { Suggestion, TextSuggestion, SnippetSuggestion } from "../types/autocomplete-extended"
import { Suggestion, TextSuggestion, SnippetSuggestion, SuggestionBase } from "../types/autocomplete-extended"

/**
* Defines the behavior of suggestion acceptance. Assume you have "cons|ole" in the editor ( `|` is the cursor position)
Expand Down Expand Up @@ -437,6 +438,7 @@ export default class AutocompleteAdapter {
suggestion.displayText = item.label
suggestion.type = AutocompleteAdapter.completionKindToSuggestionType(item.kind)
AutocompleteAdapter.applyDetailsToSuggestion(item, suggestion)
suggestion.completionItem = item
}

public static applyDetailsToSuggestion(item: CompletionItem, suggestion: Suggestion): void {
Expand Down Expand Up @@ -494,6 +496,24 @@ export default class AutocompleteAdapter {
suggestion.text = textEdit.newText
}

/**
* Handle additional text edits after a suggestion insert, e.g. `additionalTextEdits`.
*
* `additionalTextEdits` are An optional array of additional text edits that are applied when selecting this
* completion. Edits must not overlap (including the same insert position) with the main edit nor with themselves.
*
* Additional text edits should be used to change text unrelated to the current cursor position (for example adding an
* import statement at the top of the file if the completion item will insert an unqualified type).
*/
public static applyAdditionalTextEdits(event: ac.SuggestionInsertedEvent): void {
const suggestion = event.suggestion as SuggestionBase
const additionalEdits = suggestion.completionItem?.additionalTextEdits
const buffer = event.editor.getBuffer()

ApplyEditAdapter.applyEdits(buffer, Convert.convertLsTextEdits(additionalEdits))
buffer.groupLastChanges()
}

/**
* Public: Adds a snippet to the suggestion if the CompletionItem contains snippet-formatted text
*
Expand Down
5 changes: 4 additions & 1 deletion lib/auto-languageclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,10 @@ export default class AutoLanguageClient {
excludeLowerPriority: false,
filterSuggestions: true,
getSuggestions: this.getSuggestions.bind(this),
onDidInsertSuggestion: this.onDidInsertSuggestion.bind(this),
onDidInsertSuggestion: (event) => {
AutocompleteAdapter.applyAdditionalTextEdits(event)
this.onDidInsertSuggestion(event)
},
getSuggestionDetailsOnSelect: this.getSuggestionDetailsOnSelect.bind(this),
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export default class Convert {
* @param textEdits The language server protocol {atomIde.TextEdit} objects to convert.
* @returns An {Array} of Atom {atomIde.TextEdit} objects.
*/
public static convertLsTextEdits(textEdits: ls.TextEdit[] | null): atomIde.TextEdit[] {
public static convertLsTextEdits(textEdits?: ls.TextEdit[] | null): atomIde.TextEdit[] {
return (textEdits || []).map(Convert.convertLsTextEdit)
}

Expand Down
6 changes: 5 additions & 1 deletion lib/types/autocomplete-extended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// See this PR: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51284

import * as ac from "atom/autocomplete-plus"
import { CompletionItem } from "../languageclient"

/** Adds LSP specific properties to the Atom SuggestionBase type */
interface SuggestionBase extends ac.SuggestionBase {
export interface SuggestionBase extends ac.SuggestionBase {
/**
* A string that is used when filtering and sorting a set of completion items with a prefix present. When `falsy` the
* [displayText](#ac.SuggestionBase.displayText) is used. When no prefix, the `sortText` property is used.
Expand All @@ -16,6 +17,9 @@ interface SuggestionBase extends ac.SuggestionBase {
* the suggestion was gathered from.
*/
customReplacmentPrefix?: string

/** Original completion item, if available */
completionItem?: CompletionItem
}
export type TextSuggestion = SuggestionBase & ac.TextSuggestion
export type SnippetSuggestion = SuggestionBase & ac.SnippetSuggestion
Expand Down
47 changes: 46 additions & 1 deletion test/adapters/autocomplete-adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import AutoCompleteAdapter, { grammarScopeToAutoCompleteSelector } from "../../lib/adapters/autocomplete-adapter"
import { ActiveServer } from "../../lib/server-manager.js"
import * as ls from "../../lib/languageclient"
import { CompositeDisposable, Point } from "atom"
import { CompositeDisposable, Point, TextEditor } from "atom"
import * as ac from "atom/autocomplete-plus"

import { createSpyConnection, createFakeEditor } from "../helpers.js"
import { TextSuggestion, SnippetSuggestion } from "../../lib/types/autocomplete-extended"
import { CompletionItem, Range } from "../../lib/languageclient"
import { dirname, join } from "path"
import { Convert } from "../../lib/main"

function createRequest({
prefix = "",
Expand Down Expand Up @@ -596,6 +599,48 @@ describe("AutoCompleteAdapter", () => {
})
})

describe("applyAdditionalTextEdits", () => {
it("1", async () => {
const newText = "hello world"
const range = Range.create({ line: 1, character: 0 }, { line: 1, character: 0 + newText.length })
const additionalTextEdits = [
{
range,
newText,
},
]
const results = await getSuggestionsMock(
[
{
label: "align",
sortText: "a",
additionalTextEdits,
},
],
customRequest,
undefined,
undefined,
true
)
expect(results[0].displayText).toBe("align")
expect((results[0] as TextSuggestion).text).toBe("align")
expect((results[0] as TextSuggestion).completionItem).toEqual({
label: "align",
sortText: "a",
additionalTextEdits,
})
const editor = (await atom.workspace.open(join(dirname(__dirname), "fixtures", "hello.js"))) as TextEditor
const suggestionInsertedEvent = {
editor,
triggerPosition: new Point(1, 0), // has no effect?
suggestion: results[0],
} as ac.SuggestionInsertedEvent
AutoCompleteAdapter.applyAdditionalTextEdits(suggestionInsertedEvent)

expect(editor.getBuffer().getTextInRange(Convert.lsRangeToAtomRange(range))).toBe(newText)
})
})

describe("applies the change if shouldReplace is false", () => {
it("1", async () => {
const results = await getSuggestionsMock(
Expand Down

0 comments on commit 9b6a753

Please sign in to comment.