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

Automatic insertion of Unicode characters from typing shortcuts (a.k.a. autocorrect) #13856

Open
rkj90266 opened this issue Jul 3, 2023 · 12 comments

Comments

@rkj90266
Copy link

rkj90266 commented Jul 3, 2023

Description of the Issue

This is a feature request. In Microsoft apps there is an "autocorrect" feature that automatically replaces strings with replacement strings based on the contents of a replacement table. Microsoft uses it for spelling correction as you type but I use it for non-ASCII character insertion, for example all the Greek letters can be inserted by typing \alpha .. \omega, and upper case with \Alpha .. \Omega. Also available are the \degrees symbol, math operators \pm, \ge, \le. These shortcuts are generally the same as those available when typesetting math in TeX or LaTeX. I programmed all these shortcuts myself and they are very handy and easy to remember.

So basically I'm wishing that a regular feature or plugin was available to do this in npp.

Steps to Reproduce the Issue

N/A

Expected Behavior

When you type "\beta", the string "\beta" is automatically replaced by the Greek letter beta (Unicode character 03B2). Strings to be replaced to be defined in a suitable user-editable table.

Actual Behavior

I went to MS Word to get the character and pasted it into npp.

Debug Information

N/A

N/A

@rdipardo
Copy link
Contributor

rdipardo commented Jul 3, 2023

Cf. #12043

The HTML Tag plugin may someday have the capability of decoding Unicode escapes as you type. In the meantime, you can use PythonScript.

@alankilborn
Copy link
Contributor

alankilborn commented Jul 12, 2023

The PythonScript that @rdipardo linked to doesn't really do what @rkj90266 wants. A better script for the desired situation is one that installs a CHARADDED callback. The callback logic would check the characters entered previously and look for the \alpha, \omega, etc. sequences, and then replace them with the desired character.

Such a script might be this one, suggested name: DynamicReplaceAsType.py:

# -*- coding: utf-8 -*-
from __future__ import print_function

# references:
#  https://github.com/notepad-plus-plus/notepad-plus-plus/issues/13856

from Npp import *

class DRAT(object):

    def __init__(self):
        self.replacement_dict = {
            r'\Alpha' : 'Α',
            r'\alpha' : 'α',
            r'\Beta' : 'Β',
            r'\beta' : 'β',
        }
        editor.callback(self.charadded_callback, [SCINTILLANOTIFICATION.CHARADDED])

    def charadded_callback(self, args):
        for shorthand_tag in self.replacement_dict:
            if chr(args['ch']) == shorthand_tag[-1]:
                curr_pos = editor.getCurrentPos()
                len_shorthand_tag = len(shorthand_tag)
                if editor.getTextRange(curr_pos - len_shorthand_tag, curr_pos) == shorthand_tag:
                    curr_pos -= len_shorthand_tag
                    editor.deleteRange(curr_pos, len_shorthand_tag)
                    editor.insertText(curr_pos, self.replacement_dict[shorthand_tag])
                    editor.setEmptySelection(curr_pos + len(self.replacement_dict[shorthand_tag]))
                    editor.chooseCaretX()

if __name__ == '__main__':
    try:
        drat
    except NameError:
        drat = DRAT()

@rkj90266
Copy link
Author

This is great, thanks! I think @rkreilly would likely find this a better solution than the HTML JS encoding/decoding that @rdipardo suggested for his solution, although that approach is also certainly useful. This is more convenient than memorizing the Unicode character codes.

@rkj90266
Copy link
Author

May I also ask ... is there an easy addition to the script that would turn the callback on and off? So I could enable it or disable it as needed? I can presently turn it off by restarting np++, kinda inconvenient.

@alankilborn
Copy link
Contributor

This is great, thanks!

I'm glad you like it.

is there an easy addition to the script that would turn the callback on and off? So I could enable it or disable it as needed?

Why would you want to turn it off? Are there circumstances where you would type \Alpha and NOT want the substitution to be made? If so, I presume these circumstances would be rare and you could do \Alph a and then arrow left and press Backspace to remove the embedded space.

But regardless, what follows is a slightly modified version of the script that, every time it is run, toggles the state of whether substitutions are made ("on") or not ("off"). PythonScript has some history with callbacks not always being uninstalled correctly, so what is done here is that the callback is left installed, but its logic isn't executed if the current state is "off".

# -*- coding: utf-8 -*-
from __future__ import print_function

# references:
#  https://github.com/notepad-plus-plus/notepad-plus-plus/issues/13856

from Npp import *

