draivin / hsnips

HyperSnips: a powerful snippet engine for VS Code, inspired by vim's UltiSnips
MIT License
163 stars 24 forks source link

Expanding snippets inside snippets deletes tabstops #189

Open Frankwii opened 10 months ago

Frankwii commented 10 months ago

Possibly very related to #157.

When expanding a snippet inside another snippet, all tabstops of the former snippet are deleted. An example:

snippet OOO "Omega" iA
\Omega
endsnippet

snippet II "\int" i
\\int_{$1}${2:f}~d${3:\\mu}
endsnippet

If I write "OOO" inside tabstop $1 of "\int", the snipet "Omega" is expanded correctly, but the tabstops $2 and $3 of "\int" disappear.

This doesn't happen with "native" vscode snippets.

Unpredictability commented 2 months ago

@yiktllw thank you for reporting the bug! I have also commented there ♥️ @Oskar-Idland

superle3 commented 2 months ago

@XRay71 on 1.85.2 it did not delete tabstop/ behaved normally until 19 of the same insertSnippet was called. independent on how many tabstops there were in the insertSnippet call. So it "worked" as expected

PiotrSokol commented 2 months ago

I have a temporary solution to the problem. modifying extensions.ts:86:90 reverses the behavior.

  await vscode.commands.executeCommand('editor.action.insertSnippet', { "snippet": snippetInstance.snippetString.value });
  // await editor.insertSnippet(snippetInstance.snippetString, insertionRange, {
  //   undoStopAfter: false,
  //   undoStopBefore: false,
  // });

it's not the most elegant but can't figure out why vscode.TextEditor.insertSnippet doesn't work, esp. since the proposed seems to use it under the hood.

also @Oskar-Idland it'd probably be good to transfer ownership of this repo from @draivin. mind reaching out to him?

superle3 commented 2 months ago

@PiotrSokol thanks for finding a workaround/solution. but wouldn't

await vscode.commands.executeCommand("editor.action.insertSnippet", {
    snippet: snippetInstance.snippetString.value,
    location: insertionRange,
    options: { undoStopAfter: false, undoStopBefore: false },
  });

be better since I assume this would preserve the original options.

also this will probably need to be forked, since the author does not seem to be active

Alex2134556 commented 2 months ago

Thank you all for finding the solution. I am unable to make this modification as I can't locate the stated file. Can somebody assist me in locating it?

superle3 commented 2 months ago

its in src/extension.ts in this github repo. in your local installation its in ~/.vscode/extensions/draivin.hsnips-0.2.9/out/extension.js

edit: for @Alex2134556

Oskar-Idland commented 2 months ago

I have a temporary solution to the problem. modifying extensions.ts:86:90 reverses the behavior.

  await vscode.commands.executeCommand('editor.action.insertSnippet', { "snippet": snippetInstance.snippetString.value });
  // await editor.insertSnippet(snippetInstance.snippetString, insertionRange, {
  //   undoStopAfter: false,
  //   undoStopBefore: false,
  // });

it's not the most elegant but can't figure out why vscode.TextEditor.insertSnippet doesn't work, esp. since the proposed seems to use it under the hood.

also @Oskar-Idland it'd probably be good to transfer ownership of this repo from @draivin. mind reaching out to him?

I have messaged him here, and sent an email many months ago. I do not think we should bet on him to return. I would be happy to fork the project, and will accept any working solution!

Oskar-Idland commented 2 months ago

The fork is made https://github.com/Oskar-Idland/hsnips

Oskar-Idland commented 2 months ago

I edited the part you mentioned @superle3, but I was not able to make it work. I do not know if this happens to anyone else, but without the HyperSnips for Math by OrangeX4 (a fork made in 20201), I do not get any snippets at all. I am currently using vscode version 1.93.1. Any one else have both installed?? It makes sense that your fix does not work if HyperSnips does not work, but I was curious if anyone else MUST have the fork installed?

XRay71 commented 2 months ago

I got it to work by modifying the .js files in the "out" folder of the actual extension in my local installation, but modifying the .ts files didn't work for me. Am on the latest version of vscode

Oskar-Idland commented 2 months ago

Same as @XRay71. The file is located at the same place on a linux OS, as in the repo: ~/.vscode/extensions/draivin.hsnips-0.2.9/out/extension.js. I assume it will be similar for mac, and windows somewhere in the AppData folder

XRay71 commented 2 months ago

