diff --git a/.changeset/real-boxes-clap.md b/.changeset/real-boxes-clap.md new file mode 100644 index 00000000000..73af6df2405 --- /dev/null +++ b/.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. diff --git a/packages/graphql-language-service-interface/src/GraphQLLanguageService.ts b/packages/graphql-language-service-interface/src/GraphQLLanguageService.ts index 5ec23fa33e3..1de133956ae 100644 --- a/packages/graphql-language-service-interface/src/GraphQLLanguageService.ts +++ b/packages/graphql-language-service-interface/src/GraphQLLanguageService.ts @@ -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, @@ -250,12 +250,13 @@ export class GraphQLLanguageService { query: string, position: IPosition, filePath: Uri, + options?: HoverConfig, ): Promise { 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 ''; } diff --git a/packages/graphql-language-service-interface/src/getHoverInformation.ts b/packages/graphql-language-service-interface/src/getHoverInformation.ts index 1f533f9e589..ccc49f6b612 100644 --- a/packages/graphql-language-service-interface/src/getHoverInformation.ts +++ b/packages/graphql-language-service-interface/src/getHoverInformation.ts @@ -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); @@ -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 @@ -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 ( @@ -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 ( @@ -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); @@ -168,6 +192,7 @@ function renderType( if (!t) { return; } + if (t instanceof GraphQLNonNull) { renderType(into, typeInfo, options, t.ofType); text(into, '!'); diff --git a/packages/graphql-language-service-server/src/MessageProcessor.ts b/packages/graphql-language-service-server/src/MessageProcessor.ts index 3206edf2fe1..58c694faa2f 100644 --- a/packages/graphql-language-service-server/src/MessageProcessor.ts +++ b/packages/graphql-language-service-server/src/MessageProcessor.ts @@ -561,6 +561,7 @@ export class MessageProcessor { query, toPosition(position), textDocument.uri, + { useMarkdown: true }, ); return { diff --git a/packages/graphql-language-service/src/LanguageService.ts b/packages/graphql-language-service/src/LanguageService.ts index 8f52db6da72..c875910d5b0 100644 --- a/packages/graphql-language-service/src/LanguageService.ts +++ b/packages/graphql-language-service/src/LanguageService.ts @@ -25,6 +25,7 @@ import { SchemaResponse, defaultSchemaBuilder, } from './schemaLoader'; +import { HoverConfig } from 'graphql-language-service-interface/src/getHoverInformation'; export type GraphQLLanguageConfig = { parser?: typeof parse; @@ -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 }, ); }