class DRAT(object):

    def __init__(self):
        self.active = True
        self.replacement_dict = {
            r'\Alpha' : 'Α',
            r'\alpha' : 'α',
            r'\Beta' : 'Β',
            r'\beta' : 'β',
        }
        editor.callback(self.charadded_callback, [SCINTILLANOTIFICATION.CHARADDED])

    def charadded_callback(self, args):
        if not self.active: return
        for shorthand_tag in self.replacement_dict:
            if chr(args['ch']) == shorthand_tag[-1]:
                curr_pos = editor.getCurrentPos()
                len_shorthand_tag = len(shorthand_tag)
                if editor.getTextRange(curr_pos - len_shorthand_tag, curr_pos) == shorthand_tag:
                    editor.deleteRange(curr_pos - len_shorthand_tag, len_shorthand_tag)
                    curr_pos -= len_shorthand_tag
                    old_len = editor.getLength()
                    editor.insertText(curr_pos, self.replacement_dict[shorthand_tag])
                    editor.setEmptySelection(curr_pos + editor.getLength() - old_len)
                    editor.chooseCaretX()

if __name__ == '__main__':
    try:
        drat
    except NameError:
        drat = DRAT()
    else:
        drat.active = not drat.active

# to activate every time N++ starts, put the following lines (uncommented) into user startup.py:
#import DynamicReplaceAsType
#drat = DynamicReplaceAsType.DRAT()

@rkreilly
Copy link

Yup, this sure looks a little smoother than the solutions we had in my thread, and definitely better than switching text windows to find the symbol for copy-paste.

When building the script, are you just copy-pasting the characters into it, or would you need to use the unicode or other strings ?

@alankilborn
Copy link
Contributor

When building the script, are you just copy-pasting the characters into it, or would you need to use the unicode or other strings ?

When writing the demo script, I just went to Windows' charmap program and changed to the Greek section and copied out a couple of characters. I think that's what you're asking; if not, I don't know what you're asking.

@rkj90266
Copy link
Author

@rkreilly I went to "Insert Symbol" in MS Word. I also have an "autocorrect.doc" document that contains all the definitions and a VBA macro to install them for MS Office so I copied those to put into the Python dictionary. Here's my list in case it's of any use to you:
self.replacement_dict = {
r'---' : '—',
r'-' : '–',
r'\minus' : '–',
r'\degrees' : '°',
r'\infinity' : '∞',
r'\times' : '×',
r'\dot' : '∙',
r'\alpha' : 'α',
r'\beta' : 'β',
r'\gamma' : 'γ',
r'\delta' : 'δ',
r'\epsilon' : 'ε',
r'\zeta' : 'ζ',
r'\eta' : 'η',
r'\theta' : 'θ',
r'\iota' : 'ι',
r'\kappa' : 'κ',
r'\lambda' : 'λ',
r'\mu' : 'μ',
r'\nu' : 'ν',
r'\xi' : 'ξ',
r'\pi' : 'π',
r'\rho' : 'ρ',
r'\sigma' : 'σ',
r'\tau' : 'τ',
r'\upsilon' : 'υ',
r'\phi' : 'φ',
r'\chi' : 'χ',
r'\psi' : 'ψ',
r'\omega' : 'ω',
r'\Gamma' : 'Γ',
r'\Delta' : 'Δ',
r'\Theta' : 'Θ',
r'\Lambda' : 'Λ',
r'\Xi' : 'Ξ',
r'\Pi' : 'Π',
r'\Sigma' : 'Σ',
r'\Phi' : 'Φ',
r'\Psi' : 'Ψ',
r'\Omega' : 'Ω',
r'\partial' : '∂',
r'\sqrt' : '√',
r'\integral' : '∫',
r'\ne' : '≠',
r'\bullet' : '●',
r'\pm' : '±',
r'\ge' : '≥',
r'\le' : '≤',
r'\angstroms' : 'Å',
r'\euros' : '€',
r'\pounds' : '£',
r'\yen' : '¥',
r'~n' : 'ñ',
r"'e" : 'é',
r':o' : 'ö',
r'\cents' : '¢',
r'\copyright' : '©',
r'\square' : '□',
r'\ohms' : 'Ω',
r'\flat' : '♭',
r'\sharp' : '♯',
r'\spade' : '♠',
r'\club' : '♣',
r'\heart' : '♥',
r'\diamond' : '♦',
r'\registered' : '®',
r'\section' : '§',
r'\paragraph' : '¶',
r'\cross' : '†',
r'\trademark' : '™',
r'\tm' : '™',
r'\leftarrow' : '←',
r'\uparrow' : '↑',
r'\rightarrow' : '→',
r'\downarrow' : '↓',
r'\leftrightarrow' : '↔',
r'\updownarrow' : '↕',
r'\sun' : '☼',
r'\smiley' : '☺',
r'\female' : '♀',
r'\male' : '♂',
r'^^2' : '²',
r'^^3' : '³',
r'_0' : '₀',
r'_1' : '₁',
r'_2' : '₂',
r'_3' : '₃',
r'_4' : '₄',
r'_5' : '₅',
r'_6' : '₆',
r'_7' : '₇',
r'_8' : '₈',
r'_9' : '₉',
r'\approx' : '≈',
}

