Skip to content

Commit

Permalink
feat: Monaco graphql Completion (graphql#1428)
Browse files Browse the repository at this point in the history
  • Loading branch information
rebornix authored and acao committed Apr 13, 2020
1 parent 3d95bb3 commit 3672048
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 546 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
GraphQLCompositeType,
GraphQLEnumValue,
Kind,
KindEnum,
} from 'graphql';

import {
Expand Down Expand Up @@ -62,6 +61,34 @@ import {
objectValues,
} from './autocompleteUtils';

// TODO@acao,rebornix
// Convert AST token kind to Monaco CompletionItemKind
// Should we take `step` into account similar to how we resolve completion items?
function toCompletionItemKind(
kind: string,
): monaco.languages.CompletionItemKind {
const mItemKind = monaco.languages.CompletionItemKind;

switch (kind) {
case 'Document':
case 'SelectionSet':
case 'Field':
case 'AliasedField':
case 'Arguments':
case 'ObjectValue':
case 'ObjectField':
case 'EnumValue':
case 'ListValue':
case 'ListType':
case 'TypeCondition':
case 'NamedType':
case 'FragmentSpread':
case 'VariableDefinition':
case 'Directive':
return mItemKind.Method;
}
}

/**
* Given GraphQLSchema, queryText, and context of the current position within
* the source text, provide a list of typeahead entries.
Expand All @@ -88,20 +115,16 @@ export function getAutocompleteSuggestions(
// Definition kinds
if (kind === 'Document') {
return hintList(token, [
{ label: 'query', kind: CompletionItemKind.Function },
{ label: 'mutation', kind: CompletionItemKind.Function },
{ label: 'subscription', kind: CompletionItemKind.Function },
{ label: 'fragment', kind: CompletionItemKind.Function },
{ label: '{', kind: CompletionItemKind.Constructor },
{ label: 'query', kind: toCompletionItemKind(kind) },
{ label: 'mutation', kind: toCompletionItemKind(kind) },
{ label: 'subscription', kind: toCompletionItemKind(kind) },
{ label: 'fragment', kind: toCompletionItemKind(kind) },
{ label: '{', kind: toCompletionItemKind(kind) },
]);
}

// Field names
if (
kind === RuleKinds.SELECTION_SET ||
kind === RuleKinds.FIELD ||
kind === RuleKinds.ALIASED_FIELD
) {
if (kind === 'SelectionSet' || kind === 'Field' || kind === 'AliasedField') {
return getSuggestionsForFieldNames(token, typeInfo, schema, kind);
}

Expand All @@ -118,7 +141,7 @@ export function getAutocompleteSuggestions(
label: argDef.name,
detail: String(argDef.type),
documentation: argDef.description,
kind: CompletionItemKind.Variable,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -138,7 +161,7 @@ export function getAutocompleteSuggestions(
label: field.name,
detail: String(field.type),
documentation: field.description,
kind: completionKind,
kind: toCompletionItemKind(kind),
})),
);
}
Expand Down Expand Up @@ -205,7 +228,7 @@ function getSuggestionsForFieldNames(
token: ContextToken,
typeInfo: AllTypeInfo,
schema: GraphQLSchema,
kind: RuleKind.SelectionSet | RuleKind.Field | RuleKind.AliasedField,
kind: string,
): Array<CompletionItem> {
if (typeInfo.parentType) {
const parentType = typeInfo.parentType;
Expand All @@ -228,7 +251,7 @@ function getSuggestionsForFieldNames(
deprecated: field.isDeprecated,
isDeprecated: field.isDeprecated,
deprecationReason: field.deprecationReason,
kind: CompletionItemKind.Field,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -253,7 +276,7 @@ function getSuggestionsForInputValues(
deprecated: value.isDeprecated,
isDeprecated: value.isDeprecated,
deprecationReason: value.deprecationReason,
kind: CompletionItemKind.EnumMember,
kind: toCompletionItemKind(kind),
}),
),
);
Expand All @@ -263,14 +286,14 @@ function getSuggestionsForInputValues(
label: 'true',
detail: String(GraphQLBoolean),
documentation: 'Not false.',
kind: CompletionItemKind.Variable,
kind: toCompletionItemKind(kind),
},

{
label: 'false',
detail: String(GraphQLBoolean),
documentation: 'Not true.',
kind: CompletionItemKind.Variable,
kind: toCompletionItemKind(kind),
},
]);
}
Expand All @@ -282,7 +305,7 @@ function getSuggestionsForFragmentTypeConditions(
token: ContextToken,
typeInfo: AllTypeInfo,
schema: GraphQLSchema,
kind: Kind.TypeCondition | Kind.NamedType,
kind: string,
): Array<CompletionItem> {
let possibleTypes: GraphQLType[];
if (typeInfo.parentType) {
Expand Down Expand Up @@ -314,7 +337,7 @@ function getSuggestionsForFragmentTypeConditions(
return {
label: String(type),
documentation: (namedType && namedType.description) || '',
kind: CompletionItemKind.Field,
kind: toCompletionItemKind(kind),
};
}),
);
Expand Down Expand Up @@ -358,7 +381,7 @@ function getSuggestionsForFragmentSpread(
label: frag.name.value,
detail: String(typeMap[frag.typeCondition.name.value]),
documentation: `fragment ${frag.name.value} on ${frag.typeCondition.name.value}`,
kind: CompletionItemKind.Field,
kind: toCompletionItemKind(kind),
})),
);
}
Expand Down Expand Up @@ -408,7 +431,7 @@ function getSuggestionsForVariableDefinition(
inputTypes.map((type: any) => ({
label: type.name,
documentation: type.description,
kind: CompletionItemKind.Variable,
kind: toCompletionItemKind(kind),
})),
);
}
Expand All @@ -428,7 +451,7 @@ function getSuggestionsForDirective(
directives.map(directive => ({
label: directive.name,
documentation: directive.description || '',
kind: CompletionItemKind.Function,
kind: toCompletionItemKind(kind),
})),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import {
import { DiagnosticSeverity, Diagnostic } from 'vscode-languageserver-types';

export const DIAGNOSTIC_SEVERITY = {
ERROR: 1 as DiagnosticSeverity,
WARNING: 2 as DiagnosticSeverity,
INFORMATION: 3 as DiagnosticSeverity,
HINT: 4 as DiagnosticSeverity,
Error: 1 as DiagnosticSeverity,
Warning: 2 as DiagnosticSeverity,
Information: 3 as DiagnosticSeverity,
Hint: 4 as DiagnosticSeverity,
};

export function getDiagnostics(
Expand All @@ -50,7 +50,7 @@ export function getDiagnostics(
const range = getRange(error.locations[0], query);
return [
{
severity: DIAGNOSTIC_SEVERITY.ERROR as DiagnosticSeverity,
severity: DIAGNOSTIC_SEVERITY.Error as DiagnosticSeverity,
message: error.message,
source: 'GraphQL: Syntax',
range,
Expand All @@ -74,15 +74,15 @@ export function validateQuery(

const validationErrorAnnotations = mapCat(
validateWithCustomRules(schema, ast, customRules, isRelayCompatMode),
error => annotations(error, DIAGNOSTIC_SEVERITY.ERROR, 'Validation'),
error => annotations(error, DIAGNOSTIC_SEVERITY.Error, 'Validation'),
);

// Note: findDeprecatedUsages was added in graphql@0.9.0, but we want to
// support older versions of graphql-js.
const deprecationWarningAnnotations = !findDeprecatedUsages
? []
: mapCat(findDeprecatedUsages(schema, ast), error =>
annotations(error, DIAGNOSTIC_SEVERITY.WARNING, 'Deprecation'),
annotations(error, DIAGNOSTIC_SEVERITY.Warning, 'Deprecation'),
);

return validationErrorAnnotations.concat(deprecationWarningAnnotations);
Expand Down
10 changes: 7 additions & 3 deletions packages/graphql-language-service-interface/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ export * from './getAutocompleteSuggestions';

export * from './getDefinition';

export * from './getDiagnostics';
export * from './getOutline';
export * from './getHoverInformation';
export {
getDiagnostics,
validateQuery,
DIAGNOSTIC_SEVERITY as DiagnosticSeverity,
} from './getDiagnostics';
export { getOutline } from './getOutline';
export { getHoverInformation } from './getHoverInformation';

export * from './GraphQLLanguageService';
export * from './GraphQLCache';
5 changes: 3 additions & 2 deletions packages/monaco-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"name": "@graphql/monaco-graphql",
"version": "0.0.1",
"dependencies": {
"monaco-editor": "0.20.0",
"monaco-editor-core": "0.20.0",
"graphql-language-service-interface": "2.4.0-alpha.1"
},
"devDependencies": {
"monaco-editor-core": "0.20.0"
}
}
39 changes: 1 addition & 38 deletions packages/monaco-graphql/src/graphqlMode.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as monaco from 'monaco-editor';
import * as monaco from 'monaco-editor-core';

import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration;

Expand Down Expand Up @@ -28,22 +28,6 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): IDisposable {

disposeAll(providers);

if (modeConfiguration.documentFormattingEdits) {
providers.push(
monaco.languages.registerDocumentFormattingEditProvider(
languageId,
new languageFeatures.DocumentFormattingEditProvider(worker),
),
);
}
if (modeConfiguration.documentRangeFormattingEdits) {
providers.push(
monaco.languages.registerDocumentRangeFormattingEditProvider(
languageId,
new languageFeatures.DocumentRangeFormattingEditProvider(worker),
),
);
}
if (modeConfiguration.completionItems) {
providers.push(
monaco.languages.registerCompletionItemProvider(
Expand All @@ -52,27 +36,6 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): IDisposable {
),
);
}
if (modeConfiguration.hovers) {
providers.push(
monaco.languages.registerHoverProvider(
languageId,
new languageFeatures.HoverAdapter(worker),
),
);
}
if (modeConfiguration.documentSymbols) {
providers.push(
monaco.languages.registerDocumentSymbolProvider(
languageId,
new languageFeatures.DocumentSymbolAdapter(worker),
),
);
}
if (modeConfiguration.diagnostics) {
providers.push(
new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults),
);
}
}

registerProviders();
Expand Down
59 changes: 15 additions & 44 deletions packages/monaco-graphql/src/graphqlWorker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import * as monaco from 'monaco-editor';

import IWorkerContext = monaco.worker.IWorkerContext;

import * as graphqlService from 'graphql-languageservice';
Expand All @@ -26,62 +24,36 @@ export class GraphQLWorker {
}

async doValidation(uri: string): Promise<Diagnostic[]> {
const document = this._getTextDocument(uri);
if (document) {
return this._languageService.getDiagnostics(document, uri);
const query = this._getQueryText(uri);
if (query) {
return this._languageService.getDiagnostics(query, uri);
}
return Promise.resolve([]);
}
async doComplete(uri: string, position: Position): Promise<CompletionItem[]> {
const document = this._getTextDocument(uri);
const query = this._getQueryText(uri);
return this._languageService.getAutocompleteSuggestions(
document,
query,
position,
uri,
);
}

async doHover(
uri: string,
position: graphqlService.Position,
): Promise<graphqlService.Hover> {
const document = this._getTextDocument(uri);
return this._languageService.getHoverInformation(document, position, uri);
}
async format(
uri: string,
range: graphqlService.Range,
options: graphqlService.FormattingOptions,
): Promise<graphqlService.TextEdit[]> {
const document = this._getTextDocument(uri);
const textEdits = this._languageService.format(document, range, options);
return Promise.resolve(textEdits);
}
async reloadSchema(uri: string): Promise<boolean> {
return this._languageService.getConfigForURI(uri).getSchema();
}
async findDocumentSymbols(
uri: string,
): Promise<graphqlService.SymbolInformation[]> {
const document = this._getTextDocument(uri);
const symbols = this._languageService.getDocumentSymbols(document, uri);
return Promise.resolve(symbols);
// TODO@acao, rebornix
// return this._languageService.getConfigForURI(uri).getSchema();
return false;
}
// async getSelectionRanges(uri: string, positions: graphqlService.Position[]): Promise<graphqlService.SelectionRange[]> {
// const document = this._getTextDocument(uri);
// const ranges = this._languageService.getSelectionRanges(document, positions, uri);
// return Promise.resolve(ranges);
// }
private _getTextDocument(uri: string): graphqlService.TextDocument {

resetSchema(uri: string) {}

private _getQueryText(uri: string): string {
// TODO@acao, rebornix

const models = this._ctx.getMirrorModels();
for (const model of models) {
if (model.uri.toString() === uri) {
return graphqlService.TextDocument.create(
uri,
this._languageId,
model.version,
model.getValue(),
);
return model.getValue();
}
}
return null;
Expand All @@ -90,7 +62,6 @@ export class GraphQLWorker {

export interface ICreateData {
languageId: string;
languageSettings: graphqlService.LanguageSettings;
enableSchemaRequest: boolean;
}

Expand Down
Loading

0 comments on commit 3672048

Please sign in to comment.