3

Context

For the past 18 months, I've been using the LaTeX Workshop extension of VSCode for all my LaTeXing needs. Up to this point, I've primarily used it for longer-form articles and reports, and sporadically for note-taking purposes in class. I've been able to make it work sufficiently well in real-time so far with just a few custom macros (Linear Algebra typesets pretty easily). However, as I move on to different classes, I'm looking to expand my real-time abilities by implementing Gilles Castel's excellent Vim-based workflow in VSCode. Unfortunately, VSCode seems to obscure or lack (by default) a great deal of the features (especially related to snippets) of which Castel makes use.

My Question

For the purposes of this post, I'd like to focus on his fraction macro (I believe that if I can get this working, I can get much of the rest of it working). Basically, the problem appears to be that VSCode has fairly limited snippets functionality, especially compared to Vim's UltiSnips. Using UltiSnips, Castel has defined an auto-expanding macro (I'm not sure if VSCode supports auto-expanding snippets) that, when a / is typed, takes the preceding word (or words if parentheses are present) and transforms it into the LaTeX fraction format. For example:

//             --> \frac{}{}
3/             --> \frac{3}{}
4\pi^2/        --> \frac{4\pi^2}{}
(1 + 2 + 3)/   --> \frac{1 + 2 + 3}{}
(1 + (2 + 3)/) --> (1 + \frac{2 + 3}{})
(1 + (2 + 3))/ --> \frac{1 + (2 + 3)}{}

How can I implement this behavior in VSCode?

My Leads

I have spent a significant amount of time researching this, and I have strong reasons to believe that it's possible, and similarly strong reasons to believe that any successful answer to my question is going to have to ELI5 it to me -- this kind of software customization isn't exactly my cup of soup, but I'm definitely willing to learn!

First off, there are two promising VSCode extensions that may be able to implement UltiSnips: Vsnips and HyperSnips. Vsnips looks decent, but it seems to rely on an existing familiarity with UltiSnips and how to configure UltiSnips for your specific computer (if this does end up being important, I use a 2019 MacBook Pro and my software is up to date [macOS Catalina 10.15.5 as of this post]). I haven't been able to figure out even that much with regard to HyperSnips -- neither is very well documented, and none of the documentation is written for readers of my level.

Although I said that VSCode's internal snippets engine appears to be fairly limited, I may be wrong. It seems to interface with another snippets engine called TextMate.

That's all I can think to do for now. If there's any further information that I can provide, please let me know! Thanks!

Mark
  • 143,421
  • 24
  • 428
  • 436
Shady Puck
  • 133
  • 1
  • 5

2 Answers2

7

After you install HyperSnips, use its command HyperSnips: Open snippets directory to open the directory where you will put your snippets.

Snippets in all.hsnips will be available in all language files. You can also put your snippets into something like latex.hsnips or Latex.hsnips in the same directory and both versions work for me.


Modifying the code from Castel's guide put this into your chosen <language>.hsnips file:

snippet // "Fraction simple" A
\frac{$1}{$2}$0
endsnippet

snippet `((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*)/` "Fraction no ()" A
\frac{``rv = m[1]``}{$1}$0
endsnippet

Note that interpolated code goes into double backticks "``" and the value from that code must be assigned to rv (return value). Whatever is assigned to rv will appear in the snippet output. Note also there are additional tabstops $1, $2 and $0 in the above snippets - there values can be accessed by the interpolated code within the t array but you don't need that here.

And then here is the final snippet that works for the harder case of embedded parentheses within your "prefix" like (1 + (2 + 3))/. I think of (1 + (2 + 3))/ just like a traditional vscode snippet prefix EXCEPT that you can use regex expressions as the prefix!! Regex prefixes/triggers must within backticks.

snippet `^.*\)/` "Fraction with ()" A
``
    let str = m[0];
    str = str.slice(0, -1);
    let lastIndex = str.length - 1;

    let depth = 0;
    let i = str.length - 1;

    while (true) {
        if (str[i] == ')') depth += 1;
        if (str[i] == '(') depth -= 1;
        if (depth == 0) break;
        i -= 1;
    }

    let results = str.slice(0, i) + "\\frac{" + str.slice(i+1, -1) + "}";
    results += "{$1}$0";
    rv = results;
    ``
endsnippet

Here ^.*\)/ is the prefix/trigger. The extension looks at all your code as you type for that pattern which is basically at least one ) before a / and then match everything before those to the previous word boundary. And then that match info is available within the matched code as m[0]. You can have capture groups with your prefix/trigger and access them in m[1], etc. but that isn't needed here.

As you can see, the code to be interpolated must be javascript for this extension to work.

The location of that first set of backticks is important! Here

snippet `^.*\)/` "Fraction with ()" A
``
   <other code indented here>
   ``  <indented or flush left, didn't seem to matter in my testing>
endsnippet

the indented code is easier to read IMO, but the output will also be indented unless that first set of backticks is not indented (unless of course you want the output indented). I don't that if that is a "quirk" or as planned. But the position of that first set of backticks seems to determine the location of the output.

The body of this final snippet (again code from the guide you linked to, but translated to javascript by me from his python code) just figures out how far to backtrack (character by character) to get an even number of parentheses. Any preceding part of the input prefix/trigger goes before the \frac part.

After making changes to this file, always run the command HyperSnips: Reload Snippets to ensure they are ready for testing immediately.

Demo in action:

HyperSnips latex fraction demo

Mark
  • 143,421
  • 24
  • 428
  • 436
  • 1
    Hey! Just got fractions working -- so happy! Wondering if it is possible in Hypersnips to do it in [context](https://castel.dev/post/lecture-notes-1/#context). Castel has this long list of things that makes it so these math-y snippets only expand in LaTeX's math mode. Is this possible? – Shady Puck Jun 26 '20 at 00:57
  • I really doubt it on the context - I'm not familiar with LaTex, much less its modes. You might avoid the A automatic flag and require a tab to insert the change instead to help. – Mark Jul 01 '20 at 05:13
  • @Mark I don't know what's wrong, but the snippet for the fraction with parenthesis returns the literal string `{$1}$0` instead of interpreting it as a tab stop. – soupless Apr 01 '22 at 18:25
  • @soupless I have the same issue. I tweaked around with some settings and I realized that it works just fine if you use the version from an year ago. Probably the Hypersnips' developer pushed some updates which generates this issue. If you really need this snippet, you can roll back to a previous version of Hypersnips (I'm sure it'll work with v0.1.1.11 – Tachyon209 May 16 '22 at 17:12
  • @Tachyon209 Thank you very much for informing me. Although I got used to typing everything, this will make it easier. – soupless May 16 '22 at 19:40
-1

I saw that you asked how to do snippets exclusively for math mode in the comments. You can do that! The HyperSnips Readme writes something about that and I got it working too:

https://marketplace.visualstudio.com/items?itemName=draivin.hsnips

function math(context) {
    return context.scopes.some(s => s.startsWith("meta.math"));
}
endglobal

context math(context)
snippet inv "inverse" Ai
^{-1}
endsnippet

That way, every snippet with the context math(context) written before only works in math mode!

Hebol
  • 51
  • 7