Skip to content
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

Custom keyboard shortcuts #92

Closed
ghuba opened this issue Sep 10, 2013 · 30 comments
Closed

Custom keyboard shortcuts #92

ghuba opened this issue Sep 10, 2013 · 30 comments
Labels

Comments

@ghuba
Copy link

ghuba commented Sep 10, 2013

Hi,

Is there a way to create new keyboard shortcuts to insert snippets of text? This would be useful for the equation editor. Can the "UserCustom extension" be used for that? Thanks for the help.

@benweet
Copy link
Owner

benweet commented Sep 10, 2013

Yes, you can start with something like this:

    userCustom.onReady = function() {
        var $textarea = $('#wmd-input');
        Mousetrap.bind('ctrl+1', function() {
            $textarea.val($textarea.val() + ' ctrl 1');
        });
    };

You can have a look to the Mousetrap documentation to have more sophisticated shortcuts. However, I'm working on replacing the textarea with something more evolutive that it will make this code obsolete.

@ghuba
Copy link
Author

ghuba commented Sep 10, 2013

Nice! How would you change this code to insert the text at the cursor location? It seems this code always adds at the end. I tried to find some examples of this in your javascript source code but it's a bit complicated to figure out.

@benweet
Copy link
Owner

benweet commented Sep 10, 2013

These kind of things are managed by the Pagedown editor and it's not that straight forward. You have to play with the fields selectionStart and selectionEnd of the textarea (and it's different for some browsers). Basically, on Chrome, to insert the text after the caret, you do something like:

var value = $textarea.val();
value.substring(0, $textarea[0].selectionStart) + ' ctrl 1' + value.substring($textarea[0].selectionEnd)

But then, after doing $textarea.val(value), it will still move the caret to the end. So you need restore its position:

var caretPos = $textarea[0].selectionStart + ' ctrl 1'.length;
...
$textarea[0].selectionStart = caretPos;
$textarea[0].selectionEnd = caretPos;

Not tested. And you may have to manage the scrolling if it moves to the end...

@ghuba
Copy link
Author

ghuba commented Sep 10, 2013

Thanks. I went for the following. I used triggers, which I am familiar with.

Type eq + tab to get an equation ($$ $$), fr + tab to get a fraction (\frac{}{}), ma + tab for a matrix. I am not sure what shortcuts people would find useful. One could create single letter shortcuts for the Greek alphabet, like a + tab maps to \alpha.

