Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

Commit

Permalink
Merge pull request #148 from atom/autocomplete-resolve
Browse files Browse the repository at this point in the history
Resolve implementation for new AutoComplete hook
  • Loading branch information
damieng authored Dec 6, 2017
2 parents 02d8f62 + 9be7924 commit f4c1ba2
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 14 deletions.
3 changes: 3 additions & 0 deletions flow-libs/atom.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,9 @@ type atom$AutocompleteProvider = {
getSuggestions: (
request: atom$AutocompleteRequest,
) => Promise<?Array<atom$AutocompleteSuggestion>>,
getSuggestionDetailsOnFocus: (
suggestion: atom$AutocompleteSuggestion,
) => Promise<?atom$AutocompleteSuggestion>,
disableForSelector?: string,
inclusionPriority?: number,
excludeLowerPriority?: boolean,
Expand Down
50 changes: 39 additions & 11 deletions lib/adapters/autocomplete-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,49 @@ export default class AutocompleteAdapter {
return serverCapabilities.completionProvider != null;
}

// Public: Primary entry point for obtaining suggestions for AutoComplete+ by
// querying the language server.
_lastSuggestions: Map<atom$AutocompleteSuggestion, [CompletionItem, boolean]> = new Map();

// Public: Obtain suggestion list for AutoComplete+ by querying the language server using
// the `textDocument/completion` request.
//
// * `connection` A {LanguageClientConnection} to the language server to query.
// * `request` An {Object} with the AutoComplete+ request to satisfy.
//
// Returns a {Promise} of an {Array} of {Object}s containing the AutoComplete+
// suggestions to display.
// Returns a {Promise} of an {Array} of {atom$AutocompleteSuggestion}s containing the
// AutoComplete+ suggestions to display.
async getSuggestions(
connection: LanguageClientConnection,
request: atom$AutocompleteRequest,
): Promise<Array<atom$AutocompleteSuggestion>> {
const completionItems = await connection.completion(
AutocompleteAdapter.requestToTextDocumentPositionParams(request),
);
return AutocompleteAdapter.completionItemsToSuggestions(completionItems, request);
const items = await connection.completion(AutocompleteAdapter.requestToTextDocumentPositionParams(request));
return this.completionItemsToSuggestions(items, request);
}

// Public: Obtain a complete version of a suggestion with additional information
// the language server can provide by way of the `completionItem/resolve` request.
//
// * `connection` A {LanguageClientConnection} to the language server to query.
// * `request` An {Object} with the AutoComplete+ request to satisfy.
//
// Returns a {Promise} of an {atom$AutocompleteSuggestion} with the resolved AutoComplete+
// suggestion.
async completeSuggestion(
connection: LanguageClientConnection,
suggestion: atom$AutocompleteSuggestion,
request: atom$AutocompleteRequest,
): Promise<atom$AutocompleteSuggestion> {
const originalCompletionItem = this._lastSuggestions.get(suggestion);
if (originalCompletionItem != null && originalCompletionItem[1] === false) {
const resolveCompletionItem = await connection.completionItemResolve(originalCompletionItem[0]);
if (resolveCompletionItem != null) {
const resolvedSuggestion = AutocompleteAdapter.completionItemToSuggestion(resolveCompletionItem, request);
this._lastSuggestions.delete(suggestion);
this._lastSuggestions.set(resolvedSuggestion, [resolveCompletionItem, true]);
return resolvedSuggestion;
}
}

return suggestion;
}

// Public: Create TextDocumentPositionParams to be sent to the language server
Expand All @@ -59,13 +86,14 @@ export default class AutocompleteAdapter {
// * `request` An {Object} with the AutoComplete+ request to use.
//
// Returns an {Array} of AutoComplete+ suggestions ordered by the CompletionItems sortText.
static completionItemsToSuggestions(
completionItemsToSuggestions(
completionItems: Array<CompletionItem> | CompletionList,
request: atom$AutocompleteRequest,
): Array<atom$AutocompleteSuggestion> {
return (Array.isArray(completionItems) ? completionItems : completionItems.items || [])
this._lastSuggestions = new Map((Array.isArray(completionItems) ? completionItems : completionItems.items || [])
.sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label))
.map(s => AutocompleteAdapter.completionItemToSuggestion(s, request));
.map(s => [AutocompleteAdapter.completionItemToSuggestion(s, request), [s, false]]));
return Array.from(this._lastSuggestions.keys());
}

// Public: Convert a language server protocol CompletionItem to an AutoComplete+ suggestion.
Expand Down
20 changes: 20 additions & 0 deletions lib/auto-languageclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default class AutoLanguageClient {
_serverManager: ServerManager;
_linterDelegate: linter$V2IndieDelegate;
_signatureHelpRegistry: ?atomIde$SignatureHelpRegistry;
_lastAutocompleteRequest: ?atom$AutocompleteRequest;

// Available if consumeBusySignal is setup
busySignalService: ?atomIde$BusySignalService;
Expand Down Expand Up @@ -348,10 +349,12 @@ export default class AutoLanguageClient {
suggestionPriority: 2,
excludeLowerPriority: false,
getSuggestions: this.getSuggestions.bind(this),
getSuggestionDetailsOnFocus: this.getSuggestionDetailsOnFocus.bind(this),
};
}

async getSuggestions(request: atom$AutocompleteRequest): Promise<Array<atom$AutocompleteSuggestion>> {
this._lastAutocompleteRequest = request;
const server = await this._serverManager.getServer(request.editor);
if (server == null || !AutocompleteAdapter.canAdapt(server.capabilities)) {
return [];
Expand All @@ -361,6 +364,23 @@ export default class AutoLanguageClient {
return this.autoComplete.getSuggestions(server.connection, request);
}

async getSuggestionDetailsOnFocus(suggestion: atom$AutocompleteSuggestion): Promise<?atom$AutocompleteSuggestion> {
if (this._lastAutocompleteRequest == null || this.autoComplete == null) {
return null;
}

const server = await this._serverManager.getServer(this._lastAutocompleteRequest.editor);
if (server == null || !AutocompleteAdapter.canAdapt(server.capabilities)) {
return null;
}

if (this._lastAutocompleteRequest != null && this.autoComplete != null) {
return this.autoComplete.completeSuggestion(server.connection, suggestion, this._lastAutocompleteRequest);
}

return null;
}

// Definitions via LS documentHighlight and gotoDefinition------------
provideDefinitions(): atomIde$DefinitionProvider {
return {
Expand Down
45 changes: 42 additions & 3 deletions test/adapters/autocomplete-adapter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,42 @@ describe('AutoCompleteAdapter', () => {
});
});

describe('completeSuggestion', () => {
const partialItems = [
{
label: 'label1',
kind: ls.CompletionItemKind.Keyword,
sortText: 'z',
},
{
label: 'label2',
kind: ls.CompletionItemKind.Field,
sortText: 'a',
},
{
label: 'label3',
kind: ls.CompletionItemKind.Variable,
},
];

const fakeLanguageClient = new ls.LanguageClientConnection(createSpyConnection());
sinon.stub(fakeLanguageClient, 'completion').resolves(partialItems);
sinon.stub(fakeLanguageClient, 'completionItemResolve').resolves({
label: 'label3',
kind: ls.CompletionItemKind.Variable,
detail: 'description3',
documentation: 'a very exciting variable',
});

it('resolves suggestions via LSP given an AutoCompleteRequest', async () => {
const autoCompleteAdapter = new AutoCompleteAdapter();
const results: Array<atom$AutocompleteSuggestion> = await autoCompleteAdapter.getSuggestions(fakeLanguageClient, request);
expect(results[2].description).equals(undefined);
const resolvedItem = await autoCompleteAdapter.completeSuggestion(fakeLanguageClient, results[2], request);
expect(resolvedItem.description).equals('a very exciting variable');
});
});

describe('requestToTextDocumentPositionParams', () => {
it('creates a TextDocumentPositionParams from an AutocompleteRequest', () => {
const result = AutoCompleteAdapter.requestToTextDocumentPositionParams(request);
Expand All @@ -69,7 +105,8 @@ describe('AutoCompleteAdapter', () => {

describe('completionItemsToSuggestions', () => {
it('converts LSP CompletionItem array to AutoComplete Suggestions array', () => {
const results = AutoCompleteAdapter.completionItemsToSuggestions(completionItems, request);
const autoCompleteAdapter = new AutoCompleteAdapter();
const results = autoCompleteAdapter.completionItemsToSuggestions(completionItems, request);
expect(results.length).equals(3);
expect(results[0].text).equals('label2');
expect(results[1].description).equals('a very exciting variable');
Expand All @@ -78,14 +115,16 @@ describe('AutoCompleteAdapter', () => {

it('converts LSP CompletionList to AutoComplete Suggestions array', () => {
const completionList = {items: completionItems, isIncomplete: false};
const results = AutoCompleteAdapter.completionItemsToSuggestions(completionList, request);
const autoCompleteAdapter = new AutoCompleteAdapter();
const results = autoCompleteAdapter.completionItemsToSuggestions(completionList, request);
expect(results.length).equals(3);
expect(results[0].description).equals('a very exciting field');
expect(results[1].text).equals('label3');
});

it('converts empty array into an empty AutoComplete Suggestions array', () => {
const results = AutoCompleteAdapter.completionItemsToSuggestions([], request);
const autoCompleteAdapter = new AutoCompleteAdapter();
const results = autoCompleteAdapter.completionItemsToSuggestions([], request);
expect(results.length).equals(0);
});
});
Expand Down

0 comments on commit f4c1ba2

Please sign in to comment.