@rkj90266
Copy link
Author

This is great, thanks!

I'm glad you like it.

is there an easy addition to the script that would turn the callback on and off? So I could enable it or disable it as needed?

Why would you want to turn it off? Are there circumstances where you would type \Alpha and NOT want the substitution to be made? If so, I presume these circumstances would be rare and you could do \Alph a and then arrow left and press Backspace to remove the embedded space.

But regardless, what follows is a slightly modified version of the script that, every time it is run, toggles the state of whether substitutions are made ("on") or not ("off"). PythonScript has some history with callbacks not always being uninstalled correctly, so what is done here is that the callback is left installed, but its logic isn't executed if the current state is "off".

# -*- coding: utf-8 -*-
from __future__ import print_function

# references:
#  https://github.com/notepad-plus-plus/notepad-plus-plus/issues/13856

from Npp import *

class DRAT(object):

    def __init__(self):
        self.active = True
        self.replacement_dict = {
            r'\Alpha' : 'Α',
            r'\alpha' : 'α',
            r'\Beta' : 'Β',
            r'\beta' : 'β',
        }
        editor.callback(self.charadded_callback, [SCINTILLANOTIFICATION.CHARADDED])

    def charadded_callback(self, args):
        if not self.active: return
        for shorthand_tag in self.replacement_dict:
            if chr(args['ch']) == shorthand_tag[-1]:
                curr_pos = editor.getCurrentPos()
                len_shorthand_tag = len(shorthand_tag)
                if editor.getTextRange(curr_pos - len_shorthand_tag, curr_pos) == shorthand_tag:
                    editor.deleteRange(curr_pos - len_shorthand_tag, len_shorthand_tag)
                    curr_pos -= len_shorthand_tag
                    old_len = editor.getLength()
                    editor.insertText(curr_pos, self.replacement_dict[shorthand_tag])
                    editor.setEmptySelection(curr_pos + editor.getLength() - old_len)
                    editor.chooseCaretX()

if __name__ == '__main__':
    try:
        drat
    except NameError:
        drat = DRAT()
    else:
        drat.active = not drat.active

# to activate every time N++ starts, put the following lines (uncommented) into user startup.py:
#import DynamicReplaceAsType
#drat = DynamicReplaceAsType.DRAT()

I'm a newbie with PythonScript but I tried this as written and it's not working as expected. I think I need to name the script "DynamicReplaceAsType.py", which I did. When I open npp it's not activated. When I run the script the first time it's still not activated. When I run it a second time it is then activated, and can be toggled by subsequent runs.

@alankilborn
Copy link
Contributor

I think I need to name the script "DynamicReplaceAsType.py", which I did.

It can be named whatever you like, but what you've chosen is a good name. :-)

When I open npp it's not activated.

Correct. To have it autoactivated, you'd have to follow the instructions after to activate every time N++ starts in the script comments.

When I run the script the first time it's still not activated. When I run it a second time it is then activated, and can be toggled by subsequent runs.

Hmm, not my experience; just tried again on a fresh setup. Not sure what is going wrong for you.

I'm a newbie with PythonScript

Perhaps something in the "notes for newbies" will help? See https://community.notepad-plus-plus.org/topic/23039/faq-desk-how-to-install-and-run-a-script-in-pythonscript

@rkj90266
Copy link
Author

Thanks for the "newbie: link. I found there the dropdown for Python initialization and changed it from "LAZY" to "ATSTARTUP" and now it's activated when I start npp. Thanks again for sharing this.

@alankilborn
Copy link
Contributor

alankilborn commented Jul 16, 2023

changed it from "LAZY" to "ATSTARTUP"

Ah, yes, that being set to LAZY would jibe with your earlier experience.

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

No branches or pull requests

4 participants