atom-languageclient
Version:
Integrate Language Servers with Atom
116 lines (108 loc) • 5.01 kB
text/typescript
import type * as atomIde from "atom-ide-base"
import Convert from "../convert"
import * as Utils from "../utils"
import { LanguageClientConnection, Location, LocationLink, ServerCapabilities } from "../languageclient"
import { Point, TextEditor, Range } from "atom"
/**
* Public: Adapts the language server definition provider to the Atom IDE UI Definitions package for 'Go To Definition'
* functionality.
*/
export default class DefinitionAdapter {
/**
* Public: Determine whether this adapter can be used to adapt a language server based on the serverCapabilities
* matrix containing a definitionProvider.
*
* @param serverCapabilities The {ServerCapabilities} of the language server to consider.
* @returns A {Boolean} indicating adapter can adapt the server based on the given serverCapabilities.
*/
public static canAdapt(serverCapabilities: ServerCapabilities): boolean {
return serverCapabilities.definitionProvider === true
}
/**
* Public: Get the definitions for a symbol at a given {Point} within a {TextEditor} including optionally highlighting
* all other references within the document if the langauge server also supports highlighting.
*
* @param connection A {LanguageClientConnection} to the language server that will provide definitions and highlights.
* @param serverCapabilities The {ServerCapabilities} of the language server that will be used.
* @param languageName The name of the programming language.
* @param editor The Atom {TextEditor} containing the symbol and potential highlights.
* @param point The Atom {Point} containing the position of the text that represents the symbol for which the
* definition and highlights should be provided.
* @returns A {Promise} indicating adapter can adapt the server based on the given serverCapabilities.
*/
public async getDefinition(
connection: LanguageClientConnection,
serverCapabilities: ServerCapabilities,
languageName: string,
editor: TextEditor,
point: Point
): Promise<atomIde.DefinitionQueryResult | null> {
const documentPositionParams = Convert.editorToTextDocumentPositionParams(editor, point)
const definitionLocations = DefinitionAdapter.normalizeLocations(
await connection.gotoDefinition(documentPositionParams)
)
if (definitionLocations == null || definitionLocations.length === 0) {
return null
}
let queryRange
if (serverCapabilities.documentHighlightProvider) {
const highlights = await connection.documentHighlight(documentPositionParams)
if (highlights != null && highlights.length > 0) {
queryRange = highlights.map((h) => Convert.lsRangeToAtomRange(h.range))
}
}
return {
queryRange: queryRange || [Utils.getWordAtPosition(editor, point)],
definitions: DefinitionAdapter.convertLocationsToDefinitions(definitionLocations, languageName),
}
}
/**
* Public: Normalize the locations so a single {Location} becomes an {Array} of just one. The language server protocol
* return either as the protocol evolved between v1 and v2.
*
* @param locationResult Either a single {Location} object or an {Array} of {Locations}.
* @returns An {Array} of {Location}s or {null} if the locationResult was null.
*/
public static normalizeLocations(
locationResult: Location | Location[] | LocationLink[] | null
): Location[] | LocationLink[] | null {
if (locationResult == null) {
// TODO use ===
return null
}
// TODO `d.targetRange.start` never becomes `null` according to the types
if (isLocationLinkArray(locationResult)) {
return locationResult.filter((d) => d.targetRange.start != null)
}
return (Array.isArray(locationResult) ? locationResult : [locationResult]).filter((d) => d.range.start != null)
}
/**
* Public: Convert an {Array} of {Location} objects into an Array of {Definition}s.
*
* @param locations An {Array} of {Location} objects to be converted.
* @param languageName The name of the language these objects are written in.
* @returns An {Array} of {Definition}s that represented the converted {Location}s.
*/
public static convertLocationsToDefinitions(
locations: Location[] | LocationLink[],
languageName: string
): atomIde.Definition[] {
if (isLocationLinkArray(locations)) {
return locations.map((d) => ({
path: Convert.uriToPath(d.targetUri),
position: Convert.positionToPoint(d.targetRange.start),
range: Range.fromObject(Convert.lsRangeToAtomRange(d.targetRange)),
language: languageName,
}))
}
return locations.map((d) => ({
path: Convert.uriToPath(d.uri),
position: Convert.positionToPoint(d.range.start),
range: Range.fromObject(Convert.lsRangeToAtomRange(d.range)),
language: languageName,
}))
}
}
function isLocationLinkArray(value: any): value is LocationLink[] {
return Array.isArray(value) && LocationLink.is(value[0])
}