-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Add rename on local Python and Bash variables
- Loading branch information
1 parent
efdaad3
commit d3c5508
Showing
4 changed files
with
316 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
/* -------------------------------------------------------------------------------------------- | ||
* Copyright (c) 2024 Savoir-faire Linux. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
* ------------------------------------------------------------------------------------------ */ | ||
|
||
import { type RenameMiddleware } from 'vscode-languageclient' | ||
import { commands, workspace, WorkspaceEdit, type Range, type TextEdit } from 'vscode' | ||
|
||
import { getEmbeddedLanguageDocPosition, getOriginalDocRange } from './utils/embeddedLanguagesUtils' | ||
import { embeddedLanguageDocsManager } from './EmbeddedLanguageDocsManager' | ||
import { requestsManager } from './RequestManager' | ||
|
||
export const middlewareProvideRenameEdits: RenameMiddleware['provideRenameEdits'] = async (document, position, newName, token, next) => { | ||
const nextResult = await next(document, position, newName, token) | ||
if (nextResult !== undefined && nextResult !== null) { | ||
return nextResult | ||
} | ||
const embeddedLanguageType = await requestsManager.getEmbeddedLanguageTypeOnPosition(document.uri.toString(), position) | ||
if (embeddedLanguageType === undefined || embeddedLanguageType === null) { | ||
return | ||
} | ||
const embeddedLanguageDocInfos = embeddedLanguageDocsManager.getEmbeddedLanguageDocInfos(document.uri, embeddedLanguageType) | ||
if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { | ||
return | ||
} | ||
const embeddedLanguageTextDocument = await workspace.openTextDocument(embeddedLanguageDocInfos.uri) | ||
const adjustedPosition = getEmbeddedLanguageDocPosition( | ||
document, | ||
embeddedLanguageTextDocument, | ||
embeddedLanguageDocInfos.characterIndexes, | ||
position | ||
) | ||
const tempWorkspaceEdit = await commands.executeCommand<WorkspaceEdit>( | ||
'vscode.executeDocumentRenameProvider', | ||
embeddedLanguageDocInfos.uri, | ||
adjustedPosition, | ||
newName | ||
) | ||
|
||
const workspaceEdit = new WorkspaceEdit() | ||
|
||
tempWorkspaceEdit.entries().forEach(([tempUri, tempTextEdits]) => { | ||
const textEdits: TextEdit[] = [] | ||
const originalUri = embeddedLanguageDocsManager.getOriginalUri(tempUri) | ||
if (originalUri === undefined) { | ||
return | ||
} | ||
tempTextEdits.forEach((tempTextEdit) => { | ||
const range = getOriginalDocRange( | ||
document, | ||
embeddedLanguageTextDocument, | ||
embeddedLanguageDocInfos.characterIndexes, | ||
tempTextEdit.range | ||
) | ||
if (range === undefined) { | ||
return | ||
} | ||
textEdits.push({ | ||
range, | ||
newText: tempTextEdit.newText | ||
}) | ||
}) | ||
workspaceEdit.set(originalUri, textEdits) | ||
}) | ||
|
||
return workspaceEdit | ||
} | ||
|
||
// It seems RenameMiddleware['prepareRename'] expects to throw an error when rename is not possible. | ||
const invalidRenameError = new Error("The element can't be renamed.") | ||
|
||
export const middlewarePrepareRename: RenameMiddleware['prepareRename'] = async (document, position, token, next) => { | ||
let nextResult: Awaited<ReturnType<typeof next>> | undefined | ||
try { | ||
nextResult = await next(document, position, token) | ||
} catch (error) { | ||
// pass | ||
} | ||
|
||
if (nextResult !== undefined && nextResult !== null) { | ||
return nextResult | ||
} | ||
|
||
const embeddedLanguageType = await requestsManager.getEmbeddedLanguageTypeOnPosition(document.uri.toString(), position) | ||
|
||
if (embeddedLanguageType === undefined || embeddedLanguageType === null) { | ||
throw invalidRenameError | ||
} | ||
const embeddedLanguageDocInfos = embeddedLanguageDocsManager.getEmbeddedLanguageDocInfos(document.uri, embeddedLanguageType) | ||
|
||
if (embeddedLanguageDocInfos === undefined || embeddedLanguageDocInfos === null) { | ||
throw invalidRenameError | ||
} | ||
const embeddedLanguageTextDocument = await workspace.openTextDocument(embeddedLanguageDocInfos.uri) | ||
const adjustedPosition = getEmbeddedLanguageDocPosition( | ||
document, | ||
embeddedLanguageTextDocument, | ||
embeddedLanguageDocInfos.characterIndexes, | ||
position | ||
) | ||
const tempPrepareRename = await commands.executeCommand<{ range: Range, placeholder: string } | undefined>( | ||
'vscode.prepareRename', | ||
embeddedLanguageDocInfos.uri, | ||
adjustedPosition | ||
) | ||
|
||
if (tempPrepareRename === undefined) { | ||
throw invalidRenameError | ||
} | ||
|
||
const range = getOriginalDocRange( | ||
document, | ||
embeddedLanguageTextDocument, | ||
embeddedLanguageDocInfos.characterIndexes, | ||
tempPrepareRename.range | ||
) | ||
|
||
if (range === undefined) { | ||
throw invalidRenameError | ||
} | ||
|
||
return { | ||
range, | ||
placeholder: tempPrepareRename.placeholder | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
integration-tests/project-folder/sources/meta-fixtures/rename.bb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
foo='foo' | ||
|
||
python() { | ||
foo='foo' | ||
print(foo) | ||
d.getVar('foo') # should be included in global variables, but it does not work in integration tests for unknown reasons | ||
} | ||
|
||
do_stuff() { | ||
echo "${foo}" | ||
local foo='foo' | ||
echo "$foo" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/* -------------------------------------------------------------------------------------------- | ||
* Copyright (c) 2023 Savoir-faire Linux. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
* ------------------------------------------------------------------------------------------ */ | ||
|
||
import * as assert from 'assert' | ||
import * as vscode from 'vscode' | ||
import path from 'path' | ||
import { assertWillComeTrue } from '../utils/async' | ||
import { BITBAKE_TIMEOUT } from '../utils/bitbake' | ||
|
||
suite('Bitbake Rename Test Suite', () => { | ||
const filePath = path.resolve(__dirname, '../../project-folder/sources/meta-fixtures/rename.bb') | ||
const docUri = vscode.Uri.parse(`file://${filePath}`) | ||
|
||
suiteSetup(async function (this: Mocha.Context) { | ||
this.timeout(100000) | ||
const vscodeBitbake = vscode.extensions.getExtension('yocto-project.yocto-bitbake') | ||
if (vscodeBitbake === undefined) { | ||
assert.fail('Bitbake extension is not available') | ||
} | ||
await vscodeBitbake.activate() | ||
await vscode.workspace.openTextDocument(docUri) | ||
}) | ||
|
||
const testRename = async ( | ||
position: vscode.Position, | ||
newName: string, | ||
expected: ReturnType<vscode.WorkspaceEdit['entries']> | ||
): Promise<void> => { | ||
let result: vscode.WorkspaceEdit | undefined | ||
|
||
await assertWillComeTrue(async () => { | ||
result = await vscode.commands.executeCommand<vscode.WorkspaceEdit>( | ||
'vscode.executeDocumentRenameProvider', | ||
docUri, | ||
position, | ||
newName | ||
) | ||
return result.entries().length === expected.length | ||
}) | ||
result?.entries().forEach(([uri, edits], index) => { | ||
const [expectedUri, expectedTextEdits] = expected[index] | ||
assert.strictEqual(uri.toString() === expectedUri.toString(), true) | ||
assert.strictEqual(edits.length === expectedTextEdits.length, true) | ||
edits.forEach((edit, editIndex) => { | ||
const expectedEdit = expectedTextEdits[editIndex] | ||
assert.strictEqual(edit.newText === expectedEdit.newText, true) | ||
assert.strictEqual(edit.range.isEqual(expectedEdit.range), true) | ||
}) | ||
}) | ||
} | ||
|
||
const testPrepareRename = async ( | ||
position: vscode.Position, | ||
expected: { range: vscode.Range, placeholder: string } | ||
): Promise<void> => { | ||
let result: { range: vscode.Range, placeholder: string } | undefined | ||
|
||
await assertWillComeTrue(async () => { | ||
result = await vscode.commands.executeCommand<{ range: vscode.Range, placeholder: string } | undefined>( | ||
'vscode.prepareRename', | ||
docUri, | ||
position | ||
) | ||
return result !== undefined | ||
}) | ||
|
||
assert.strictEqual(result?.range.isEqual(expected.range), true) | ||
assert.strictEqual(result?.placeholder === expected.placeholder, true) | ||
} | ||
|
||
const testInvalidRename = async ( | ||
position: vscode.Position | ||
): Promise<void> => { | ||
try { | ||
await vscode.commands.executeCommand<{ range: vscode.Range, placeholder: string } | undefined>( | ||
'vscode.prepareRename', | ||
docUri, | ||
position | ||
) | ||
} catch (error) { | ||
if (error instanceof Error) { | ||
assert.strictEqual(error.message === "The element can't be renamed.", true) | ||
return | ||
} | ||
} | ||
assert.fail() | ||
} | ||
|
||
test('Rename properly on global variable', async () => { | ||
const position = new vscode.Position(0, 2) | ||
const expectedPrepareRename = { | ||
range: new vscode.Range(0, 0, 0, 3), | ||
placeholder: 'foo' | ||
} | ||
await testPrepareRename(position, expectedPrepareRename) | ||
const newName = 'bar' | ||
const expectedRename: ReturnType<vscode.WorkspaceEdit['entries']> = [ | ||
[ | ||
docUri, | ||
[ | ||
new vscode.TextEdit( | ||
new vscode.Range(0, 0, 0, 3), | ||
newName | ||
), | ||
new vscode.TextEdit( | ||
new vscode.Range(9, 12, 9, 15), | ||
newName | ||
) | ||
] | ||
] | ||
] | ||
await testRename(position, newName, expectedRename) | ||
}).timeout(BITBAKE_TIMEOUT) | ||
|
||
test('Rename properly on local Python variable', async () => { | ||
const position = new vscode.Position(4, 12) | ||
const expectedPrepareRename = { | ||
range: new vscode.Range(4, 10, 4, 13), | ||
placeholder: 'foo' | ||
} | ||
await testPrepareRename(position, expectedPrepareRename) | ||
const newName = 'bar' | ||
const expectedRename: ReturnType<vscode.WorkspaceEdit['entries']> = [ | ||
[ | ||
docUri, | ||
[ | ||
new vscode.TextEdit( | ||
new vscode.Range(3, 4, 3, 7), | ||
newName | ||
), | ||
new vscode.TextEdit( | ||
new vscode.Range(4, 10, 4, 13), | ||
newName | ||
) | ||
] | ||
] | ||
] | ||
await testRename(position, newName, expectedRename) | ||
}).timeout(BITBAKE_TIMEOUT) | ||
|
||
test('Rename properly on local Bash variable', async () => { | ||
const position = new vscode.Position(10, 12) | ||
const expectedPrepareRename = { | ||
range: new vscode.Range(10, 10, 10, 13), | ||
placeholder: 'foo' | ||
} | ||
await testPrepareRename(position, expectedPrepareRename) | ||
const newName = 'bar' | ||
const expectedRename: ReturnType<vscode.WorkspaceEdit['entries']> = [ | ||
[ | ||
docUri, | ||
[ | ||
new vscode.TextEdit( | ||
new vscode.Range(10, 10, 10, 13), | ||
newName | ||
), | ||
new vscode.TextEdit( | ||
new vscode.Range(11, 11, 11, 14), | ||
newName | ||
) | ||
] | ||
] | ||
] | ||
await testRename(position, newName, expectedRename) | ||
}).timeout(BITBAKE_TIMEOUT) | ||
|
||
test('Rename shows proper message where renaming is not possible', async () => { | ||
const position = new vscode.Position(0, 7) | ||
await testInvalidRename(position) | ||
}).timeout(BITBAKE_TIMEOUT) | ||
}) |