VSCode API: Anticipate or combine document text changes - visual-studio-code

Let's say I want to make an extension that listens for document changes and always applies them twice. If the user types 'w' it will turn out as 'ww', if the user backspaces once, it should actually backspace twice. If the user pastes something, it should be pasted twice. (My real use case is more complicated, but it is similar to this)
I could listen for document changes using
vscode.workspace.onDidChangeTextDocument(event => {
// Do something
});
and use event.contentChanges to see what changes were made and then use
activeEditor.edit((editBuilder: vscode.TextEditorEdit) => {
// Do something
});
to make the same edit to the document again.
The problem is that this effectively makes two changes to the document so the user will need to ctrl+z twice to undo what is seen as a single action from the user's perspective.
What would be great is if I could anticipate text changes (and then just append more changes) through some method like vscode.workspace.onWillChangeTextDocument but this does not exist (vscode issue #74381, #44771). I also described my problem there.
I see that some people override the 'type' command in vscode to execute before any edit is made (example). But this does not give me access to the edit I want to alter, nor does it capture backspaces or copy/paste commands.
I also tried uding a formatter but this will seemingly override other formatters and users will need to enable "formatting on type" (manually?) for it to update as often as I want. That will also mess with unnecessary user settings/workflows/extensions.
Any idea of how I can achieve this?

The undesired behavior with undo/redo can be mitigated by passing an options object to activeEditor.edit that prevents an "undo stop" to be added before my 2nd edit/change/action. Like this:
activeEditor.edit((editBuilder: vscode.TextEditorEdit) => {
// Do something
}, {undoStopBefore: false, undoStopAfter: false});
undoStopAfter seems like it could be either true or false, producing identical behavior for now.

Related

VSCode Extension API showInputBox call producing unexpected behavior

According to the VSCode extension docs, the InputBoxOption ignoreFocusOut is described thusly:
Set to true to keep the input box open when focus moves to
another part of the editor or to another window.
This setting is ignored on iPad and is always false.
I am not using an iPad, so I can presumably ignore the second sentence.
When I do set this property to true, I see absolutely no behavior change. That is,
as soon as I click in some other window the VSCode input box closes. Am I misinterpreting what this property is supposed to do? Or is this behavior broken?
My typical use looks like this:
state.url = await input.showInputBox({
ignoreFocusOut: true,
title: this.title,
step: 1,
totalSteps: this.maxSteps,
value: state.url ?? '',
prompt: 'Enter remote Git URL',
validate: this.validateProtocol,
shouldResume: shouldResume,
});
If there is, in fact, no way to alter that undesirable behavior of the input closing (and thus the command terminating),
is there perhaps a way to resume the command? That is, I have a multi-step dialog of 5 steps. Upon restoring focus to the VSCode window can I continue with, say, step 3, where I left off? I have not seen any indication this is possible but thought I would ask while I am here.
(My system: VSCode/macOS version 1.74.2)
Found a more targeted site to post the same question and got an answer there:
https://github.com/microsoft/vscode-discussions/discussions/407

vscode.workspace.openTextDocument fails silently

With the same value for Uri, openTextDocument fails to have any discernible effect yet executeCommand successfully opens the document.
vscode.workspace.openTextDocument(uri);
vscode.commands.executeCommand("vscode.open", uri);
Are there any known problems with vscode.workspace.openTextDocument?
This might simply be a misunderstanding of what openTextDocument() does. It just creates a vscode.TextDocument instance, actually showing it in the UI is independent of that. That's why it's in the vscode.workspace namespace rather than vscode.window.
vscode.window.showTextDocument is used for actually showing a document:
Show the given document in a text editor. A column can be provided
to control where the editor is being shown. Might change the active editor.
vscode.workspace.openTextDocument(...).then(
document => vscode.window.showTextDocument(document));

javascript turn off code formatting

vscode is great, but it oversteps boundaries. I'd like to maintain my coding style but seem to be constantly forced into some invisible 'standard', defined by MSoft, I suppose. At any rate, I have "javascript.format.enable": false set and yet it still insists on changing my code.
Take something simple, like dothis(y+2), gets converted to dothis(y + 2) on Save (Ctrl-S). There are many times when I just want what I typed in. How can I get vscode to help me out instead of imposing its own 'standard'?
Do you have "editor.formatOnSave": true in user settings or folder settings? Or, an extension installed that may be doing this?

In a VS Code extension, how can I be notified when the user cuts/copies/or pastes?

For my extension I need to know when a cut/copy/paste happens and be able to get the text associated with those operations. I can probably get the text from the editor if I know when they happen.
I cannot find a listener for these operations. I suppose I can look for ctrl-x, ctrl-c, and ctrl-v keyboard inputs but some users may use the edit menu and not use the keyboard.
Is there a way to be notified when these operations happen either from the keyboard or the edit menu?
Original asker here...
I came up with a solution that involves overriding the default cut/copy/paste actions in the editor. Here is the code for 'copy' in extension.js (I am using js not ts):
//override the editor.action.clipboardCopyAction with our own
var clipboardCopyDisposable = vscode.commands.registerTextEditorCommand('editor.action.clipboardCopyAction', overriddenClipboardCopyAction);
context.subscriptions.push(clipboardCopyDisposable);
/*
* Function that overrides the default copy behavior. We get the selection and use it, dispose of this registered
* command (returning to the default editor.action.clipboardCopyAction), invoke the default one, and then re-register it after the default completes
*/
function overriddenClipboardCopyAction(textEditor, edit, params) {
//debug
console.log("---COPY TEST---");
//use the selected text that is being copied here
getCurrentSelectionEvents(); //not shown for brevity
//dispose of the overridden editor.action.clipboardCopyAction- back to default copy behavior
clipboardCopyDisposable.dispose();
//execute the default editor.action.clipboardCopyAction to copy
vscode.commands.executeCommand("editor.action.clipboardCopyAction").then(function(){
console.log("After Copy");
//add the overridden editor.action.clipboardCopyAction back
clipboardCopyDisposable = vscode.commands.registerTextEditorCommand('editor.action.clipboardCopyAction', overriddenClipboardCopyAction);
context.subscriptions.push(clipboardCopyDisposable);
});
}
This definitely doesn't feel like the best solution... however it does seem to work. Any comments/suggestions? Are there any issues that repeatedly registering and unregistering will cause?
There is no api to access the clipboard directly but some extensions override the default copy and paste shortcuts to customize the copy paste behavior. Here are two examples:
https://github.com/aefernandes/vscode-clipboard-history-extension/blob/master/src/clipboard.ts
https://github.com/stef-levesque/vscode-multiclip/blob/master/src/extension.ts
As you note, that approach will not work when copying using the context menu however. For supporting that as well, you could try intercepting the editor.action.clipboardCopyAction command. See how the Vim extension intercepts the type command for an example of this: https://github.com/VSCodeVim/Vim/blob/aa8d9549ac0d31b393a9346788f9a9a93187c222/extension.ts#L208
There is a proposed api in v1.68 for intercepting and modifying pastes. Since it is proposed for now you can only test it in the Insiders Build.
See copy/paste proposed extension api:
The new documentPaste API proposal lets extensions hook into copy
and paste inside text editors. This can be used to modify the text
that is inserted on paste. Your extension can also store metadata when
text copy and use this metadata when pasting (for example for bringing
along imports when pasting between two code files).
The document paste extension
sample
shows this API in action: <-long code example in the release notes->
Here is the actual (brief) api: proposed documentPaste api
See also using a proposed api

Grails: Radio button doesn't populate command obj when not selected

When I submit my form, if I do not select a value for my radio box then nothing is sent over to my command object for validation. I don't have issues with textFields or other input types.
class ApplicationCommand implements Validateable {
Map<String,String[]> questions = new LinkedHashMap<String,String[]>();
static constraints = {
questions validator: {val, obj, errors ->
for(String key : val.keySet()) {
errors.rejectValue("questions[${key}]",'required');
}
}
}
}
<g:radioGroup name="cmd.questions[1]" values="['Yes']" labels="['Yes']">
${it.radio}${it.label}
</g:radioGroup>
<g:textField name="cmd.questions[2]" />
With the above example, If i leave both fields empty and I submit I get the following
qusetions = [2: String[0]]
What I expect to see is
questions = [1: String[0], 2: String[0]]
Due to this issue, in my questions validator since key=1 is not populated I cannot validate it. The questions are dynamic all pulled from a DB so I cant hard-code anything in the validator such as if !questions.contains(1). Whatever data is populated into questions upon submission is what I have to assume the data is.
I have found 2 work-arounds, neither of which I like
1) Only for radio buttons, add a hidden field that will force a value to be populated if a radio is not selected. Horribly ugly
<g:hiddenField name="cmd.questions[1]" value="-" />
2) In my validator, I query the DB for all my questions and manually check for the existence of each one in the questions map. I want to avoid DB queries in my validators.
So while my 2 work-around options do work, I don't feel like I should have to resort to them.
I don't think this is a limitation of command objects or Grails; I think it's a limitation of HTML. I see the same question popping up in PHP.
The essential problem is that your command object doesn't know how many questions there are, but is responsible for making sure they are all there. I can think of a couple of (additional) ways to deal with this limitation:
The quick and easy way would be to put a single hidden input with the number of questions into your form. Also ugly, and open to alteration by the end-user.
The other option is to promote questions into a Domain so that the number of questions is known ahead of time, and is made available to your command object via a field or method. Then your command object can ask your questions, "How many are there supposed to be? Okay, did I get that many from the view?" This is similar to your #2 but without having to retrieve an entire collection and iterate it. This also opens up a path to perform further validation on each question (e.g., don't allow text into a numbers-only answer).
This does require hitting the DB, but it's the only way I can think of to validate the number of questions without relying on input from the view. The nice thing is that you can make it a very shallow hit, and hitting the DB can be very quick if you do it properly.