I'm unfamiliar with how TypeScript works, but I do believe that it's supposed to be a compiled language; maybe you need to recompile the extension to JavaScript after modifying it to get it to work?

Oskar-Idland commented 2 months ago

You are right hahahah. Ofc it needs to be compiled. But then we must edit the typescript, then compile right? I have no experience with either language. It seems like we need to use tsc -p tsconfig.json in the extension folder, but I get errors as I am missing the vscode module and other

tsc -p tsconfig.json

output ```bash src/completion.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. src/dynamicRange.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. src/extension.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. src/extension.ts(2,78): error TS2307: Cannot find module 'fs' or its corresponding type declarations. src/extension.ts(3,23): error TS2307: Cannot find module 'path' or its corresponding type declarations. src/extension.ts(81,6): error TS7006: Parameter 'eb' implicitly has an 'any' type. src/extension.ts(164,45): error TS7006: Parameter 'document' implicitly has an 'any' type. src/extension.ts(174,8): error TS7006: Parameter 'editor' implicitly has an 'any' type. src/extension.ts(174,16): error TS7006: Parameter '_' implicitly has an 'any' type. src/extension.ts(182,47): error TS7006: Parameter 'e' implicitly has an 'any' type. src/extension.ts(225,51): error TS7006: Parameter 'e' implicitly has an 'any' type. src/extension.ts(227,32): error TS7006: Parameter 's' implicitly has an 'any' type. src/hsnippetInstance.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. src/hsnippetInstance.ts(178,7): error TS2584: Cannot find name 'console'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'. src/hsnippetInstance.ts(251,23): error TS7006: Parameter 'edit' implicitly has an 'any' type. src/parser.ts(143,63): error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`. src/utils.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. src/utils.ts(2,23): error TS2307: Cannot find module 'path' or its corresponding type declarations. src/utils.ts(3,21): error TS2307: Cannot find module 'os' or its corresponding type declarations. src/utils.ts(56,37): error TS18047: 'hsnipsPath' is possibly 'null'. src/utils.ts(70,39): error TS18047: 'hsnipsPath' is possibly 'null'. src/utils.ts(106,11): error TS2322: Type 'string | null' is not assignable to type 'string'. Type 'null' is not assignable to type 'string'. src/utils.ts(131,17): error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`. src/utils.ts(132,14): error TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`. types/hscopes.d.ts(1,25): error TS2307: Cannot find module 'vscode' or its corresponding type declarations. ```
Oskar-Idland commented 2 months ago

Hey, @OrangeX4. As you have some experience with this project would you please help us out and get this project running again? With a bit of help, I can continue my own fork, but it is a bit hard to get started without help from a creator or contributor. For now, only fork work on my linux machine. How much did you change? Any help to get us independent would be greatly appreciated!

PapierFliegr commented 2 months ago

@Oskar-Idland, do you have the HyperScope extension installed? I made the changes as specified by @superle3 and got it running without issues (and without installing the OrangeX4 fork).

After cloning the repo and opening the folder, I ran the following code lines in the hsnip folder

npm install -g @vscode/vsce
npm install

The first installs a utility to create a .visx package globally (didn't make it work locally). The second installs all necessary packages as specified in the project. Then pressing F5 to debug the extension, should run tsc -p ./ automatically (see package.json).

Tbh, I have no idea about typescript (I am not even a developer), but that worked for me.

superle3 commented 2 months ago

idk if this is necessary but to reiterate. For a LOCAL change on windows 10 native I got it to work by changing the file ~/.vscode/extensions/draivin.hsnips-0.2.9/out/extension.js (unix path here but should still work) where ~ is your userhome, can be found by cd ~ in powershell, does not work in explorer. if dravin ever updates the extension and this is still an issue the version number 0.2.9 will be different in the folder. unix systems should also have a home like folder where it is but i don't use mac or unix so look up vscode documentation yourself.

For the REPO change, so to make a new version not a local change you change src/extension.ts

the change in question

await vscode.commands.executeCommand("editor.action.insertSnippet", {
    snippet: snippetInstance.snippetString.value,
    location: insertionRange,
    options: { undoStopAfter: false, undoStopBefore: false },
  });
   // await editor.insertSnippet(snippetInstance.snippetString, insertionRange, {
  //   undoStopAfter: false,
  //   undoStopBefore: false,
  // });

hope this helps and this may be a bit too much explanation but I have been stuck at not knowing what ~ or home is since it doesn't work in explorer (don't judge me)

Oskar-Idland commented 2 months ago

Thanks for a good summary! We'll figure it out together

PiotrSokol commented 2 months ago

I'll fork the repo and figure out how to publish it to the vscode extension marketplace. AFAIK, need to verify myself so give me a few days.

For now look up locally packaging the extension. You'll need yarn/npm, and vsce.

Oskar-Idland commented 2 months ago

The change was easy to implement, and both direct edit and compilation was without issue. I have just noticed how the extension does not work at all on my linux machine. Anyone else have this issue? I looked at the output in the terminal from the "extension host" and found the snippet not able to activate:

extension host output

```bash Activating extension draivin.hsnips failed due to an error: 2024-09-25 23:02:52.598 [error] Error: ENOTEMPTY: directory not empty, rename '/home/oskar/.config/Code/User/hsnips' -> '/home/oskar/.config/Code/User/globalStorage/draivin.hsnips/hsnips' at renameSync (node:fs:1032:11) at activate (/home/oskar/.vscode/extensions/draivin.hsnips-0.2.9/out/extension.js:85:33) at q.kb (/opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:167:13836) at q.jb (/opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:167:13508) at /opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:167:11493 at async m.n (/opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:151:6409) at async m (/opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:151:6372) at async m.l (/opt/visual-studio-code/resources/app/out/vs/workbench/api/node/extensionHostProcess.js:151:5829) ```

The second line of the indented block references something being wrong on 85:33 in extension.js. Looking further at this I found this block:

function activate(context) {
    var _a;
    (_a = vscode.extensions.getExtension('draivin.hscopes')) === null || _a === void 0 ? void 0 : _a.activate();
    // migrating from the old, hardcoded directory to the new one. TODO: remove this at some point
    const oldGlobalSnippetDir = (0, utils_1.getOldGlobalSnippetDir)();
    if ((0, fs_1.existsSync)(oldGlobalSnippetDir)) {
        // only the global directory needs to be migrated, which is why `ignoreWorkspace` is set to `true` here
        const newSnippetDirInfo = (0, utils_1.getSnippetDirInfo)(context, { ignoreWorkspace: true });
        if (newSnippetDirInfo.type == utils_1.SnippetDirType.Global) {
            (0, fs_1.mkdirSync)(path.dirname(newSnippetDirInfo.path), { recursive: true });
            (0, fs_1.renameSync)(oldGlobalSnippetDir, newSnippetDirInfo.path);
        }
    }

At relative linenumber 5 or absolute linenumber 79, I se the use of getGlobalSnippetDir(), which is defined in out/utils.js:

/**
 * @deprecated The paths here are hardcoded in. Only keep this function so that older users can migrate.
 */
function getOldGlobalSnippetDir() {
    let hsnipsPath = vscode.workspace.getConfiguration('hsnips').get('hsnipsPath');
    if (hsnipsPath && path.isAbsolute(hsnipsPath)) {
        return hsnipsPath;
    }

This seems like a an old file structure, renamed, but with legacy code to keep everyone on board. I have neither platform == 'darwin' or platform == 'win32', so the path should be correct at ~/.config/Code/User/hsnips as written in the else-block. The problem must therefore be at the use of renameSync at line 85 in extension.js. I commented out this entire block on line 78 of extension.js in activate(context) and it worked again!

function activate(context) {
    var _a;
    (_a = vscode.extensions.getExtension('draivin.hscopes')) === null || _a === void 0 ? void 0 : _a.activate();
    //// migrating from the old, hardcoded directory to the new one. TODO: remove this at some point
    // const oldGlobalSnippetDir = (0, utils_1.getOldGlobalSnippetDir)();
    // if ((0, fs_1.existsSync)(oldGlobalSnippetDir)) {
        // only the global directory needs to be migrated, which is why `ignoreWorkspace` is set to `true` here
        const newSnippetDirInfo = (0, utils_1.getSnippetDirInfo)(context, { ignoreWorkspace: true });
        // if (newSnippetDirInfo.type == utils_1.SnippetDirType.Global) {
        //     (0, fs_1.mkdirSync)(path.dirname(newSnippetDirInfo.path), { recursive: true });
        //     (0, fs_1.renameSync)(oldGlobalSnippetDir, newSnippetDirInfo.path);
        // }
    // }
    loadSnippets(context);

As this is legacy, I might remove it on my new fork. Hopefully anyone in the same situation as I was can use this.

Oskar-Idland commented 2 months ago

I applied the change as summarized by @superle3 to the fork of hsnips by @OrangeX4, and it worked as well! This fork has more features, like a math flag, VISUAL variable for selection, a great starting config and amazing matrix/table support. This fork was last updated in march 2023. If the creator of the fork is busy or not interested, I believe it to be better to fork @OrangeX4's fork again with the update. I can figure out how to release an extension this weekend. Does this sound like a good solution?

XRay71 commented 2 months ago

Something which may be related to this issue is the fact that nested tabstops also sometimes get mixed up with the outer tabstops. For example, if I have a snippet that generates, say, 10 tabstops (a_1, ..., a_10), and then use another snippet inside that generates, say, 90 tabstops (b_1, ..., b_10), I find that the order of tabstops goes b_1, ..., b_9, a_1, b_10, ..., b_17, a_2, etc.

I've read through the code several times in search of where this behaviour may be coming from, but came up with nothing, so I believe it may also be an issue with the VSCode API. Can anyone replicate?

Oskar-Idland commented 2 months ago

Something which may be related to this issue is the fact that nested tabstops also sometimes get mixed up with the outer tabstops. For example, if I have a snippet that generates, say, 10 tabstops (a_1, ..., a_10), and then use another snippet inside that generates, say, 90 tabstops (b_1, ..., b_10), I find that the order of tabstops goes b_1, ..., b_9, a_1, b_10, ..., b_17, a_2, etc.

I've read through the code several times in search of where this behaviour may be coming from, but came up with nothing, so I believe it may also be an issue with the VSCode API. Can anyone replicate?

@superle3 and @PiotrSokol had both different solutions to the original problem. Do both solutions create this problem?

superle3 commented 2 months ago

@XRay71 could you send the snippet that caused this problem and is does the change only partially work or just not at all. Because im not able to replicate it with

snippet trigger1 "Trigger 1" i
b_${1} b_${2} b_${3} b_${4} b_${5} b_${6} b_${7} b_${8} b_${9} b_${10}
endsnippet

snippet trigger2 "Trigger 2" i
a_${1} a_${2} a_${3} a_${4} a_${5} a_${6} a_${7} a_${8} a_${9} a_${10}
endsnippet

also I haven't found a difference between the two implentations. It SHOULD give different behavior for undo but it doesn't. Seems like another bug. The documentation is a bit vague https://vscode-api.js.org/interfaces/vscode.TextEditor.html . But doing C-Z after trigger1 reverts back to nothing. this does still work with 1.85.2 and with normal vscode snippets

edit: I think this is because

await editor.edit(
    (eb) => {
      eb.delete(snippetExpansion ? completion.completionRange : completion.range);
    },
    { undoStopAfter: false, undoStopBefore: !snippetExpansion },
  );

is broken, where the undoStopBefore isn't happening

XRay71 commented 2 months ago

I have tried both of their solutions, both still contain this issue.

snippet expand1 A
``let out = "", order = 1; for (let i = 0; i < 10; i++) out += " | " + snip.tabstop(order++); rv = out;``
endsnippet

snippet expand2 A
``let out = "", order = 1; for (let i = 0; i < 30; i++) out += " | " + snip.tabstop(order++); rv = out;``
endsnippet

@superle3 I am also able to replicate it with your snippet there. Try running trigger1 and then immediately trigger2, then tab to the final "a". You will see that both the final "a" and the second "b" tabstops are highlighted. image

superle3 commented 2 months ago

@XRay71 oh yes you're right, I didn't check if it worked when I pressed tab. Though this is "expected behaviour" it seems. same behaviour in 1.85.2 and for normal snippets. Another bug to report to vscode.

[
    "test1": {
        "prefix": "test1",
        "body": [
            "a_$1 a_$2 a_$3 a_$4 a_$5 a_$6 a_$7 a_$8 a_$9 a_$10"
        ],
        "description": "test1"
    },
    "test2": {
        "prefix": "test2",
        "body": [
            "b_$1 b_$2 b_$3 b_$4 b_$5 b_$6 b_$7 b_$8 b_$9 b_$10"
        ],
        "description": "test2"
    }]
Oskar-Idland commented 2 months ago

I have made a new repo, and published to the marketplace. Would love to collaborate! See HyperSnips V2 on github or marketplace

image

superle3 commented 2 months ago

@XRay71 I reported this to vscode in https://github.com/microsoft/vscode/issues/230149. Everyone please also upvote this issue to create attention to it