Sublime Forum

Run a command whenever a specific character is written?

#1

Say I want to perform some task on the view whenever a given character (say, ‘£’) is written. I’m aware that different keyboard configurations may use different keystrokes to write this symbol, so kwybindings are not a viable option, since I’d want to do this for any keyboard configuration.

Any adivce on how to achieve this?

0 Likes

#2

Key bindings also work on symbols.

0 Likes

#3

Would you mind to elaborate, please?

0 Likes

#4

You just make a binding on £. Just remember that the binding will cause the character not to be inserted since the key is eaten by the binding, so you’d need to insert the character manually by calling the insert command yourself, for example.

0 Likes

#5

Just as a quick concrete example, here’s a plugin that defines a command and a key binding to invoke it. This assumes you might want to do this for more than one key so it takes the key it’s overriding as a argument to make things easier to follow.

Note that you want to constrain the binding to only take effect where you want it, because otherwise it triggers for every press of this key, be it in a file or even in widgets like the console input or the command palette.

import sublime
import sublime_plugin


class MyCustomCommandCommand(sublime_plugin.TextCommand):
    def run(self, edit, trigger):
        self.view.run_command("insert", {"characters": trigger})
        print("The {char} key was pressed".format(char=trigger))
{
    "keys": ["£"],
    "command": "my_custom_command",
    "args": {"trigger": "£"}
},
0 Likes

#6

Thank you very much! Keybindings affecting places outside of the main buffer is a menacing idea though. I need to study the keybinding docs, it looks like!

0 Likes

#7

You could also use AutoHotkey…

I use it for my F13 through F24 keys ( Logitech mechanical light up keyboard with M1 through M3 which changes G6 through G9 horizontal at top above F1 though F4, and G1 through G5 vertical on left side ) - I use the G6 throuhg F9 keys as F13 14 15 and 16 for M1, M2 is thrugh 20, and m3 is for 20 to 24…

The M3 value I keep it on most of the time runs the following:

F24 = close and restart sublime text
F23 = Open console
F22 = Paste and play sound ( staggered from F3 for easy copy / paste - I wanted a separate button for search vs paste to avoid issues )
F21 = Copy + Sound although I never use this so I’ll probably change it to set the syntax to the active view file extension mapped to current language mapped to syntax file so .lua will map to GMod Lua Lang, which maps to Lua.sublime-syntax in GMod Lua package folder…

F13 throuhg F20 are all assigned to change the syntax languages ( so if I do F21 to the mapping with my plugin then I can get rid of these )

So - how is it done?