userCustom.onReady = function() {
var $textarea = $('#wmd-input');
Mousetrap.bind('e q tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart;
var prefix = '$$';
var selection = ' ';
var suffix = '$$';
$textarea.val(value.substring(0, $textarea[0].selectionStart - 3) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos-3+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
Mousetrap.bind('m a tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart;
var prefix = '\begin{pmatrix}\n';
var selection = '1 & 2 \\n3 & 4';
var suffix = '\n\end{pmatrix}';
$textarea.val(value.substring(0, $textarea[0].selectionStart - 3) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos-3+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
Mousetrap.bind('f r tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart;
var prefix = '\frac{';
var selection = ' ';
var suffix = '}{}';
$textarea.val(value.substring(0, $textarea[0].selectionStart - 3) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos-3+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
};

@ghuba
Copy link
Author

ghuba commented Sep 10, 2013

Same thing, shorter code:

userCustom.onReady = function() {
var $textarea = $('#wmd-input');
Mousetrap.bind('e q tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart - 3;
var prefix = '$$';
var selection = ' ';
var suffix = '$$';
$textarea.val(value.substring(0, caretPos) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
Mousetrap.bind('m a tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart - 3;
var prefix = '\begin{pmatrix}\n';
var selection = '1 & 2 \\n3 & 4';
var suffix = '\n\end{pmatrix}';
$textarea.val(value.substring(0, caretPos) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
Mousetrap.bind('f r tab', function() {
var value = $textarea.val();
var caretPos = $textarea[0].selectionStart - 3;
var prefix = '\frac{';
var selection = ' ';
var suffix = '}{}';
$textarea.val(value.substring(0, caretPos) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
$textarea[0].selectionStart = caretPos+prefix.length;
$textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
});
};

@benweet
Copy link
Owner

benweet commented Sep 10, 2013

You could have declared a function just after the line var $textarea = $('#wmd-input');:

function doShortcut(prefix, selection, suffix) {
    var value = $textarea.val();
    var caretPos = $textarea[0].selectionStart - 3;

    $textarea.val(value.substring(0, caretPos) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
    $textarea[0].selectionStart = caretPos+prefix.length;
    $textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
}

And call it like this:

Mousetrap.bind('m a tab', function() {
    doShortcut('\begin{pmatrix}\n', '1 & 2 \\\n3 & 4', '\n\end{pmatrix}');
});

By the way, the operation $textarea[0].selectionStart - 3 is puzzling me...

@ghuba
Copy link
Author

ghuba commented Sep 11, 2013

OK. Here is the new code:

userCustom.onReady = function() {
    var $textarea = $('#wmd-input');

    function insertSnippet(prefix, selection, suffix, combo) {
        var value = $textarea.val();
        var caretPos = $textarea[0].selectionStart - (combo.length-3)/2 - 1;
        $textarea.val(value.substring(0, caretPos) + prefix + selection + suffix + value.substring($textarea[0].selectionEnd));
        $textarea[0].selectionStart = caretPos+prefix.length;
        $textarea[0].selectionEnd = $textarea[0].selectionStart+selection.length;
    }

    Mousetrap.bind('e q tab', function(e,combo) {
        var prefix = '$$';
        var selection = ' ';
        var suffix = '$$';
        insertSnippet(prefix, selection, suffix, combo);
    });

    Mousetrap.bind('m a tab', function(e,combo) {
        var prefix = '\\begin{pmatrix}\n';
        var selection = ' 1 & 2 \\\\\n 3 & 4';
        var suffix = '\n\\end{pmatrix}';
        insertSnippet(prefix, selection, suffix, combo);        
    });

    Mousetrap.bind('f r tab', function(e,combo) {
        var prefix = '\\frac{';
        var selection = ' ';
        var suffix = '}{}';
        insertSnippet(prefix, selection, suffix, combo);    
    });

    Mousetrap.bind('i n f tab', function(e,combo) {
        var prefix = '\\infty';
        var selection = '';
        var suffix = '';
        insertSnippet(prefix, selection, suffix, combo);    
    });

};

Explanation:
var caretPos = $textarea[0].selectionStart - (combo.length-3)/2 - 1;
combo.length is the number of characters that define the shortcut, for example
e q tab
7 characters. To get the trigger we in effect type in 3 characters: e, q, TAB. So we take the current caret location. Then we need to move back 3 characters, otherwise the characters eqTAB will remain in the text. Instead we want to get rid of the key trigger and replace it by the new text. Hence the formula: - (combo.length-3)/2 - 1. The advantage of the formula is that if you want the shortcut
i n f tab
say for \infty (infinity character), the current code still works.

combo.length-3 = number of characters without tab at the end
Each key is followed by space. Hence:
(combo.length-3)/2 = actual number of keys typed
(combo.length-3)/2 + 1: add back the tab to get the number of characters to move back.

@benweet
Copy link
Owner

benweet commented Sep 15, 2013

I deployed a new version. This code shouldn't be working anymore... To adapt your code, you will need to add this at the very beginning:

    var aceEditor = undefined;
    userCustom.onAceCreated = function(aceEditorParam) {
        aceEditor = aceEditorParam;
    };

And then play with the ACE editor instead of the textarea.

@ghuba
Copy link
Author

ghuba commented Sep 15, 2013

Thanks. Will try.

@ghuba
Copy link
Author

ghuba commented Sep 21, 2013

I tried to use your code but with limited success. I am probably misunderstanding something. Here is what I tried

userCustom.onReady = function() {

    console.log("In userCustom.onReady");

    var editor = undefined;

    userCustom.onAceCreated = function(aceEditorParam) {
        editor = aceEditorParam;

        if (editor !== undefined) {
            console.log("aceEditor defined");
        }  else {
            console.log("aceEditor un-defined");
        }
    };      
};

The console does print

In userCustom.onReady

but no message after that. Is the code correct? How come the function does not get executed?

@ghuba
Copy link
Author

ghuba commented Sep 21, 2013

I figured out the correct syntax. I will post some code shortly in case other people are interested.

@ghuba
Copy link
Author

ghuba commented Sep 22, 2013

Here is the code that I am using:

userCustom.onAceCreated = function(aceEditorParam) {
    editor = aceEditorParam;

    console.log("onAceCreated");

    editor.commands.addCommand({
        name: 'Insert a fraction',
        bindKey: {win: 'Ctrl-F',  mac: 'Command-F'},
        exec: function(editor) {
            editor.insert("\\frac{}{}");
            var selection = editor.getSelection();
            selection.moveCursorBy(0, -3);
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });     

    editor.commands.addCommand({
        name: 'Insert a matrix',
        bindKey: {win: 'Ctrl-M',  mac: 'Command-M'},
        exec: function(editor) {
            editor.insert("\\begin{pmatrix}\n1 & 0 \\\\\n0 & 1\n\\end{pmatrix}");
            var selection = editor.getSelection();
            selection.moveCursorBy(-2, -8);
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });     

    editor.commands.addCommand({
        name: 'Insert a display equation',
        bindKey: {win: 'Ctrl-E',  mac: 'Command-E'},
        exec: function(editor) {
            editor.insert("$$\n\n$$");
            var selection = editor.getSelection();
            selection.moveCursorBy(-1, 0);          
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });     
};

@benweet
Copy link
Owner

benweet commented Sep 22, 2013

Excellent. Thank you for sharing that.

@kasperpeulen
Copy link

@ghuba This is an intresting feature. Mathjax supports unicode, and you could use shortcuts to for example convert \mathbb{R} to ℝ in the editor. In some situations, this makes the editor code a lot more readable. Or better use the shortcuts the user prefers, for example \R or R? (Office word style)

@ghuba
Copy link
Author

ghuba commented Sep 26, 2013

That's probably feasible. This is something I was thinking about in a slightly different context. I think with ACE it's possible to, for example, program the Tab key to do something special. For example you press Tab, a function then runs that looks at the characters that were typed before Tab. Then if it's a special sequence like \alpha or \mathbb{R} (or some other easier to remember sequence of characters), the function replaces that sequence by the appropriate unicode character. The code in the left panel would indeed look a lot better. Emacs/Xemacs has a similar functionality that's pretty handy called X-symbol. It would work similarly here. Is there a doc of the unicode Mathjax supports?

http://x-symbol.sourceforge.net/

@benweet
Copy link
Owner

benweet commented Sep 26, 2013

Here is the list of all available macros. And here is a page with the special characters and the corresponding ISO codes.

@kasperpeulen
Copy link

@ghuba I've been using unicode symbols with mathjax for around a year now. I use autohotkey for that. However, this doesn't work on the pc's at the university, so adding this a extension to stackedit, sounds very good to me.

All the unicode I've tried so far, seemed to work, except for √ and ∑. Those symbols are displayed, but mathjax doesn't seem to understand that this is a macro instead of just a symbol.

Edit: Wow, I'm amazed, it seems like, you can even use those unicode symbols as a macro if you tell mathjax too. Look here: http://jsfiddle.net/tVyJz/21/

@kasperpeulen
Copy link

@ghuba I've been trying to write some custom shortcuts in ace editor. I'm not good at all with javascript, but a very nice ace editor guy has helped me out. Here is the code: http://jsfiddle.net/fKvaR/
Sadly enough, there are some unicode math symbols that are not supported by the monospace fonts, such as ℕ,∈ or ⇔. This give problems with the ace editor. There is however a pull request that adds support for non monospace fonts to ace editor: ajaxorg/ace#1608. And this is the live demo: https://brionv.com/misc/demo/ace/build/kitchen-sink.html

@benweet Is it possible to add this code: http://jsfiddle.net/fKvaR/ as an usercustom extension ?

@benweet
Copy link
Owner

benweet commented Nov 1, 2013

So, this is the custom extension:

var corrections = {
      a: "α",
      b: "β",
      g: "γ",// why this isn't g? it now is ;)
      d: "δ",
      e: "ϵ",
      emp : "∅",
      f: "\\frac{}{}",
      in : "∈",
      s: "σ",
      t: "\\text{}",
      tau : "τ",
      th : "θ",
      p: "π",
      pm : "±",
      o : "ω",
      O : "Ω",
      r : "ρ",
      A : "∀",
      E : "∃",
      R: "ℝ",
      C: "ℂ",
      H: "ℍ",
      N: "ℕ",
      Q: "ℚ",
      Z: "ℤ",
      int: "\\int_{}^{}",
      inf : "∞",
      sum : "\\sum_{}^{}",
      "-1": "^{-1}",
      ph: "ϕ",
      ch: "χ",
      ps: "ψ",
      leq : "≥",
      xi : "ξ", 
      geq : "≤",
      M22 : "\\begin{pmatrix}\n1 & 0 \\\\\n0 & 1\n\\end{pmatrix}\n",
      "=" : "≠",
      "==" : "≡",
      "<" : "\\langle",
      ">" : "\\rangle",
      "->" : "→",
      "=>" : "⇒",
      "<=" : "⇐",
      "<>" : "⇔",
      "sq" : "\\sqrt{}" 
};

userCustom.onAceCreated = function(editor) {
    var Range = require("ace/range").Range;
    editor.commands.on("afterExec", function(e) {
      if (e.command.name == "insertstring")
      if (e.args == " ") {
        var pos = editor.getCursorPosition()
        var line = editor.session.getLine(pos.row)
        var index = line.lastIndexOf("\\", pos.column) + 1
        var match = line.substring(index, pos.column - 1)
        console.log(match)
        if (index && corrections.hasOwnProperty(match)){
          editor.session.replace(
            new Range(pos.row, index - 1, pos.row, pos.column)
          , corrections[match])
          if (corrections[match].indexOf("{}") !=-1 )
          { 
            editor.moveCursorTo(pos.row,index +corrections[match].indexOf("{}") );
          }
        }
      }
    })
};

The ℕ,∈ or ⇔ are working fine for me on OSX. Have you played with different monospaced fonts?

Regarding the ACE mode, you can set it to ace/mode/latex here (which would solve your issue #113) but I'm thinking in a proper way to nest latex in markdown. I think it's possible with ACE, for instance, the HTML mode supports nested javascript blocks.

@benweet
Copy link
Owner

benweet commented Nov 1, 2013

Note: ace/mode/latex is not available on the production server. It's only available in debug mode.

@kasperpeulen
Copy link

So, this is the custom extension

Thanks, works perfect :)

The ℕ,∈ or ⇔ are working fine for me on OSX. Have you played with different monospaced fonts?

Which font do you use at OSX ? I've been testing with unifont. This font contains all (!) unicode symbols and every symbol has the same width or exactly double the width. It is however not installed on the university pc, and it doesn't work as a webfont (as it has too many glyphs).

Regarding the ACE mode, you can set it to ace/mode/latex here (which would solve your issue #113) but I'm thinking in a proper way to nest latex in markdown. I think it's possible with ACE, for instance, the HTML mode supports nested javascript blocks.

That would be perfect !

@benweet
Copy link
Owner

benweet commented Dec 9, 2013

Update from @ghuba :

  var corrections = {
        a: "α",
        b: "β",
        bigcap: "⋂",
        bigcup: "⋃",
        bullet: "∙",
        c: "χ",      
        cap: "∩",
        cdot: "⋅",
        cdots: "⋯",
        checkmark: "✓",
        circ: "∘",
        colon: ":",
        cup: "∪",
        dagger: "†",
        dot: "˙",   
        d: "δ",
        D: "Δ",
        div: "÷",
        dots: "…",
        g: "γ",
        e: "ϵ",
        empty : "∅",
        eqsim: "≂",
        eta: "η",
        exists: "∃",
        f: "ϕ",
        F: "Φ",      
        forall: "∀",
        G: "Γ",
        g: "γ",
        ge : "≥",      
        gg: "≫",
        gtrsim: "≳",
        h: "ℏ",
        i: "ι",      
        iff: "⟺",
        inf : "∞",
        int: "∫_{}^{}",      
        imath: "ı",
        implies: "⟹",      
        in: "∈",
        L: "Λ",
        l: "λ",
        "<": "⟨",
        ">": "⟩",
        lceil: "⌈",
        ldots: "…",
        le : "≤",      
        leftarrow: "←",
        "<-" : "→",      
        Leftarrow: "⇐",
        "<=" : "⇐",       
        leftrightarrow: "↔",
        "<>": "↔",      
        Leftrightarrow: "⇔",
        lfloor: "⌊",
        ll: "≪",      
        m: "μ",
        mathring: "˚",
        mho: "℧",      
        mp: "∓",
        n: "ν",      
        nabla: "∇",
        ne: "≠",
        "=": "≠",      
        nexists: "∄",
        notin: "∉",
        o: "ω",
        O: "Ω",      
        otimes: "⊗",
        oplus: "⊕",
        oint: "∮_{}^{}",
        omicron: "ο",
        parallel: "∥",
        partial: "∂",
        perp: "⊥",
        p: "π",
        P: "Π",      
        pm: "±",
        prod: "∏",
        propto: "∝",      
        psi: "ψ",
        Psi: "Ψ",
        rceil: "⌉",
        Re: "ℜ",
        rfloor: "⌋",      
        r : "ρ",    
        rightarrow: "→",
        "->" : "→",
        Rightarrow: "⇒",
        "=>": "⇒",      
        s: "σ",
        S: "Σ",
        sim: "∼",
        simeq: "≃",
        sq: "√{}",   
        star: "⋆",
        subset: "⊂",
        subseteq: "⊆",
        subsetneq: "⊊",
        sum : "∑_{}^{}",
        supset: "⊃",
        supseteq: "⊇",
        supsetneq: "⊋",
        surd: "√",
        t: "τ",
        q: "θ",
        Q: "Θ",
        times: "×",
        to: "→",
        triangle: "△",
        txt: "\text{}",      
        u: "υ",
        v: "∨",
        w: "∧", // wedge
        x: "ξ", // xi
        X: "Ξ", // Xi
        z: "ζ", // zeta
        fr: "\\frac{}{}",       
        A: "∀",
        E: "∃",
        bR: "ℝ", // Black-board letters
        bC: "ℂ",
        bH: "ℍ",
        bN: "ℕ",
        bQ: "ℚ",
        bZ: "ℤ",
        "-1": "^{-1}",
        M22: "\\begin{pmatrix}\n1 & 0 \\\\\n0 & 1\n\\end{pmatrix}\n", // a 2x2 matrix
        "==": "≡"
  };

  userCustom.onAceCreated = function(editor) {
      var Range = require("ace/range").Range;
      editor.commands.on("afterExec", function(e) {
        if (e.command.name == "insertstring")
              if (e.args == " ") {
                var pos = editor.getCursorPosition()
                var line = editor.session.getLine(pos.row)
                var index = line.lastIndexOf("\\", pos.column) + 1
                var match = line.substring(index, pos.column - 1)
                console.log(match)
                if (index && corrections.hasOwnProperty(match)){
                  editor.session.replace(
                    new Range(pos.row, index - 1, pos.row, pos.column)
                    , corrections[match])
                  if (corrections[match].indexOf("{}") !=-1 )
                  { 
                    editor.moveCursorTo(pos.row,index +corrections[match].indexOf("{}") );
              }
        }
  }
  })
  };

@benweet
Copy link
Owner

benweet commented Apr 8, 2014

Just copy/paste in Settings>Extensions>UserCustom.

@jbaum98
Copy link

jbaum98 commented Apr 8, 2014

Ah I didn't know you could click on those extensions for settings thanks so much.

@jbaum98
Copy link

jbaum98 commented Apr 9, 2014

I modified and added to the above code and I thought I'd share the changes I made. The first section is a tweaked version of the replacements above for my personal tastes, emphasizing trig functions and their inverses.

In the second part, I added shortcuts to aid cursor movement.

  • Ctrl/Cmd - E has different behavior depending on if you are inside a Latex block, which it determines by counting the $'s on either side of the cursor.
    • If you aren't in a block and puts a $ on either side of your cursor to start a new block
    • If you are inside a block that has been closed already and jumps to the closing $
    • If you are inside a block that hasn't yet been closed it inserts a $ at the first whitespace and moves your cursor there
  • Ctrl/Cmd - J moves your cursor to the next { so you can jump quickly between the inputs after inserting something like\frac{}{} or \cos^{-1}({})
  • Ctrl/Cmd-Shift - J moves your cursor to the previous { for the same reason
// REPLACEMENTS
var corrections = {
        a: "α",
        b: "β",
        bigcap: "⋂",
        bigcup: "⋃",
        bullet: "∙",    
        cap: "∩",
        cdot: "⋅",
        cdots: "⋯",
        checkmark: "✓",
        circ: "∘",
        colon: ":",
        cup: "∪",
        dagger: "†",
        deg: "º",
        dot: "˙",   
        d: "δ",
        D: "Δ",
        div: "÷",
        dots: "…",
        g: "γ",
        e: "ϵ",
        empty : "∅",
        eqsim: "≂",
        eta: "η",
        exists: "∃",
        f: "ϕ",
        F: "Φ",      
        forall: "∀",
        G: "Γ",
        ge : "≥",      
        gg: "≫",
        gtrsim: "≳",
        h: "ℏ",
        i: "ι",      
        iff: "⟺",
        inf : "∞",
        int: "∫_{}^{}",      
        imath: "ı",
        implies: "⟹",      
        in: "∈",
        L: "Λ",
        l: "λ",
        "<": "⟨",
        ">": "⟩",
        lceil: "⌈",
        ldots: "…",
        le : "≤",      
        leftarrow: "←",
        "<-" : "→",      
        Leftarrow: "⇐",
        "<=" : "⇐",       
        leftrightarrow: "↔",
        "<>": "↔",      
        Leftrightarrow: "⇔",
        lfloor: "⌊",
        ll: "≪",      
        m: "μ",
        mathring: "˚",
        mho: "℧",      
        mp: "∓",
        n: "ν",      
        nabla: "∇",
        ne: "≠",
        "=": "≠",      
        nexists: "∄",
        notin: "∉",
        o: "ω",
        O: "Ω",      
        otimes: "⊗",
        oplus: "⊕",
        oint: "∮_{}^{}",
        omicron: "ο",
        parallel: "∥",
        partial: "∂",
        perp: "⊥",
        p: "π",
        P: "Π",      
        pm: "±",
        prod: "∏",
        propto: "∝",      
        psi: "ψ",
        Psi: "Ψ",
        rceil: "⌉",
        Re: "ℜ",
        rfloor: "⌋",      
        r : "ρ",    
        rightarrow: "→",
        "->" : "→",
        Rightarrow: "⇒",
        "=>": "⇒",      
        S: "Σ",
        sim: "∼",
        simeq: "≃",
        sq: "√{}",   
        star: "⋆",
        subset: "⊂",
        subseteq: "⊆",
        subsetneq: "⊊",
        sum : "∑_{}^{}",
        supset: "⊃",
        supseteq: "⊇",
        supsetneq: "⊋",
        surd: "√",
        q: "θ",
        Q: "Θ",
        times: "×",
        to: "→",
        triangle: "△",
        txt: "\text{}",      
        u: "υ",
        v: "∨",
        w: "∧", // wedge
        x: "ξ", // xi
        X: "Ξ", // Xi
        z: "ζ", // zeta
        fr: "\\frac{}{}",       
        A: "∀",
        E: "∃",
        bR: "ℝ", // Black-board letters
        bC: "ℂ",
        bH: "ℍ",
        bN: "ℕ",
        bQ: "ℚ",
        bZ: "ℤ",
        "-1": "^{-1}",
        M22: "\\begin{pmatrix}\n1 & 0 \\\\\n0 & 1\n\\end{pmatrix}\n", // a 2x2 matrix
        "==": "≡",
        s: "\\sin({})", //Trig
        c: "\\cos({})",
        t: "\\tan({})",
        cs: "\\csc({})",
        sc: "\\sec({})",
        ct: "\\cot({})", 
        si: "\\sin^{-1}({})",
        ci: "\\cos^{-1}({})",
        ti: "\\tan^{-1}({})",
        csi: "\\csc^{-1}({})",
        sci: "\\sec^{-1}({})",
        cti: "\\cot^{-1}({})",
        pf: "\\frac{π}{}"
  };

  userCustom.onAceCreated = function(editor) {
      var Range = require("ace/range").Range;
      editor.commands.on("afterExec", function(e) {
        if (e.command.name == "insertstring")
              if (e.args == " ") {
                var pos = editor.getCursorPosition();
                var line = editor.session.getLine(pos.row);
                var index = line.lastIndexOf("\\", pos.column) + 1;
                var match = line.substring(index, pos.column - 1);
                if (index && corrections.hasOwnProperty(match)){
                  editor.session.replace(
                    new Range(pos.row, index - 1, pos.row, pos.column), 
                      corrections[match]);
                  if (corrections[match].search(/{.*?}/) !=-1)
                  { 
                    editor.moveCursorTo(pos.row,index +corrections[match].search(/{.*?}/) );
              }
        }
  }
  });
      // CURSOR MOVEMENT
      editor.commands.addCommand({
          name: 'Insert Latex',
          bindKey: {win: "Ctrl-E", mac: "Command-E"},
          exec: function(editor) {
                var selection = editor.getSelection(); 
                var pos = editor.getCursorPosition();
                var line = editor.session.getLine(pos.row);
                var behind = line.slice(0,pos.column);
                var behind_count;
                if (behind.match(/\$/g) === null) {behind_count = 0;} else {behind_count = behind.match(/\$/g).length;}
                var ahead = line.slice(pos.column);
                var ahead_count;
                if (ahead.match(/\$/g) === null) {ahead_count = 0;} else {ahead_count = ahead.match(/\$/g).length;}

                if (behind_count % 2 == 1) { // odd number of $ behind, inside a Latex expression
                    if (ahead_count % 2 == 1) { // and odd number ahead, so it has been closed
                        selection.moveCursorBy(0,ahead.search(/\$/g)+1);  // jump to closing
                    }
                    else { // unclosed
                        var forward;
                        if (ahead.search(/\s/) == -1) {forward = ahead.length; } else { forward = ahead.search(/\s/); }
                        selection.moveCursorBy(0,forward);
                        editor.insert("$"); // close it
                    }
                }
                else { // outside Latex expression so make one
                    editor.insert("$$");
                    selection.moveCursorBy(0,-1);
                }
          },
          readOnly: false // false if this command should not apply in readOnly mode
      });

      editor.commands.addCommand({
          name: 'Next Latex Input',
          bindKey: {win: "Ctrl-J", mac: "Command-J"},
          exec: function(editor) {
                var selection = editor.getSelection();  
                var pos = editor.getCursorPosition();
                var line = editor.session.getLine(pos.row);
                var ahead = line.slice(pos.column);
                var index = ahead.search(/{/);
                if (index != -1) {
                    selection.moveCursorBy(0,index + 1);
                }
          },
            readOnly: false // false if this command should not apply in readOnly mode
      });

      editor.commands.addCommand({
          name: 'Previous Latex Input',
          bindKey: {win: "Ctrl-Shift-J", mac: "Command-Shift-J"},
          exec: function(editor) {
                var selection = editor.getSelection();  
                var pos = editor.getCursorPosition();
                var line = editor.session.getLine(pos.row);
                var behind = line.slice(0,pos.column-1);
                    behind = behind.split("").reverse().join(""); // reverse
                var index = behind.search(/{/);
                if (index != -1) {
                    selection.moveCursorBy(0, -(index) - 1 );
                }
          },
            readOnly: false // false if this command should not apply in readOnly mode
      });
  };

@ghuba
Copy link
Author

ghuba commented Apr 9, 2014

Great improvement. For those unfamiliar with how to use these shortcuts, here is a brief how-to.

1 - How to install these shortcuts.

  • Go to Settings (top left icon in stackedit)
  • Go to tab Extensions, then at the bottom: UserCustom extension.
  • Copy and paste the code inside the "JavaScript code" window.

2 - How to use the shortcuts.

The corrections variable is used as follows. On the left you have the trigger and on the right the text to be inserted. Take the first line:

var corrections = {
        a: "α",

If you enter \a[SPACE] in stackedit you get the character α.

More generally if you have:

        trigger: "insert_this",

then if you type \trigger[SPACE] in stackedit you get insert_this.

This should allow you to easily create your own triggers to save yourself some typing.

@jbaum98
Copy link

jbaum98 commented Apr 10, 2014

I improved my code a little bit. The last thing I'm having trouble with is detecting if I am in a Latex code block accounting for $$ and $. Since the color of the $s changes depending on if they are closed or not, can you give me some insight on how you detect if the block is closed? I'm kind of loathe to parse the whole document looking for $s.

// REPLACEMENTS
var corrections = {
        a: "α",
        b: "β",
        bigcap: "⋂",
        bigcup: "⋃",
        bullet: "∙",
        cap: "∩",
        cdot: "⋅",
        cdots: "⋯",
        checkmark: "✓",
        circ: "∘",
        colon: ":",
        cup: "∪",
        dagger: "†",
        deg: "º",
        dot: "˙",
        d: "δ",
        D: "Δ",
        div: "÷",
        dots: "…",
        g: "γ",
        e: "ϵ",
        empty : "∅",
        eqsim: "≂",
        eta: "η",
        exists: "∃",
        ph: "ϕ",
        Ph: "Φ",
        PH: "Φ",
        forall: "∀",
        G: "Γ",
        ge : "≥",
        gg: "≫",
        gtrsim: "≳",
        h: "ℏ",
        i: "ι",
        iff: "⟺",
        inf : "∞",
        int: "∫_{}^{}",
        imath: "ı",
        implies: "⟹",
        "in": "∈",
        L: "Λ",
        l: "λ",
        "<": "⟨",
        ">": "⟩",
        lceil: "⌈",
        ldots: "…",
        le : "≤",
        leftarrow: "←",
        "<-" : "→",
        Leftarrow: "⇐",
        "<=" : "⇐",
        leftrightarrow: "↔",
        "<>": "↔",
        Leftrightarrow: "⇔",
        lfloor: "⌊",
        ll: "≪",
        m: "μ",
        mathring: "˚",
        mho: "℧",
        mp: "∓",
        n: "ν",
        nabla: "∇",
        ne: "≠",
        "=": "≠",
        nexists: "∄",
        notin: "∉",
        o: "ω",
        O: "Ω",
        otimes: "⊗",
        oplus: "⊕",
        oint: "∮_{}^{}",
        omicron: "ο",
        parallel: "∥",
        partial: "∂",
        perp: "⊥",
        p: "π",
        P: "Π",
        pm: "±",
        prod: "∏",
        propto: "∝",
        psi: "ψ",
        Psi: "Ψ",
        rceil: "⌉",
        Re: "ℜ",
        rfloor: "⌋",
        r : "ρ",
        rightarrow: "→",
        "->" : "→",
        Rightarrow: "⇒",
        "=>": "⇒",
        S: "Σ",
        sim: "∼",
        simeq: "≃",
        sq: "√{}",
        star: "⋆",
        subset: "⊂",
        subseteq: "⊆",
        subsetneq: "⊊",
        sum : "∑_{}^{}",
        supset: "⊃",
        supseteq: "⊇",
        supsetneq: "⊋",
        surd: "√",
        q: "θ",
        Q: "Θ",
        times: "×",
        to: "→",
        triangle: "△",
        txt: "\text{}",
        u: "υ",
        v: "∨",
        w: "∧", // wedge
        x: "ξ", // xi
        X: "Ξ", // Xi
        z: "ζ", // zeta
        f: "\\frac{}{}",
        A: "∀",
        E: "∃",
        bR: "ℝ", // Black-board letters
        bC: "ℂ",
        bH: "ℍ",
        bN: "ℕ",
        bQ: "ℚ",
        bZ: "ℤ",
        "-1": "^{-1}",
        M22: "\\begin{pmatrix}\n1 & 0 \\\\\n0 & 1\n\\end{pmatrix}\n", // a 2x2 matrix
        "==": "≡",
        s: "\\sin({})", //Trig
        c: "\\cos({})",
        t: "\\tan({})",
        cs: "\\csc({})",
        sc: "\\sec({})",
        ct: "\\cot({})",
        si: "\\sin^{-1}({})",
        ci: "\\cos^{-1}({})",
        ti: "\\tan^{-1}({})",
        csi: "\\csc^{-1}({})",
        sci: "\\sec^{-1}({})",
        cti: "\\cot^{-1}({})",
        pf: "\\frac{π}{}"
    };

userCustom.onAceCreated = function (editor) {
    var Range = require("ace/range").Range;
    editor.commands.on("afterExec", function (e) {
        if (e.command.name === "insertstring") {
            if (e.args === " ") {
                var pos = editor.getCursorPosition(),
                    line = editor.session.getLine(pos.row),
                    index = line.lastIndexOf("\\", pos.column) + 1,
                    match = line.substring(index, pos.column - 1);
                if (index && corrections.hasOwnProperty(match)) {
                    editor.session.replace(
                        new Range(pos.row, index - 1, pos.row, pos.column),
                        corrections[match]
                    );
                    if (corrections[match].search(/\{[^\-1]?\}/) !== -1) { // Regex: { <anything except -1> } to jump to input for inverse trig functions
                        editor.moveCursorTo(pos.row, index + corrections[match].search(/\{[^\-1]?\}/));
                    }
                }
            }
        }
    });
// CLOSING LATEX BLOCKS
    editor.commands.addCommand({
        name: 'Insert Latex',
        bindKey: {win: "Ctrl-E", mac: "Command-E"},
        exec: function (editor) {
            var selection = editor.getSelection(),
                pos = editor.getCursorPosition(),
                line = editor.session.getLine(pos.row),
                behind = line.slice(0, pos.column),
                behind_count = behind.match(/\$/g) === null ? 0 : behind.match(/\$/g).length,
                ahead = line.slice(pos.column),
                ahead_count = ahead.match(/\$/g) === null ? 0 : ahead.match(/\$/g).length,
                forward = // in case we need to jump ahead to close Latex expression
                    ahead.search(/\s/) === -1 ? // if no whitespaces ahead
                            ahead.length :      // jump all the way to the end of the line
                            ahead.search(/\s/); // otherwise jump to whitespace
            if (behind_count % 2 === 1) { // odd number of $ behind, inside a Latex expression
                if (ahead_count % 2 === 1) { // and odd number ahead, so it has been closed
                    selection.moveCursorBy(0, ahead.search(/\$/g) + 1);  // jump to closing
                } else { // unclosed Latex expression
                    selection.moveCursorBy(0, forward);
                    editor.insert("$"); // close it
                }
            } else { // outside Latex expression so make one
                editor.insert("$$");
                selection.moveCursorBy(0, -1);
            }
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });
// MOVING  TO INPUTS
    editor.commands.addCommand({
        name: 'Next Latex Input',
        bindKey: {win: "Ctrl-J", mac: "Command-J"},
        exec: function (editor) {
            var selection = editor.getSelection(),
                pos = editor.getCursorPosition(),
                line = editor.session.getLine(pos.row),
                ahead = line.slice(pos.column),
                index = ahead.search(/\{|.?(?=\$)/); // Regex: { OR any character followed by $
            if (index !== -1) {
                selection.moveCursorBy(0, index + 1);
            }
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });

    editor.commands.addCommand({
        name: 'Previous Latex Input',
        bindKey: {win: "Ctrl-Shift-J", mac: "Command-Shift-J"},
        exec: function (editor) {
            var selection = editor.getSelection(),
                pos = editor.getCursorPosition(),
                line = editor.session.getLine(pos.row),
                behind = line.slice(0, pos.column).split("").reverse().join(""), // reverse
                index = Math.min(
                    behind.slice(1).search(/\{/) === -1 // if no { not right in front of cursor
                        ? behind.search(/\$/) + 1 // assure that the $ one is chosen
                        : behind.slice(1).search(/\{/) + 1, // + 1 to make up for slice above
                    behind.search(/\$/) === 0 ? 1 // if right in front of a $ go behind it
                        : behind.search(/\$/) // otherwise go in front of it
                );
            if (index !== -1) {
                selection.moveCursorBy(0, -(index));
            }
        },
        readOnly: false // false if this command should not apply in readOnly mode
    });
};

@victorel-petrovich
Copy link

Looks like all above extensions make use of Ace component. But I understood that now is removed? Will then they still work, or what's the workaround?...

@victorel-petrovich
Copy link

@ghuba , @benweet , @jbaum98

How about an extension that provides a list (of recently used) matching latex commands, when when user starts typing a particular latex command. Ex: $\left...$ (then a popup appears and suggests \leftarror, \leftbracket etc). Then user can navigate with arrow down and select by enter or space.

I think this could not only help beginners, but speed the work of advanced users too.

@vladejs
Copy link

vladejs commented Oct 5, 2015

The shortcuts above aren't working on StackEdit 4.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants