support rename for import path.
This commit is contained in:
parent
f4ed223296
commit
48a515005b
3 changed files with 114 additions and 22 deletions
src/server
|
@ -2,50 +2,87 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { CancellationToken, Position, TextDocument, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||
import { RenameProvider } from 'coc.nvim/lib/provider'
|
||||
import path from 'path'
|
||||
import { CancellationToken, Position, Range, TextDocument, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||
import * as Proto from '../protocol'
|
||||
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
|
||||
import API from '../utils/api'
|
||||
import * as typeConverters from '../utils/typeConverters'
|
||||
import Uri from 'vscode-uri'
|
||||
|
||||
export default class TypeScriptRenameProvider implements RenameProvider {
|
||||
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||
|
||||
public async prepareRename(
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
token: CancellationToken
|
||||
): Promise<Range | null> {
|
||||
const response = await this.execRename(document, position, token)
|
||||
if (!response || response.type !== 'response' || !response.body) {
|
||||
return null
|
||||
}
|
||||
|
||||
const renameInfo = response.body.info
|
||||
if (!renameInfo.canRename) {
|
||||
return Promise.reject(new Error('Invalid location for rename.'))
|
||||
}
|
||||
|
||||
if (this.client.apiVersion.gte(API.v310)) {
|
||||
const triggerSpan = (renameInfo as any).triggerSpan
|
||||
if (triggerSpan) {
|
||||
const range = typeConverters.Range.fromTextSpan(triggerSpan)
|
||||
return range
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
public async provideRenameEdits(
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
newName: string,
|
||||
token: CancellationToken
|
||||
): Promise<WorkspaceEdit | null> {
|
||||
const file = this.client.toPath(document.uri)
|
||||
if (!file) {
|
||||
const response = await this.execRename(document, position, token)
|
||||
if (!response || response.type !== 'response' || !response.body) {
|
||||
return null
|
||||
}
|
||||
|
||||
const renameInfo = response.body.info
|
||||
if (!renameInfo.canRename) {
|
||||
return Promise.reject(new Error('Invalid location for rename.'))
|
||||
}
|
||||
|
||||
if (this.client.apiVersion.gte(API.v310)) {
|
||||
if ((renameInfo as any).fileToRename) {
|
||||
const edits = await this.renameFile((renameInfo as any).fileToRename, newName, token)
|
||||
if (edits) {
|
||||
return edits
|
||||
} else {
|
||||
return Promise.reject(new Error('An error occurred while renaming file'))
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.toWorkspaceEdit(response.body.locs, newName)
|
||||
}
|
||||
|
||||
public async execRename(
|
||||
document: TextDocument,
|
||||
position: Position,
|
||||
token: CancellationToken
|
||||
): Promise<ServerResponse<Proto.RenameResponse> | undefined> {
|
||||
const file = this.client.toPath(document.uri)
|
||||
if (!file) return undefined
|
||||
|
||||
const args: Proto.RenameRequestArgs = {
|
||||
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||
findInStrings: false,
|
||||
findInComments: false
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.client.execute('rename', args, token)
|
||||
if (!response.body) {
|
||||
return null
|
||||
}
|
||||
|
||||
const renameInfo = response.body.info
|
||||
if (!renameInfo.canRename) {
|
||||
return Promise.reject<WorkspaceEdit>(
|
||||
renameInfo.localizedErrorMessage
|
||||
)
|
||||
}
|
||||
|
||||
return this.toWorkspaceEdit(response.body.locs, newName)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
return null
|
||||
return this.client.execute('rename', args, token)
|
||||
}
|
||||
|
||||
private toWorkspaceEdit(
|
||||
|
@ -67,4 +104,43 @@ export default class TypeScriptRenameProvider implements RenameProvider {
|
|||
}
|
||||
return { changes }
|
||||
}
|
||||
|
||||
private async renameFile(
|
||||
fileToRename: string,
|
||||
newName: string,
|
||||
token: CancellationToken,
|
||||
): Promise<WorkspaceEdit | undefined> {
|
||||
// Make sure we preserve file exension if none provided
|
||||
if (!path.extname(newName)) {
|
||||
newName += path.extname(fileToRename)
|
||||
}
|
||||
|
||||
const dirname = path.dirname(fileToRename)
|
||||
const newFilePath = path.join(dirname, newName)
|
||||
|
||||
const args: Proto.GetEditsForFileRenameRequestArgs & { file: string } = {
|
||||
file: fileToRename,
|
||||
oldFilePath: fileToRename,
|
||||
newFilePath
|
||||
}
|
||||
const response = await this.client.execute('getEditsForFileRename', args, token)
|
||||
if (response.type !== 'response' || !response.body) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const edits = typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, response.body)
|
||||
|
||||
edits.documentChanges = edits.documentChanges || []
|
||||
edits.documentChanges.push({
|
||||
kind: 'rename',
|
||||
oldUri: Uri.file(fileToRename).toString(),
|
||||
newUri: Uri.file(newFilePath).toString(),
|
||||
options: {
|
||||
overwrite: false,
|
||||
ignoreIfExists: true
|
||||
}
|
||||
})
|
||||
return edits
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,20 @@ import API from './utils/api'
|
|||
import { TypeScriptServiceConfiguration } from './utils/configuration'
|
||||
import Logger from './utils/logger'
|
||||
|
||||
export class CancelledResponse {
|
||||
public readonly type: 'cancelled' = 'cancelled'
|
||||
|
||||
constructor(
|
||||
public readonly reason: string
|
||||
) { }
|
||||
}
|
||||
|
||||
export class NoContentResponse {
|
||||
public readonly type: 'noContent' = 'noContent'
|
||||
}
|
||||
|
||||
export type ServerResponse<T extends Proto.Response> = T | CancelledResponse | NoContentResponse
|
||||
|
||||
export interface TypeScriptServerPlugin {
|
||||
readonly path: string
|
||||
readonly name: string
|
||||
|
|
|
@ -27,6 +27,8 @@ export default class API {
|
|||
public static readonly v291 = API.fromSimpleString('2.9.1')
|
||||
public static readonly v292 = API.fromSimpleString('2.9.2')
|
||||
public static readonly v300 = API.fromSimpleString('3.0.0')
|
||||
public static readonly v310 = API.fromSimpleString('3.1.0')
|
||||
public static readonly v320 = API.fromSimpleString('3.2.0')
|
||||
|
||||
public static fromVersionString(versionString: string): API {
|
||||
let version = semver.valid(versionString)
|
||||
|
|
Loading…
Add table
Reference in a new issue