Skip to content

Commit

Permalink
perf: switch from quickinfo to completions
Browse files Browse the repository at this point in the history
  • Loading branch information
mxsdev committed Oct 29, 2022
1 parent 65e3c87 commit 0d18d5c
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 119 deletions.
9 changes: 8 additions & 1 deletion packages/api/src/index.ts
Expand Up @@ -8,12 +8,14 @@ export {
getDescendantAtPosition,
getDescendantAtRange,
isValidType,
getSymbolOrTypeOfNode,
} from "./util"
export { recursivelyExpandType } from "./merge"
export {
generateTypeTree,
getTypeInfoChildren,
getTypeInfoSymbols,
getTypeInfoAtRange,
} from "./tree"
export {
TypeInfo,
Expand All @@ -25,7 +27,12 @@ export {
TypeParameterInfo,
SourceFileLocation,
SymbolOrType,
ExpandedQuickInfo,
CustomTypeScriptRequest,
CustomTypeScriptRequestId,
CustomTypeScriptResponse,
CustomTypeScriptResponseBody,
CustomTypeScriptRequestOfId,
TextRange,
} from "./types"
export {
LocalizedTypeInfo,
Expand Down
34 changes: 34 additions & 0 deletions packages/api/src/tree.ts
Expand Up @@ -49,6 +49,8 @@ import {
getNodeSymbol,
narrowDeclarationForLocation,
isPureObjectOrMappedTypeShallow,
getDescendantAtRange,
getSymbolOrTypeOfNode,
} from "./util"

const maxDepthExceeded: TypeInfo = { kind: "max_depth", id: getEmptyTypeId() }
Expand Down Expand Up @@ -877,3 +879,35 @@ export function getTypeInfoSymbols(info: TypeInfo): SymbolInfo[] {
}
}
}

export function getTypeInfoAtRange(
ctx: TypescriptContext,
location: SourceFileLocation,
apiConfig?: APIConfig
) {
const sourceFile = ctx.program.getSourceFile(location.fileName)
if (!sourceFile) return undefined

const startPos = sourceFile.getPositionOfLineAndCharacter(
location.range.start.line,
location.range.start.character
)

// TODO: integrate this
// getDescendantAtRange will probably need to be improved...
// const endPos = sourceFile.getPositionOfLineAndCharacter(
// location.range.end.line,
// location.range.end.character
// )

const node = getDescendantAtRange(ctx, sourceFile, [startPos, startPos])

if (node === sourceFile || !node.parent) {
return undefined
}

const symbolOrType = getSymbolOrTypeOfNode(ctx, node)
if (!symbolOrType) return undefined

return generateTypeTree(symbolOrType, ctx, apiConfig)
}
28 changes: 26 additions & 2 deletions packages/api/src/types.ts
@@ -1,10 +1,34 @@
import type * as ts from "typescript"
import { APIConfig } from "./config"