Well - Sublime Text doesn’t let me use F13 through F24 keys which is a pain so I map my keys through AutoHotkey… When I want the key to trigger a bind, but still insert it ( for example your you simply enable passthrough on AutoHotkey bind…

so this is what you’d use for it to trigger ( say Ctrl + Alt + L )

;;
;; £ - Triggers Ctrl + Alt + L which can be used in Sublime Text... ~ means passthrough so £ will still written ( remove ~ to prevent £ from being typed ) and * is used so the bind will still trigger if ctrl, alt, shift, etc.. are held which is useful if you need to press any of them in order to trigger £..
;;
;; Note: ! == ALT, ^ == Control, + == SHIFT, # == WIN
;;
;;
~*£:: Send ^!+L

If you want to do more - for instance trigger that keybind using a function key

~*£:: Send ^!+{F1}

If you want to add more to it… this will send ctrl alt shift F1 when £ is typed and it’ll play a sound…

~*£:: 
	Send ^!+{F1}

	;; _default := %A_WinDir% . "\Media\Speech On.wav"
	_default := A_WinDir . "\Media\Speech On.wav"
	SoundPlay, %_default%, false

Now, if you have to press some keys in order to trigger £ then add * so if ctrl, alt, shift, etc… is held down when the value is added - it’ll still trigger…

the * would be added next to the ~ which is the passthrough var…

It is possible to also do most or all of this in sublime text but it may be more complicated… On £ add a keybind which calls a custom command in a python script… Then you need to add regions, and all of this to add the £ char to the output ( there may be another way but the one way I know uses several lines of code to do it ) and run whatever else you want including other sublime text commands…

I use AutoHotkey because it’s fantastic when an application has shortcomings and because it’s incredibly easy to make complex binds which work with the app… The shortcomings with Sublime Text being they don’t allow F13 through F24 binds - or they don’t with this keyboard for some reason…

This is my Sublime Text script

;; AutoHotkey Script Example to show off XCodeMapper - Josh 'Acecool' Moser
;;
;; Note: ! == ALT, ^ == Control, + == SHIFT, # == WIN
;;


;;
;; ------------------------------------------------------------------------------------------------------------------------------------------------------------------
;;


;;
;; F13
;;
*~F13::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.Chime( "", false )
		Send ^!{F1}
		nircmd.TextToSpeech( "J-S-O-N-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F14
;;
*~F14::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.Chime( "", false )
		Send ^!{F2}
		nircmd.TextToSpeech( "XML-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F15
;;
*~F15::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.Chime( "", false )
		Send ^!{F3}
		nircmd.TextToSpeech( "HTML-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F16
;;
*~F16::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.Chime( "", false )
		Send ^!{F4}
		nircmd.TextToSpeech( "PHP-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; ------------------------------------------------------------------------------------------------------------------------------------------------------------------
;;


;;
;; F17
;;
*~F17::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.ChimeOff( "", false )
		Send ^!+{F1}
		nircmd.TextToSpeech( "Garrys-Mod-Lua-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F18
;;
*~F18::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.ChimeOff( "", false )
		Send ^!+{F2}
		nircmd.TextToSpeech( "Python-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F19
;;
*~F19::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.ChimeOff( "", false )
		Send ^!+{F3}
		nircmd.TextToSpeech( "Java-Script-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; F20
;;
*~F20::
	if ( sublime_text.IsActive( ) )
	{
		;; notify.ChimeOff( "", false )
		Send ^!+{F4}
		nircmd.TextToSpeech( "AutoHot-key-Syntax-HighLighting" )
		notify.Chime( "", false )
	}
return


;;
;; ------------------------------------------------------------------------------------------------------------------------------------------------------------------
;;


;;
;; F21 to Ctrl + C - ie COPY
;;
*~F21::
	;; sublime_text:IsActive( )
	if ( sublime_text.IsActive( ) )
	{
		notify.Chime( "", false )
		Send, ^c
	}
return


;;
;; F22 to Ctrl + V - ie PASTE
;;
*~F22::
	if ( sublime_text.IsActive( ) )
	{
		notify.Chime( "", false )
		Send, ^v
	}
return


;;
;; F23 to Alt + C - ie OPEN CONSOLE
;;
*~F23::
	if ( sublime_text.IsActive( ) )
	{
		notify.Chime( "", false )
		Send, !c
	}
return


;;
;; F24 to Restart / Start Sublime Test 3
;;
*~F24::
	;; Close and Launch, or Relaunch the App...
	sublime_text.RestartApp( )
return

And the class for making sure it’s running, etc… and the notify for sounds


;;
;; Sublime Textr Library
;;
class sublime_text
{
	;;
	;; Close Sublime Text...
	;;
	CloseApp( )
	{
		;; If the App is running, try to close it...
		if ( this.Isrunning( ) )
		{
			;; Play a shut-down sound.
			notify.ChimeOff( "", true )

			;; Close the app...
			WinClose

			;; The app is running
			return true
		}

		;; The app was not running
		return false
	}


	;;
	;; Launch Sublime Text...
	;;
	RunApp( )
	{
		;; Re-Open ST3
		Run, "C:\Program Files\Sublime Text 3\sublime_text.exe"

		;; Play an audible alert to let us know that the command is executing...
		notify.Chime( )
	}


	;;
	;; Close and Re-Launch Sublime Text... - Note: For some reason using AutoHotkey close, then WinWait it can lock up so this may not always wait until closed before trying to relaunch - I'll add a sleep later...
	;;
	RestartApp( )
	{
		;; If the app is running, close it...
		if ( this.Isrunning( ) )
		{
			;;
			this.CloseApp( )

			;; Wait until fully-closed before taking the next step...
			WinWaitClose
		}

		;; Start the App...
		this.RunApp( )
	}


	;;
	;; Helper - Determine whether or not Sublime Text is running..
	;;
	IsRunning( )
	{
		;; If the executable is running, return true - otherwise false..
		if WinExist( this.GetExeString( ) )
			return true

		return false
	}


	;;
	;; Helper - Determine whether or not Sublime Text is Running and has focus..
	;;
	IsActive( )
	{
		;; If sublime_text.exe is running, and it is also in-focus - ie it is the active screen / program.. Then return true - otherwise false..
		if this.IsRunning( ) && WinActive( this.GetExeString( ) )
			return true

		return false
	}


	;;
	;; Alias of IsActive
	;;
	InFocus( )
	{
		return this.IsActive( )
	}


	;;
	;; Return the Application Executable String..
	;;
	GetExeString( )
	{
		return "ahk_exe sublime_text.exe"
	}
}
;;
;; Notification Library - Removed all except for basic sound...
;;
class notify
{
	;;
	;;
	;;
	Chime( _sound := "", _wait := true )
	{
		;; _default := %A_WinDir% . "\Media\Speech On.wav"
		_default := A_WinDir . "\Media\Speech On.wav"

		if ( _sound = "" )
			SoundPlay, %_default%, %_wait%
		else
			SoundPlay, %_sound%, %_wait%
	}


	;;
	;;
	;;
	ChimeOff( _sound := "", _wait := true )
	{
		;; _default := %A_WinDir% . "\Media\Speech Off.wav"
		_default := A_WinDir . "\Media\Speech Off.wav"

		if ( _sound = "" )
			SoundPlay, %_default%, %_wait%
		else
			SoundPlay, %_sound%, %_wait%
	}


	;;
	;; notify.Beep( 10000 , 2500 )
	;;
	Beep( _duration := 150, _pitch := 500 )
	{
		;; SoundBeep - Default is 500, 500?
		SoundBeep, _pitch, _duration
	}


	;;
	;; notify.Beep( 10000 , 2500 )
	;;
	BeepLow( _duration := 100, _pitch := 250 )
	{
		;; SoundBeep - Default is 500, 500?
		this.Beep( _duration, _pitch )
	}


	;;
	;;
	;;
	CodeShort( )
	{
		this.Beep( 100, 200 )
	}


	;;
	;;
	;;
	CodeLong( )
	{
		this.Beep( 500, 200 )
	}


	;;
	;;
	;;
	CodeWordPause( )
	{
		Sleep, 750
	}


	;;
	;;
	;;
	CodeLetterPause( )
	{
		Sleep, 250
	}
}

I have no idea why the tabs are screwy… they aren’t in the file… Odd…

Anyway - long post, but I highly recommend AutoHotkey… You can even compile your scripts into Stand-Alone Executables…

The language itself can be somwhat ugly at times - I’d honestly prefer a C#, C++, Java, JavaScript, Lua, Python or anything other than this - but it is incredibly useful…

I do have a framework available to download ( auto executes your scripts - drag and drop into folders and it’ll compile all of them into a run these script file )… If you put into addons_run then they’ll run in their own process… addons_inc and they’ll run with the framework… addons_exe and you can use executables ( I still need to add a feature to remember your selections - basically the first time it compiles with scripts in it’ll ask whether or not each file should be excluded or included for security reasons )…

drop files in hotkeys for include to run with framework - and there are on init callbacks available for each script for setup, etc… super useful.

Here’s one script I run which gives me quick and easy access to a wide variety of math chars, French, Spanish, German, etc… keys…

;;
;; Charset Cycler - Josh 'Acecool' Moser
;;


;;
;; TODO: Alter this to look and operate similarly to the audio-device-cycler and load / unload file-definitions on the fly.. should be possible..
;;


;;
;; Index - WIN + F8 to toggle between Spanish / German overlapping characters
;; German Overlapping Characters ( WIN + F8 To Switch Between the German and Spanish Chars )
;;		Alt + 			a o u		==		ä ö ü
;;		ALT + SHIFT + 	A O U		==		Ä Ö Ü
;;
;; Other German Characters:
;;		ALT + 			S			==		ß
;;
;; Spanish Overlapping Characters ( WIN + F8 To Switch Between the German and Spanish Chars )
;;		Alt + 			a o u		==		á ó ú
;;
;; Other Spanish Characters:
;;		ALT + 			e i n ? !	==		é í ñ ¿ ¡
;;
;; Special Characters
;;		ALT + 			` c l r t	==		° © ® ™
;;		ALT + CTRL +	c e l		==		¢ € £
;;
;; Math Characters
;;		ALT + 			d =	/ -	0	==	Δ ≡ ÷ ± ø
;;		ALT + SHIFT		D > < * +	==	δ ≥ ≤ √ ±
;;


;;
;; Set up Language Keys...
;;
OnInit_hotkey_cycle_charsets_of_german_spanish_math_currency_special( )
{
	;; Set Default ( German )
	config.SetDefault( "HotKeys", "LanguageKeys", 0 )

	;; Load the active key-set into memory...
	global German_Spanish_Keys_Toggle:=config.Get( "HotKeys", "LanguageKeys" )
}


;;
;; WIN + F8 - Toggle between Spanish and German chars - SPANISH DEFAULT!
;;
LWin & F8::
	;; Define our var to be set to itself or 0 if it wasn't set ( or when 0 is seen as false )...
	German_Spanish_Keys_Toggle := ( German_Spanish_Keys_Toggle is digit ) ? German_Spanish_Keys_Toggle : 0
	_notification = "Unknown"

	;; Enable Spanish
	if German_Spanish_Keys_Toggle = 0
	{
		; TrayTip, Spanish Keys Enabled, áóóúú
		OnMessage( 0x404, "" )
		OnMessage( 0x404, Func( "Toast_Callback_Help" ) )
		; notify.TrayTip( "Spanish Keys Enabled, ALT + a / o / u [ + SHifT ]: á / ó / ú or á / ó / ú" )
		TrayTip, "Spanish Keys Enabled, ALT + a / o / u [ + SHifT ]: á / ó / ú or á / ó / ú"
		_notification = "Español Activado! -- Click for more hot keys and help!"
	}

	;; Enable German
	if German_Spanish_Keys_Toggle = 1
	{
		OnMessage( 0x404, "" )
		OnMessage( 0x404, Func( "Toast_Callback_Help" ) )
		; notify.TrayTip( "German Keys Enabled, ALT + a / o / u [ + SHifT ]: ä / ö / ü or Ä / Ö / Ü" )
		TrayTip, "German Keys Enabled, ALT + a / o / u [ + SHifT ]: ä / ö / ü or Ä / Ö / Ü"
		_notification = "Deutsch Aktiviert! -- Click for more hot keys and help!"
	}

	;; Update the cycler with the new choice..
	German_Spanish_Keys_Toggle := mod( German_Spanish_Keys_Toggle + 1, 2 )

	;; Save the most recent choice...
	config.Set( "HotKeys", "LanguageKeys", German_Spanish_Keys_Toggle )

	;; Speech
	nircmd.TextToSpeech( _notification )
return


;;
;; Toggle-able hotkeys for German / Spanish - These 3 keys are SHARED so we have to swap them...
;;
!+a::
	if !German_Spanish_Keys_Toggle
		sendInput	{Ä}
	else
		sendInput	{á}
return

!a::
	if !German_Spanish_Keys_Toggle
		sendInput	{ä}
	else
		sendInput	{á}
return

; o
!+o::
	if !German_Spanish_Keys_Toggle
		sendInput	{Ö}
	else
		sendInput	{ó}
return

!o::
	if !German_Spanish_Keys_Toggle
		sendInput	{ö}
	else
		sendInput	{ó}
return

; u
!+u::
	if !German_Spanish_Keys_Toggle
		sendInput	{Ü}
	else
		sendInput	{ú}
return

!u::
	if !German_Spanish_Keys_Toggle
		sendInput	{ü}
	else
		sendInput	{ú}
return


;;
;; French - French Quotes
;;
; !+>::		sendInput	{»}
; !+<::		sendInput	{«}


;;
;; German Umlauts and Special Characters
;; ▓▒░│┤╡╢╖╕║╣╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▀▐
;;

;; a
; !+a::		SendInput	{Ä}
; !a::		SendInput	{ä}

;; o
; !+o::		SendInput	{Ö}
; !o::		SendInput	{ö}

;; u
; !+u::		SendInput	{Ü}
; !u::		SendInput	{ü}

;; ss
!s::		SendInput	{ß}




;;
;; Spanish Special Chars a = á, e = é, i = í, o = ó, u = ú, n = ñ, ! = ¡, ? = ¿
;;
;; These are unique so we don't need to swap them... I may set it up differently though to avoid confusion and have extra cycles for math etc.. chars..
;;

;; e
!+e::		sendInput	{é}
!e::		sendInput	{é}

;; i
!+i::		sendInput	{í}
!i::		sendInput	{í}

;; n
!+n::		sendInput	{ñ}
!n::		sendInput	{ñ}

;; Spanish ?
!?::		sendInput	{¿}

;; Spanish !
!!::		sendInput	{¡}





;;
;; Special Characters Charset
;;

;; Temperature / Degrees
!`::		sendInput	{°}

;; Copyright
!c::		sendInput	{©}

;; Penny / Cents
^!c::		sendInput	{¢}

;; Europe Euro
^!e::		sendInput	{€}

;; English Pounds
^!l::		sendInput	{£}

;; All Rights Reserved
!r::		sendInput	{®}

;; Trademarked
!t::		sendInput	{™}





;;
;; Math Charset
;;

;; Delta
!+d::		sendInput	{δ}
!d::		sendInput	{Δ}
; !+d::		sendInput	{Δ}
; !d::		sendInput	{•}

;; Greater / Less-Than or Equal-To
!+>::		sendInput	{≥}
!+<::		sendInput	{≤}
; !+>::		sendInput	{»}
; !+<::		sendInput	{«}

;; Identical To
!=::		sendInput	{≡}

;; Division / Sqrt
!/::		sendInput	{÷}
!+*::		sendInput	{√}
!*::		sendInput	{•}

;; Plus or Minus
!++::		sendInput	{±}
!-::		sendInput	{±}

;; Null
!0::		sendInput	{ø}

;; Electricity
^!o::		sendInput	{Ω}
^!u::		sendInput	{µ}{F}

;; Measurement for Electronics ( such as 1 Ounce of Copper flattened over 1 Square foot - 35µm thick )
^!m::		sendInput	{µ}{m}



;;
;; Other Charset - ▓▒░│┤╡╢╖╕║╣╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▀▐
;;

I am going to write a script to use arrow keys to be able to output those lines super easily… or numkeys with plus to increase the thickness or number of lines in the chars, and based on direction traveling it’ll use the next progressively in so you start by creating 1 char then you add to it and it’ll be sure to use a char which connects to it… and I’ll need to allow trace-back for tree-like views…

Projects in mind, I say… but hopefully this gives you something to work with.

1 Like