Merge branch 'neoclide:master' into master

This commit is contained in:
サポミク 2022-09-13 14:55:31 +08:00 committed by GitHub
commit c728c143f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 260 additions and 168 deletions

View file

@ -1,3 +1,7 @@
# 1.11.0
- Add command `tsserver.goToSourceDefinition`.
# 1.10.5
- Fix a fold issue #380

View file

@ -1,11 +1,11 @@
{
"name": "coc-tsserver",
"version": "1.10.5",
"version": "1.11.7",
"description": "tsserver extension for coc.nvim",
"main": "lib/index.js",
"publisher": "chemzqm",
"engines": {
"coc": "^0.0.80"
"coc": "^0.0.82"
},
"repository": {
"type": "git",
@ -30,11 +30,10 @@
"onLanguage:jsx-tags",
"onLanguage:jsonc",
"onCommand:_typescript.configurePlugin",
"onCommand:typescript.reloadProjects",
"onCommand:javascript.reloadProjects",
"onCommand:javascript.goToProjectConfig",
"onCommand:typescript.goToProjectConfig",
"onCommand:typescript.openTsServerLog",
"onCommand:tsserver.reloadProjects",
"onCommand:tsserver.goToProjectConfig",
"onCommand:tsserver.openTsServerLog",
"onCommand:tsserver.goToSourceDefinition",
"onCommand:tsserver.watchBuild"
],
"contributes": {
@ -94,6 +93,11 @@
"category": "TSServer",
"command": "tsserver.findAllFileReferences"
},
{
"command": "tsserver.goToSourceDefinition",
"title": "Go to Source Definition",
"category": "TSServer"
},
{
"title": "Run `tsc --watch` for current project by use vim's job feature.",
"category": "TSServer",

View file

@ -218,6 +218,56 @@ export class FileReferencesCommand implements Command {
}
}
export class SourceDefinitionCommand implements Command {
public static readonly context = 'tsSupportsSourceDefinition'
public static readonly minVersion = API.v470
public readonly id = 'tsserver.goToSourceDefinition'
public constructor(private readonly service: TsserverService) {}
public async execute() {
const client = await this.service.getClientHost()
if (client.serviceClient.apiVersion.lt(SourceDefinitionCommand.minVersion)) {
window.showErrorMessage('Go to Source Definition failed. Requires TypeScript 4.7+.')
return
}
const { document, position } = await workspace.getCurrentState()
if (client.serviceClient.modeIds.indexOf(document.languageId) == -1) {
window.showErrorMessage('Go to Source Definition failed. Unsupported file type.')
return
}
const openedFiledPath = client.serviceClient.toOpenedFilePath(document.uri)
if (!openedFiledPath) {
window.showErrorMessage('Go to Source Definition failed. Unknown file type.')
return
}
await window.withProgress({ title: 'Finding source definitions' }, async (_progress, token) => {
const args = typeConverters.Position.toFileLocationRequestArgs(openedFiledPath, position)
const response = await client.serviceClient.execute('findSourceDefinition', args, token)
if (response.type === 'response' && response.body) {
const locations: Location[] = (response as Proto.DefinitionResponse).body.map(reference =>
typeConverters.Location.fromTextSpan(client.serviceClient.toResource(reference.file), reference))
if (locations.length) {
commands.executeCommand('editor.action.showReferences', document.uri, position, locations)
// if (locations.length === 1) {
// commands.executeCommand('vscode.open', locations[0].uri)
// } else {
// commands.executeCommand('editor.action.showReferences', document.uri, position, locations)
// }
return
}
}
window.showErrorMessage('No source definitions found.')
})
}
}
export function registCommand(cmd: Command): Disposable {
let { id, execute } = cmd
return commands.registerCommand(id as string, execute, cmd)

View file

@ -30,21 +30,21 @@ class CloseOperation {
readonly type = BufferOperationType.Close;
constructor(
public readonly args: string
) { }
) {}
}
class OpenOperation {
readonly type = BufferOperationType.Open;
constructor(
public readonly args: Proto.OpenRequestArgs
) { }
) {}
}
class ChangeOperation {
readonly type = BufferOperationType.Change;
constructor(
public readonly args: Proto.FileCodeEdits
) { }
) {}
}
type BufferOperation = CloseOperation | OpenOperation | ChangeOperation
@ -59,7 +59,7 @@ class SyncedBuffer {
public readonly filepath: string,
private readonly client: ITypeScriptServiceClient,
private readonly synchronizer: BufferSynchronizer,
) { }
) {}
public open(): void {
const args: Proto.OpenRequestArgs = {

View file

@ -225,8 +225,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
let { uri, position, source, name, data } = item.data
const filepath = this.client.toPath(uri)
if (!filepath) return undefined
let document = workspace.getDocument(uri)
if (!document) return undefined
const args: Proto.CompletionDetailsRequestArgs = {
...typeConverters.Position.toFileLocationRequestArgs(
filepath,
@ -259,11 +257,10 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
item.additionalTextEdits = additionalTextEdits
if (detail && item.insertTextFormat == InsertTextFormat.Snippet) {
const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, position, token)
if (shouldCompleteFunction && !item.insertText) {
if (shouldCompleteFunction) {
this.createSnippetOfFunctionCall(item, detail)
}
}
return item
}
@ -380,7 +377,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
let { displayParts } = detail
const parameterListParts = getParameterListParts(displayParts)
const snippet = new SnippetString()
snippet.appendText(`${item.insertText || item.label}(`)
snippet.appendText(`${item.insertText ?? item.label}(`)
appendJoinedPlaceholders(snippet, parameterListParts.parts, ', ')
if (parameterListParts.hasOptionalParameters) {
snippet.appendTabstop()

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'coc.nvim'
import { LocationLink, TextDocument } from 'coc.nvim'
import { DefinitionProvider, CancellationToken, Definition, Location, Position, DefinitionLink, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim'
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
@ -17,7 +17,7 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Location[] | undefined> {
): Promise<Location[] | LocationLink[] | undefined> {
const filepath = this.client.toPath(document.uri)
if (!filepath) {
return undefined
@ -29,12 +29,21 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
)
try {
const response = await this.client.execute(definitionType, args, token)
const locations: Proto.FileSpan[] = (response.type == 'response' && response.body) || []
return locations.map(location =>
typeConverters.Location.fromTextSpan(
this.client.toResource(location.file),
location
)
if (response.type !== 'response' || !response.body) {
return undefined
}
const locations: Proto.FileSpanWithContext[] = (response.type == 'response' && response.body) || []
return locations.map(location => {
const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location)
if (location.contextStart && location.contextEnd) {
return {
targetRange: typeConverters.Range.fromLocations(location.contextStart, location.contextEnd),
targetUri: target.uri,
targetSelectionRange: target.range,
} as any
}
return target
}
)
} catch {
return []
@ -84,14 +93,14 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
public provideTypeDefinition(
document: TextDocument,
position: Position,
token: CancellationToken): Promise<Definition> {
token: CancellationToken): Promise<Definition | DefinitionLink[]> {
return this.getSymbolLocations('typeDefinition', document, position, token)
}
public provideImplementation(
document: TextDocument,
position: Position,
token: CancellationToken): Promise<Definition> {
token: CancellationToken): Promise<Definition | DefinitionLink[]> {
return this.getSymbolLocations('implementation', document, position, token)
}
}

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DiagnosticCollection, languages, workspace } from 'coc.nvim'
import { DiagnosticCollection, Uri, languages, workspace } from 'coc.nvim'
import { Diagnostic, DiagnosticTag } from 'vscode-languageserver-protocol'
import { ResourceMap } from './resourceMap'

View file

@ -86,7 +86,7 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
output: DocumentSymbol[],
item: Proto.NavigationTree,
): boolean {
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldIncludeEntry(item)
const children = new Set(item.childItems || [])
for (const span of item.spans) {
const range = typeConverters.Range.fromTextSpan(span)
@ -129,7 +129,7 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
return symbolInfo
}
private static shouldInclueEntry(
private static shouldIncludeEntry(
item: Proto.NavigationTree | Proto.NavigationBarItem
): boolean {
if (item.kind === PConst.Kind.alias) {

View file

@ -61,11 +61,11 @@ export default class UpdateImportsOnFileRenameHandler {
await workspace.nvim.command(`silent ${oldDocument.bufnr}bwipeout!`)
}
let document = workspace.getDocument(newUri)
if (document) {
await workspace.nvim.command(`silent ${document.bufnr}bwipeout!`)
await wait(30)
if (!document) {
document = await workspace.loadFile(newUri)
} else {
workspace.nvim.command('checktime', true)
}
document = await workspace.loadFile(newUri)
if (!document) return
await wait(50)
const edits = await this.getEditsForFileRename(

View file

@ -1,126 +0,0 @@
import { commands, Disposable, disposeAll, StatusBarItem, TaskOptions, Uri, window, workspace } from 'coc.nvim'
import path from 'path'
import TypeScriptServiceClient from '../typescriptServiceClient'
const countRegex = /Found\s+(\d+)\s+error/
const errorRegex = /^(.+)\((\d+),(\d+)\):\s(\w+)\sTS(\d+):\s*(.+)$/
export default class WatchProject implements Disposable {
private disposables: Disposable[] = []
public static readonly id: string = 'tsserver.watchBuild'
public static readonly startTexts: string[] = ['Starting compilation in watch mode', 'Starting incremental compilation']
private statusItem: StatusBarItem
private task: any
private options: TaskOptions
public constructor(
private client: TypeScriptServiceClient
) {
this.statusItem = window.createStatusBarItem(1, { progress: true })
let task = this.task = workspace.createTask('TSC')
this.disposables.push(commands.registerCommand(WatchProject.id, async () => {
let opts = this.options = await this.getOptions()
await this.start(opts)
}))
task.onExit(code => {
if (code != 0) {
window.showMessage(`TSC exit with code ${code}`, 'warning')
}
this.onStop()
})
task.onStdout(lines => {
for (let line of lines) {
this.onLine(line)
}
})
task.onStderr(lines => {
window.showMessage(`TSC error: ` + lines.join('\n'), 'error')
})
this.disposables.push(Disposable.create(() => {
task.dispose()
}))
this.check().catch(_e => {
// noop
})
}
private async check(): Promise<void> {
let running = await this.task.running
if (running) {
this.options = await this.getOptions()
this.statusItem.isProgress = false
this.statusItem.text = '?'
this.statusItem.show()
} else {
this.onStop()
}
}
private async start(options: TaskOptions): Promise<void> {
await this.task.start(options)
}
private onStop(): void {
this.statusItem.hide()
}
private onStart(): void {
this.statusItem.text = 'compiling'
this.statusItem.isProgress = true
this.statusItem.show()
workspace.nvim.call('setqflist', [[]], true)
}
private onLine(line: string): void {
if (countRegex.test(line)) {
let ms = line.match(countRegex)
this.statusItem.text = ms[1] == '0' ? '✓' : '✗'
this.statusItem.isProgress = false
} else if (WatchProject.startTexts.findIndex(s => line.indexOf(s) !== -1) != -1) {
this.onStart()
} else {
let ms = line.match(errorRegex)
if (!ms) return
let fullpath = path.join(this.options.cwd, ms[1])
let uri = Uri.file(fullpath).toString()
let doc = workspace.getDocument(uri)
let bufnr = doc ? doc.bufnr : null
let item = {
filename: fullpath,
lnum: Number(ms[2]),
col: Number(ms[3]),
text: `[tsc ${ms[5]}] ${ms[6]}`,
type: /error/i.test(ms[4]) ? 'E' : 'W'
} as any
if (bufnr) item.bufnr = bufnr
workspace.nvim.call('setqflist', [[item], 'a'])
}
}
public async getOptions(): Promise<TaskOptions> {
let { tscPath } = this.client
if (!tscPath) {
window.showMessage(`Local & global tsc not found`, 'error')
return
}
const tsconfigPath = workspace.getConfiguration('tsserver').get<string>('tsconfigPath', 'tsconfig.json')
let find = await workspace.findUp([tsconfigPath])
if (!find) {
window.showMessage(`${tsconfigPath} not found!`, 'error')
return
}
let root = path.dirname(find)
return {
cmd: tscPath,
args: ['-p', tsconfigPath, '--watch', 'true', '--pretty', 'false'],
cwd: root
}
}
public dispose(): void {
disposeAll(this.disposables)
}
}

View file

@ -1,8 +1,9 @@
import { commands, disposeAll, IServiceProvider, ServiceStat, workspace, WorkspaceConfiguration } from 'coc.nvim'
import { Disposable, DocumentSelector, Emitter, Event } from 'vscode-languageserver-protocol'
import { PluginManager } from '../utils/plugins'
import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './commands'
import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, SourceDefinitionCommand, TypeScriptGoToProjectConfigCommand } from './commands'
import { OrganizeImportsCommand, SourceImportsCommand } from './organizeImports'
import WatchProject from './watchBuild'
import TypeScriptServiceClientHost from './typescriptServiceClientHost'
import { LanguageDescription, standardLanguageDescriptions } from './utils/languageDescription'
@ -61,6 +62,14 @@ export default class TsserverService implements IServiceProvider {
let { id, execute } = cmd
subscriptions.push(commands.registerCommand(id as string, execute, cmd))
}
let watchProject = new WatchProject(this)
subscriptions.push(watchProject)
registCommand({
id: WatchProject.id,
execute: () => {
return watchProject.execute()
}
})
registCommand(new ConfigurePluginCommand(this.pluginManager))
registCommand(new AutoFixCommand(this))
registCommand(new ReloadProjectsCommand(this))
@ -69,6 +78,7 @@ export default class TsserverService implements IServiceProvider {
registCommand(new TypeScriptGoToProjectConfigCommand(this))
registCommand(new OrganizeImportsCommand(this))
registCommand(new SourceImportsCommand(this))
registCommand(new SourceDefinitionCommand(this))
registCommand({
id: 'tsserver.restart',
execute: (): void => {

View file

@ -42,6 +42,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private fileConfigurationManager: FileConfigurationManager
private pathSeparator: string
private readonly emptyAuthority = 'ts-nul-authority'
private tracer: Tracer
private _configuration: TypeScriptServiceConfiguration
private versionProvider: TypeScriptVersionProvider
@ -226,7 +227,16 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (this.tscPathVim) currentVersion = this.versionProvider.getVersionFromTscPath(this.tscPathVim)
if (!currentVersion && !ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion()
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.')
if (ignoreLocalTsserver) {
this.info(`local tsserver is ignored, try global version`)
} else {
this.info(`local tsserver is not found, try global version`)
}
currentVersion = this.versionProvider.globalVersion
if (currentVersion) this.info('Local and global tsserver not found, using global tsserver from configuration')
}
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
this.info('Local and global tsserver not found, using bundled tsserver with coc-tsserver.')
currentVersion = this.versionProvider.getDefaultVersion()
}
if (!currentVersion || !currentVersion.isValid) {
@ -432,7 +442,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
public toResource(filepath: string): string {
if (filepath.includes('zipfile:')) {
return filepath.replace(/.*zipfile:/, 'zipfile://');
return filepath.replace(/.*zipfile:/, 'zipfile://')
}
if (this._apiVersion.gte(API.v213)) {
if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) {
@ -938,7 +948,7 @@ function getDiagnosticsKind(event: Proto.Event): DiagnosticKind {
case 'suggestionDiag':
return DiagnosticKind.Suggestion
}
throw new Error('Unknown dignostics kind')
throw new Error('Unknown diagnostics kind')
}
const fenceCommands = new Set(['change', 'close', 'open'])

View file

@ -8,7 +8,6 @@ import { flatten } from '../utils/arrays'
import { PluginManager } from '../utils/plugins'
import { DiagnosticKind } from './features/diagnostics'
import FileConfigurationManager from './features/fileConfigurationManager'
import WatchBuild from './features/watchBuild'
import WorkspaceSymbolProvider from './features/workspaceSymbols'
import LanguageProvider from './languageProvider'
import * as Proto from './protocol'
@ -66,7 +65,6 @@ export default class TypeScriptServiceClientHost implements Disposable {
}, null, this.disposables)
// features
this.disposables.push(new WatchBuild(this.client))
this.disposables.push(languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.client, allModeIds)))
this.client.onConfigDiagnosticsReceived(diag => {
let { body } = diag
@ -217,11 +215,11 @@ export default class TypeScriptServiceClientHost implements Disposable {
language.diagnosticsReceived(
kind,
resource,
this.createMarkerDatas(diagnostics))
this.createMarkerData(diagnostics))
}
}
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): (Diagnostic & { reportUnnecessary: any, reportDeprecated: any })[] {
private createMarkerData(diagnostics: Proto.Diagnostic[]): (Diagnostic & { reportUnnecessary: any, reportDeprecated: any })[] {
return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag))
}

View file

@ -44,6 +44,7 @@ export default class API {
public static readonly v420 = API.fromSimpleString('4.2.0')
public static readonly v430 = API.fromSimpleString('4.3.0')
public static readonly v440 = API.fromSimpleString('4.4.0')
public static readonly v470 = API.fromSimpleString('4.7.0')
public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString)

View file

@ -102,7 +102,11 @@ export class TypeScriptVersionProvider {
public get globalVersion(): TypeScriptVersion | undefined {
let { globalTsdk } = this.configuration
if (globalTsdk) return new TypeScriptVersion(workspace.expand(globalTsdk))
let folder = workspace.expand(globalTsdk)
if (!path.isAbsolute(folder)) {
folder = path.join(workspace.root, folder)
}
if (globalTsdk) return new TypeScriptVersion(folder)
return undefined
}

129
src/server/watchBuild.ts Normal file
View file

@ -0,0 +1,129 @@
import { Disposable, disposeAll, StatusBarItem, TaskOptions, Uri, window, workspace } from 'coc.nvim'
import path from 'path'
import type TsserverService from '../server'
const countRegex = /Found\s+(\d+)\s+error/
const errorRegex = /^(.+)\((\d+),(\d+)\):\s(\w+)\sTS(\d+):\s*(.+)$/
export default class WatchProject implements Disposable {
private disposables: Disposable[] = []
public static readonly id: string = 'tsserver.watchBuild'
public static readonly startTexts: string[] = ['Starting compilation in watch mode', 'Starting incremental compilation']
private statusItem: StatusBarItem
private task: any
private options: TaskOptions
public constructor(
private readonly service: TsserverService
) {
this.statusItem = window.createStatusBarItem(1, { progress: true })
this.disposables.push(this.statusItem)
let task = this.task = workspace.createTask('TSC')
task.onExit(code => {
if (code != 0) {
window.showMessage(`TSC exit with code ${code}`, 'warning')
}
this.onStop()
})
task.onStdout(lines => {
for (let line of lines) {
this.onLine(line)
}
})
task.onStderr(lines => {
window.showMessage(`TSC error: ` + lines.join('\n'), 'error')
})
this.disposables.push(Disposable.create(() => {
task.dispose()
}))
this.check().catch(_e => {
// noop
})
}
public async execute(): Promise<void> {
let opts = this.options = await this.getOptions()
await this.start(opts)
}
private async check(): Promise<void> {
let running = await this.task.running
if (running) {
this.options = await this.getOptions()
this.statusItem.isProgress = false
this.statusItem.text = '?'
this.statusItem.show()
} else {
this.onStop()
}
}
private async start(options: TaskOptions): Promise<void> {
await this.task.start(options)
}
private onStop(): void {
this.statusItem.hide()
}
private onStart(): void {
this.statusItem.text = 'compiling'
this.statusItem.isProgress = true
this.statusItem.show()
workspace.nvim.call('setqflist', [[]], true)
}
private onLine(line: string): void {
if (countRegex.test(line)) {
let ms = line.match(countRegex)
this.statusItem.text = ms[1] == '0' ? '✓' : '✗'
this.statusItem.isProgress = false
} else if (WatchProject.startTexts.findIndex(s => line.indexOf(s) !== -1) != -1) {
this.onStart()
} else {
let ms = line.match(errorRegex)
if (!ms) return
let fullpath = path.join(this.options.cwd, ms[1])
let uri = Uri.file(fullpath).toString()
let doc = workspace.getDocument(uri)
let bufnr = doc ? doc.bufnr : null
let item = {
filename: fullpath,
lnum: Number(ms[2]),
col: Number(ms[3]),
text: `[tsc ${ms[5]}] ${ms[6]}`,
type: /error/i.test(ms[4]) ? 'E' : 'W'
} as any
if (bufnr) item.bufnr = bufnr
workspace.nvim.call('setqflist', [[item], 'a'])
}
}
public async getOptions(): Promise<TaskOptions> {
let client = await this.service.getClientHost()
let { tscPath } = client.serviceClient
if (!tscPath) {
window.showMessage(`Local & global tsc not found`, 'error')
return
}
const tsconfigPath = workspace.getConfiguration('tsserver').get<string>('tsconfigPath', 'tsconfig.json')
let find = await workspace.findUp([tsconfigPath])
if (!find) {
window.showMessage(`${tsconfigPath} not found!`, 'error')
return
}
let root = path.dirname(find)
return {
cmd: tscPath,
args: ['-p', tsconfigPath, '--watch', 'true', '--pretty', 'false'],
cwd: root
}
}
public dispose(): void {
disposeAll(this.disposables)
}
}

View file

@ -6,6 +6,7 @@
export const file = 'file'
export const untitled = 'untitled'
export const git = 'git'
export const fugitive = 'fugitive'
/** Live share scheme */
export const vsls = 'vsls'
export const walkThroughSnippet = 'walkThroughSnippet'
@ -21,5 +22,6 @@ export const semanticSupportedSchemes = [
*/
export const disabledSchemes = new Set([
git,
fugitive,
vsls
])