export type ExpandedQuickInfo = ts.QuickInfo & {
__displayTree?: TypeInfo
type FileLocationRequest = {
range: TextRange
}

export type CustomTypeScriptRequest = {
id: "type-tree"
} & FileLocationRequest

export type CustomTypeScriptRequestId = CustomTypeScriptRequest["id"]

export type CustomTypeScriptRequestOfId<Id extends CustomTypeScriptRequestId> =
Extract<CustomTypeScriptRequest, { id: Id }>

type TypeTreeResponseBody = {
typeInfo: TypeInfo | undefined
}

type CustomTypeScriptResponseById = {
"type-tree": TypeTreeResponseBody
}

export type CustomTypeScriptResponse<Id extends CustomTypeScriptRequestId> = {
body: CustomTypeScriptResponseById[Id]
}

export type CustomTypeScriptResponseBody<Id extends CustomTypeScriptRequestId> =
CustomTypeScriptResponse<Id>["body"]

export type TypescriptContext = {
program: ts.Program
typeChecker: ts.TypeChecker
Expand Down
29 changes: 28 additions & 1 deletion packages/api/src/util.ts
Expand Up @@ -7,6 +7,7 @@ import {
SourceFileTypescriptContext,
DiscriminatedIndexInfo,
ParameterInfo,
SymbolOrType,
} from "./types"
import {
CheckFlags,
Expand Down Expand Up @@ -700,7 +701,7 @@ export function getSignatureTypeArguments(
}

export function getDescendantAtPosition(
ctx: SourceFileTypescriptContext,
ctx: TypescriptContext,
sourceFile: ts.SourceFile,
position: number
) {
Expand Down Expand Up @@ -795,6 +796,32 @@ export function getSourceFileLocation(
}
}

export function getSymbolOrTypeOfNode(
ctx: TypescriptContext,
node: ts.Node
): SymbolOrType | undefined {
const { typeChecker } = ctx

const symbol =
typeChecker.getSymbolAtLocation(node) ?? getNodeSymbol(ctx, node)

if (symbol) {
const symbolType = getSymbolType(ctx, symbol, node)

if (isValidType(symbolType)) {
return { symbol, node }
}
}

const type = getNodeType(ctx, node)

if (type) {
return { type, node }
}

return undefined
}

/**
* Tries to find subnode that can be retrieved later
* by the client
Expand Down
13 changes: 6 additions & 7 deletions packages/typescript-explorer-vscode/src/state/stateManager.ts
@@ -1,7 +1,7 @@
import { TypeInfo } from "@ts-type-explorer/api"
import * as vscode from "vscode"
import { selectionEnabled } from "../config"
import { getQuickInfoAtPosition, isDocumentSupported, showError } from "../util"
import { getTypeTreeAtRange, isDocumentSupported, showError } from "../util"
import { TypeTreeItem, TypeTreeProvider } from "../view/typeTreeView"
import { ViewProviders } from "../view/views"

Expand Down Expand Up @@ -165,14 +165,13 @@ export class StateManager {
return
}

return getQuickInfoAtPosition(fileName, selections[0].start)
.then((body) => {
const { __displayTree } = body ?? {}
this.setTypeTree(__displayTree)
return getTypeTreeAtRange(fileName, selections[0])
.then((tree) => {
this.setTypeTree(tree)
})
.catch((e) => {
showError("Error getting quick info")
console.error("Quick info error", e)
showError("Error getting type information!")
console.error("TypeTreeRequest error", e)
})
}
}
76 changes: 61 additions & 15 deletions packages/typescript-explorer-vscode/src/util.ts
@@ -1,29 +1,37 @@
import * as vscode from "vscode"
import type * as ts from "typescript"
import {
CustomTypeScriptRequestId,
CustomTypeScriptRequestOfId,
CustomTypeScriptResponse,
SourceFileLocation,
TextRange,
TypeInfo,
ExpandedQuickInfo,
} from "@ts-type-explorer/api"
import type * as Proto from "typescript/lib/protocol"

export const toFileLocationRequestArgs = (
export const positionToLineAndCharacter = (
position: vscode.Position
): ts.LineAndCharacter => ({
line: position.line,
character: position.character,
})

export const rangeToTextRange = (range: vscode.Range): TextRange => ({
start: positionFromLineAndCharacter(range.start),
end: positionFromLineAndCharacter(range.end),
})

const toFileLocationRequestArgs = (
file: string,
position: vscode.Position
) => ({
): Proto.FileLocationRequestArgs => ({
file,
line: position.line + 1,
offset: position.character + 1,
})

export const fromFileLocationRequestArgs = (position: {
line: number
character: number
}) => ({
line: position.line - 1,
character: position.character - 1,
})

export const positionFromLineAndCharacter = ({
const positionFromLineAndCharacter = ({
line,
character,
}: ts.LineAndCharacter) => new vscode.Position(line, character)
Expand All @@ -43,7 +51,7 @@ export function getTypescriptMd(code: string) {
return mds
}

export async function getQuickInfoAtPosition(
async function getQuickInfoAtPosition(
fileName: string,
position: vscode.Position
) {
Expand All @@ -53,7 +61,26 @@ export async function getQuickInfoAtPosition(
"quickinfo-full",
toFileLocationRequestArgs(fileName, position)
)
.then((r) => (r as { body: ExpandedQuickInfo | undefined }).body)
.then((r) => (r as Proto.QuickInfoResponse).body)
}

async function customTypescriptRequest<Id extends CustomTypeScriptRequestId>(
fileName: string,
position: vscode.Position,
request: CustomTypeScriptRequestOfId<Id>
): Promise<CustomTypeScriptResponse<Id> | undefined> {
return await vscode.commands.executeCommand(
"typescript.tsserverRequest",
"completionInfo",
{
...toFileLocationRequestArgs(fileName, position),
/**
* We override the "triggerCharacter" property here as a hack so
* that we can send custom commands to TSServer
*/
triggerCharacter: request,
}
)
}

export function getQuickInfoAtLocation(location: SourceFileLocation) {
Expand All @@ -66,7 +93,26 @@ export function getQuickInfoAtLocation(location: SourceFileLocation) {
export function getTypeTreeAtLocation(
location: SourceFileLocation
): Promise<TypeInfo | undefined> {
return getQuickInfoAtLocation(location).then((data) => data?.__displayTree)
return getTypeTreeAtRange(
location.fileName,
rangeFromLineAndCharacters(location.range.start, location.range.end)
)
}

export function getTypeTreeAtRange(
fileName: string,
range: vscode.Range
): Promise<TypeInfo | undefined> {
return customTypescriptRequest(
fileName,
positionFromLineAndCharacter(range.start),
{
id: "type-tree",
range: rangeToTextRange(range),
}
).then((res) => {
return res?.body.typeInfo
})
}

/**
Expand Down

0 comments on commit 0d18d5c

Please sign in to comment.