Skip to content

Commit

Permalink
Add document and workspace symbols to VS Code extension
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnwalraven committed Nov 12, 2018
1 parent 43c11eb commit a95dc6f
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 12 deletions.
96 changes: 89 additions & 7 deletions packages/apollo-language-server/src/languageProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
Hover,
Definition,
CodeLens,
Command,
ReferenceContext,
InsertTextFormat
InsertTextFormat,
DocumentSymbol,
SymbolKind,
SymbolInformation
} from "vscode-languageserver";
import Uri from "vscode-uri";

// should eventually be moved into this package, since we're overriding a lot of the existing behavior here
import { getAutocompleteSuggestions } from "@apollographql/graphql-language-service-interface";
Expand Down Expand Up @@ -46,16 +47,15 @@ import {
isNonNullType,
ASTNode,
FieldDefinitionNode,
FieldNode,
visit,
BREAK,
ObjectTypeDefinitionNode
isExecutableDefinitionNode,
isTypeSystemDefinitionNode,
isTypeSystemExtensionNode
} from "graphql";
import { highlightNodeForNode } from "./utilities/graphql";

import { GraphQLClientProject, isClientProject } from "./project/client";
import { isNotNullOrUndefined } from "@apollographql/apollo-tools";
import { notDeepEqual } from "assert";

function hasFields(type: GraphQLType): boolean {
return (
Expand All @@ -79,6 +79,17 @@ function locationForASTNode(node: ASTNode): Location | null {
return Location.create(uri, rangeForASTNode(node));
}

function symbolForFieldDefinition(
definition: FieldDefinitionNode
): DocumentSymbol {
return {
name: definition.name.value,
kind: SymbolKind.Field,
range: rangeForASTNode(definition),
selectionRange: rangeForASTNode(definition)
};
}

export class GraphQLLanguageProvider {
constructor(public workspace: GraphQLWorkspace) {}

Expand Down Expand Up @@ -409,6 +420,77 @@ ${argumentNode.description ? argumentNode.description : ""}
return null;
}

async provideDocumentSymbol(
uri: DocumentUri,
_token: CancellationToken
): Promise<DocumentSymbol[]> {
const project = this.workspace.projectForFile(uri);
if (!project) return [];

const definitions = project.definitionsAt(uri);

const symbols: DocumentSymbol[] = [];

for (const definition of definitions) {
if (isExecutableDefinitionNode(definition)) {
if (!definition.name) continue;
const location = locationForASTNode(definition);
if (!location) continue;
symbols.push({
name: definition.name.value,
kind: SymbolKind.Function,
range: rangeForASTNode(definition),
selectionRange: rangeForASTNode(highlightNodeForNode(definition))
});
} else if (
isTypeSystemDefinitionNode(definition) ||
isTypeSystemExtensionNode(definition)
) {
if (
definition.kind === Kind.SCHEMA_DEFINITION ||
definition.kind === Kind.SCHEMA_EXTENSION
) {
continue;
}
symbols.push({
name: definition.name.value,
kind: SymbolKind.Class,
range: rangeForASTNode(definition),
selectionRange: rangeForASTNode(highlightNodeForNode(definition)),
children:
definition.kind === Kind.OBJECT_TYPE_DEFINITION ||
definition.kind === Kind.OBJECT_TYPE_EXTENSION
? (definition.fields || []).map(symbolForFieldDefinition)
: undefined
});
}
}

return symbols;
}

async provideWorkspaceSymbol(
query: string,
_token: CancellationToken
): Promise<SymbolInformation[]> {
const symbols: SymbolInformation[] = [];
for (const project of this.workspace.projects) {
for (const definition of project.definitions) {
if (isExecutableDefinitionNode(definition)) {
if (!definition.name) continue;
const location = locationForASTNode(definition);
if (!location) continue;
symbols.push({
name: definition.name.value,
kind: SymbolKind.Function,
location
});
}
}
}
return symbols;
}

async provideCodeLenses(
uri: DocumentUri,
_token: CancellationToken
Expand Down
19 changes: 15 additions & 4 deletions packages/apollo-language-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
ProposedFeatures,
TextDocuments,
FileChangeType,
NotificationType
NotificationType,
ServerCapabilities
} from "vscode-languageserver";
import { QuickPickItem } from "vscode";
import { GraphQLWorkspace } from "./workspace";
Expand Down Expand Up @@ -72,12 +73,14 @@ connection.onInitialize(async params => {
return {
capabilities: {
hoverProvider: true,
definitionProvider: true,
referencesProvider: true,
completionProvider: {
resolveProvider: false,
triggerCharacters: ["..."]
},
definitionProvider: true,
referencesProvider: true,
documentSymbolProvider: true,
workspaceSymbolProvider: true,
codeLensProvider: {
resolveProvider: false
},
Expand All @@ -88,7 +91,7 @@ connection.onInitialize(async params => {
]
},
textDocumentSync: documents.syncKind
}
} as ServerCapabilities
};
});

Expand Down Expand Up @@ -194,6 +197,14 @@ connection.onReferences((params, token) =>
)
);

connection.onDocumentSymbol((params, token) =>
languageProvider.provideDocumentSymbol(params.textDocument.uri, token)
);

connection.onWorkspaceSymbol((params, token) =>
languageProvider.provideWorkspaceSymbol(params.query, token)
);

connection.onCompletion((params, token) =>
languageProvider.provideCompletionItems(
params.textDocument.uri,
Expand Down
6 changes: 5 additions & 1 deletion packages/apollo-language-server/src/typings/graphql.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import {
ASTNode,
TypeSystemDefinitionNode,
TypeSystemExtensionNode,
FragmentDefinitionNode
FragmentDefinitionNode,
OperationDefinitionNode
} from "graphql";

// FIXME: We should add proper type guards for these predicate functions
// to `@types/graphql`.
declare module "graphql/language/predicates" {
function isExecutableDefinitionNode(
node: ASTNode
): node is OperationDefinitionNode | FragmentDefinitionNode;
function isTypeSystemDefinitionNode(
node: ASTNode
): node is TypeSystemDefinitionNode;
Expand Down

0 comments on commit a95dc6f

Please sign in to comment.