New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add v-bind
code action
#2524
Add v-bind
code action
#2524
Conversation
Thanks for the PR! Are you planning to change the code action to two-way conversion?
This is the implementation, but really not sure we should have it. provideAutoInsertionEdit(document, position, insertCtx) {
if (insertCtx.lastChange.text !== ':') return;
const offset = document.offsetAt(position);
const [virtualFile, source] = ctx!.documents.getVirtualFileByUri(document.uri);
if (!virtualFile || !(source.root instanceof VueFile)) {
return;
}
const { templateAst } = source.root.sfc;
if (!templateAst) return;
let result: vscode.TextEdit | undefined;
walkElementNodes(templateAst, node => {
for (const prop of node.props) {
if (
prop.type === NodeTypes.ATTRIBUTE
&& offset === prop.loc.start.offset + prop.name.length
) {
result = {
newText: ':' + prop.name.slice(0, -1) + '$0',
range: {
start: document.positionAt(prop.loc.start.offset),
end: document.positionAt(prop.loc.start.offset + prop.name.length),
},
};
}
}
});
return result;
} |
I was thinking of adding it, but only value is string: I was also thinking of adding
If you're against of having this feature, I don't think I need implementation, but thanks. Though I don't see any downsides of having it e.g. typing |
packages/vue-language-service/src/plugins/vue-toggle-v-bind-codeaction.ts
Show resolved
Hide resolved
How its possible code action to use snippet edit? Want to move cursor inside added empty value. |
You can't. microsoft/language-server-protocol#592 BTW. This probably would be more useful if it would be automatically provided in relevant contexts (whether cursor is within the attribute's key or value. Then it wouldn't require custom key bindings and could be used by any LSP client by just triggering the default key binding for showing code actions. |
Sorry, didn't get what you mean. It is already using regular code action provider that doesn't require any additional keybindings in vscode and I guess any lsp client can use it? Also
Doesn't the current impl already do it? |
Hmm, right. I guess it should. But I can't get this plugins' I'm testing on a Vue 2.7 project, if that matters. |
I think you need to use |
The issue with not seeing this code action is due to some bigger issue with Volar itself. Between 1.2.1 and 1.3.0 it just stopped functioning properly and now it stalls after sending diagnostics... |
Done, but it would really better to use SnippetEdit in future as now it requires to undo twice to get content before code action |
packages/vue-language-service/src/plugins/vue-toggle-v-bind-codeaction.ts
Outdated
Show resolved
Hide resolved
Thanks @zardoy for the implementation Does this mean, we can now add That's super helpful 🚀 |
Tried to test it a little: Screen.Recording.2023-03-20.at.08.31.50.mov
Is this change tying its functionality to the vscode extension (or even vscode itself)? Because it doesn't seem like the server supports Kinda weird to split the response into a text edit and a command when everything can be pretty easily done with just a single text edit. EDIT: I see it's due to this comment:
But I guess you meant "editors" and not "language servers" and more like "VSCode" rather than "editors" |
You're absolutely right, but I didn't find a way to figure out which client made request (e.g. vscode) from @johnsoncodehk I think it'd be better to provide |
You shouldn't be making workarounds for specific editors and I hope @johnsoncodehk thinks the same. If you must make something vscode-specific then do it in the vscode extension. You can probably even intercept commands and add whatever you want there. Think of creating a behavior that is best for all LSP-complient clients. I wouldn't consider it a breaking issue if the user has to move the cursor manually to the attribute value after triggering this code action. |
This reverts commit ac6e3fa.
Isn't
Agree, it's not breaking, but it could be a huge ux improvement, though I see a logical limitation in code actions support, so I reverted that commit with the workaround. |
I use We can merge the PR when you implement "Toggle". |
Do you mean Remove v-bind refactoring for this case? <Test :prop="'string'" /> -> <Text prop="string" /> will do! |
Hey @johnsoncodehk, I was thinking of the following detection for this code action:
But I'm not sure how to obtain correct range of sourceFile. I tried to add this to if (
startOffset - templateStartOffset >= prop.loc.start.offset
&& endOffset - templateStartOffset <= prop.loc.end.offset
&& prop.type === NodeTypes.DIRECTIVE && prop.exp
) {
for (const [_, map] of ctx!.documents.getMapsByVirtualFileUri(document.uri)) {
const r = map.toGeneratedRange({
start: document.positionAt(prop.exp.loc.start.offset),
end: document.positionAt(prop.exp.loc.end.offset)
});
if (!r) continue;
const ts = ctx!.typescript!.module;
const sourceFile = ctx!.typescript!.languageService.getProgram()!.getSourceFile(_.mainScriptName)!;
const startOffset = ts.getPositionOfLineAndCharacter(sourceFile, r.start.line, r.start.character);
const endOffset = ts.getPositionOfLineAndCharacter(sourceFile, r.end.line, r.end.character);
const generatedText = sourceFile.getFullText().slice(startOffset, endOffset);
// or the same, but much simpler
const generatedText = map.virtualFileDocument.getText(r)
}
} But this doesn't seem to work. Is there an easy way to get that range in virtual script file? |
@zardoy Sorry I probably didn't understand your request, is this what you're asking for? if (
startOffset - templateStartOffset >= prop.loc.start.offset
&& endOffset - templateStartOffset <= prop.loc.end.offset
&& prop.type === NodeTypes.DIRECTIVE && prop.exp
) {
const nodeAtPosition = prop;
const nodeIsString = (prop.exp.loc.source.startsWith('"') && prop.exp.loc.source.endsWith('"'))
|| (prop.exp.loc.source.startsWith("'") && prop.exp.loc.source.endsWith("'"));
const isEndOfDirective = endOffset - templateStartOffset === prop.arg?.loc.end.offset;
} |
edit: { | ||
changes: { [document.uri]: edits }, | ||
}, | ||
command: newPosition ? ctx?.commands.createSetSelectionCommand(newPosition) : undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still using a custom command which will trigger a user facing error in form a dialog box because this is how ST handles invalid commands. I don't know about how other editors handle unsupported commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add an option to let the language client prompt for a list of supported commands.
@johnsoncodehk This is too naive. I wanted to use TypeScript API to do this check: Let's say I have
|
@zardoy Since if (
startOffset - templateStartOffset >= prop.loc.start.offset
&& endOffset - templateStartOffset <= prop.loc.end.offset
&& prop.type === NodeTypes.DIRECTIVE && prop.exp
) {
for (const [tsFile, map] of ctx!.documents.getMapsByVirtualFileName(vueFile.mainScriptName)) {
const tsStart = map.map.toGeneratedOffset(prop.exp.loc.start.offset + templateStartOffset, false)?.[0];
const tsEnd = map.map.toGeneratedOffset(prop.exp.loc.end.offset + templateStartOffset, true)?.[0];
const ast = ctx?.typescript?.languageService.getProgram()?.getSourceFile(tsFile.fileName);
if (tsStart !== undefined && tsEnd !== undefined && ast) {
let node: ts.Node | undefined;
ast.forEachChild(function visit(_node) {
if (_node.getStart(ast) === tsStart && _node.getEnd() === tsEnd) {
node = _node;
}
if (!node && _node.getStart(ast) <= tsStart && _node.getEnd() >= tsEnd) {
_node.forEachChild(visit);
}
});
console.log(tsStart, tsEnd, node?.getText(ast));
}
}
} You may also consider another simpler alternative. const ast = ts.createSourceFile('/a.ts', prop.exp.loc.source, ts.ScriptTarget.Latest);
Did it mean |
I believe creating new source file on every cursor change wihtin directive isn't cheap. However I see there is |
|
Made some refinements, hope not messed up. Thanks! |
Fixes #2087
Quotes are not added by design, as the code action intended for cases where user meant an identifier
So @jd-solanki could add the following keybinding:
IMO its worth also adding
:
when:
typed after attribute name, it would be faster for me rather than adding additional keybinding.