Skip to content

Commit

Permalink
Add hover syntax highlighting for types (#2065)
Browse files Browse the repository at this point in the history
  • Loading branch information
acao committed Dec 1, 2021
1 parent 989fca6 commit df57cd2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .changeset/real-boxes-clap.md
@@ -0,0 +1,7 @@
---
'graphql-language-service': patch
'graphql-language-service-interface': patch
'graphql-language-service-server': patch
---

Add an opt-in feature to generate markdown in hover elements, starting with highlighting type information. Enabled for the language server and also the language service and thus `monaco-graphql` as well.
Expand Up @@ -35,7 +35,7 @@ import {

import { Kind, parse, print } from 'graphql';
import { getAutocompleteSuggestions } from './getAutocompleteSuggestions';
import { getHoverInformation } from './getHoverInformation';
import { getHoverInformation, HoverConfig } from './getHoverInformation';
import { validateQuery, getRange, DIAGNOSTIC_SEVERITY } from './getDiagnostics';
import {
getDefinitionQueryResultForFragmentSpread,
Expand Down Expand Up @@ -250,12 +250,13 @@ export class GraphQLLanguageService {
query: string,
position: IPosition,
filePath: Uri,
options?: HoverConfig,
): Promise<Hover['contents']> {
const projectConfig = this.getConfigForURI(filePath);
const schema = await this._graphQLCache.getSchema(projectConfig.name);

if (schema) {
return getHoverInformation(schema, query, position);
return getHoverInformation(schema, query, position, undefined, options);
}
return '';
}
Expand Down
Expand Up @@ -26,11 +26,14 @@ import { AllTypeInfo, IPosition } from 'graphql-language-service-types';
import { Hover } from 'vscode-languageserver-types';
import { getTokenAtPosition, getTypeInfo } from './getAutocompleteSuggestions';

export type HoverConfig = { useMarkdown?: boolean };

export function getHoverInformation(
schema: GraphQLSchema,
queryText: string,
cursor: IPosition,
contextToken?: ContextToken,
config?: HoverConfig,
): Hover['contents'] {
const token = contextToken || getTokenAtPosition(queryText, cursor);

Expand All @@ -42,7 +45,7 @@ export function getHoverInformation(
const kind = state.kind;
const step = state.step;
const typeInfo = getTypeInfo(schema, token.state);
const options = { schema };
const options = { ...config, schema };

// Given a Schema and a Token, produce the contents of an info tooltip.
// To do this, create a div element that we will render "into" and then pass
Expand All @@ -52,17 +55,23 @@ export function getHoverInformation(
(kind === 'AliasedField' && step === 2 && typeInfo.fieldDef)
) {
const into: string[] = [];
renderMdCodeStart(into, options);
renderField(into, typeInfo, options);
renderMdCodeEnd(into, options);
renderDescription(into, options, typeInfo.fieldDef);
return into.join('').trim();
} else if (kind === 'Directive' && step === 1 && typeInfo.directiveDef) {
const into: string[] = [];
renderMdCodeStart(into, options);
renderDirective(into, typeInfo, options);
renderMdCodeEnd(into, options);
renderDescription(into, options, typeInfo.directiveDef);
return into.join('').trim();
} else if (kind === 'Argument' && step === 0 && typeInfo.argDef) {
const into: string[] = [];
renderMdCodeStart(into, options);
renderArg(into, typeInfo, options);
renderMdCodeEnd(into, options);
renderDescription(into, options, typeInfo.argDef);
return into.join('').trim();
} else if (
Expand All @@ -71,7 +80,9 @@ export function getHoverInformation(
'description' in typeInfo.enumValue
) {
const into: string[] = [];
renderMdCodeStart(into, options);
renderEnumValue(into, typeInfo, options);
renderMdCodeEnd(into, options);
renderDescription(into, options, typeInfo.enumValue);
return into.join('').trim();
} else if (
Expand All @@ -80,13 +91,26 @@ export function getHoverInformation(
'description' in typeInfo.type
) {
const into: string[] = [];
renderMdCodeStart(into, options);
renderType(into, typeInfo, options, typeInfo.type);
renderMdCodeEnd(into, options);
renderDescription(into, options, typeInfo.type);
return into.join('').trim();
}
return '';
}

function renderMdCodeStart(into: string[], options: any) {
if (options.useMarkdown) {
text(into, '```graphql\n');
}
}
function renderMdCodeEnd(into: string[], options: any) {
if (options.useMarkdown) {
text(into, '\n```');
}
}

function renderField(into: string[], typeInfo: AllTypeInfo, options: any) {
renderQualifiedField(into, typeInfo, options);
renderTypeAnnotation(into, typeInfo, options, typeInfo.type as GraphQLType);
Expand Down Expand Up @@ -168,6 +192,7 @@ function renderType(
if (!t) {
return;
}

if (t instanceof GraphQLNonNull) {
renderType(into, typeInfo, options, t.ofType);
text(into, '!');
Expand Down
Expand Up @@ -561,6 +561,7 @@ export class MessageProcessor {
query,
toPosition(position),
textDocument.uri,
{ useMarkdown: true },
);

return {
Expand Down
4 changes: 4 additions & 0 deletions packages/graphql-language-service/src/LanguageService.ts
Expand Up @@ -25,6 +25,7 @@ import {
SchemaResponse,
defaultSchemaBuilder,
} from './schemaLoader';
import { HoverConfig } from 'graphql-language-service-interface/src/getHoverInformation';

export type GraphQLLanguageConfig = {
parser?: typeof parse;
Expand Down Expand Up @@ -203,10 +204,13 @@ export class LanguageService {
_uri: string,
documentText: string,
position: IPosition,
options?: HoverConfig,
) =>
getHoverInformation(
(await this.getSchema()) as GraphQLSchema,
documentText,
position,
undefined,
{ useMarkdown: true, ...options },
);
}

2 comments on commit df57cd2

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.