Sublime Forum

So, on the dead keys topic

#1

So, i’m trying to use sublime as a VSCode replacement.

So far so good, the main issue i’m having right now is with Dead Keys. I’m not able to use things like to quote a selected word because after I type a " (for example) it expect a new key. If I press space, the word gets replaced.

For comparisson, in VSCode, i get the selected word quoted as expected.

I did search the forums and found these two (old) posts without an answer:


A few additional internet (and AI) searches recommend to disable dead keys, which is not an applicable solution in my case as i need them to type character with accents.

Considering the current version of ST, is there a proper solution for this ?

0 Likes

#2

On keyboards with backticks being dead keys auto-pairing and auto-quoting can be achieved with key bindings which insert snippets. Maybe this strategy works for other quotation types as well.

ST’s Markdown syntax ships with such key bindings. see: https://github.com/sublimehq/Packages/blob/36763f6f68517049f2e78125dbc0043bc224879d/Markdown/Default.sublime-keymap#L26-L83

With those bindings I can select a piece of text and hit backtick followed by space to wrap it.

A solution might be to remove "selector" key and replace "`" by "\"" to achieve the same for double quotes on your end.

Note: Maybe other binding than \" is needed as ST doesn’t correctly map foreign keyboard layouts.

    // Auto-pair double quotes
    {
        "keys": ["\""],
        "command": "insert_snippet",
        "args": {"contents": "\"$0\""},
        "context": [
            { "key": "setting.auto_match_enabled"},
            { "key": "selection_empty", "match_all": true },
            { "key": "preceding_text", "operator": "not_regex_contains", "operand": "[\\w\"]$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|\\.|,|$)", "match_all": true }
        ]
    },
    {
        "keys": ["\""],
        "command": "insert_snippet",
        "args": {"contents": "\"${0:$SELECTION}\""},
        "context": [
            { "key": "setting.auto_match_enabled"},
            { "key": "selection_empty", "operand": false, "match_all": true },
        ]
    },
    {
        "keys": ["\""],
        "command": "move",
        "args": {"by": "characters", "forward": true},
        "context": [
            { "key": "setting.auto_match_enabled" },
            { "key": "selection_empty", "operand": true, "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }
        ]
    },
    {
        "keys": ["\""],
        "command": "move",
        "args": {"by": "characters", "forward": true},
        "context": [
            { "key": "setting.auto_match_enabled" },
            { "key": "selection_empty", "operand": true, "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }
        ]
    },
    {
        "keys": ["backspace"],
        "command": "run_macro_file",
        "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"},
        "context": [
            { "key": "setting.auto_match_enabled" },
            { "key": "selection_empty", "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true }
        ]
    },
0 Likes

#3

Not sure if I followed the suggestion the right way.

As far as I saw here, the binding for double quote already uses the insert_snippet solution. Below the default ST configuration i have locally:

// Auto-pair quotes
{ "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"$0\""}, "context":
	[
		{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
		{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
		{ "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true },
		{ "key": "preceding_text", "operator": "not_regex_contains", "operand": "[\"a-zA-Z0-9_]$", "match_all": true },
		{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }
	]
},
{ "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"${0:$SELECTION}\""}, "context":
	[
		{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
		{ "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }
	]
},
{ "keys": ["\""], "command": "move", "args": {"by": "characters", "forward": true}, "context":
	[
		{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
		{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
		{ "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true },
		{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
		{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true },
	]
},
{ "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context":
	[
		{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
		{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
		{ "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true },
		{ "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true },
		{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
		{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true },
	]
},

I did notice you changed a few things, so i copied you’re entire example and pasted as a custom keybinding, but was not able to get a selected text surrounded with quotes.

I did try to surround a selected text with backticks in a mardown file, but the result is the same, i mean, the word gets deleted.

0 Likes

#4

Than my proposal is probably not viable. I hoped it working as selecting a word, typing backtick and space, deletes it on my box without those markdown specific bindings, while correctly being wrapped in backticks otherwise.

As ST doesn’t apply any language specific key mappings, chances are high configured \" and ` bindings just apply to unexpected keys on your box. Maybe you’ll need to chose a totally different (unexpected) binding to make it work.

As on my end I need to type ctrl+# to trigger the binding { "keys": ["ctrl+`"], "command": "show_panel", "args": {"panel": "console", "toggle": true} }, on Windows. I haven’t found the proper key binding to use on Linux so far, even though hardware and config is the same.

This is the weird world of ST.

1 Like

#5

For future searchers that will probably get here, i’m on a macOS Tahoe 26.5.1 using an U.S. International layout with dead keys (quotes - single,double - and backticks).

I did try to create a different keybinding using ctrl+\" and ctrl+shift+\" but it only works for single quotes.

0 Likes

#6

I went a bit further, as this is a deal breaker for me to adopt Sublime.

I did disable smart quotes and all possible replacements mac could be doing behind the scenes, but still getting selected words being replaced instead of surrounded with the quotes/double quotes/backticks.

One thing got my attention: Emacs and VSCode works just fine. Testing the same behavior in BBEdit, native TextEdit with the application smart quote option configured (or in case of BBEdit Surround with…) also delete de selected word instead of surround it.

Not sure what kind of native component/configuration is getting in the way but looks like Sublime uses the same while Emacs and VSCode abstract it.

0 Likes

#7

When I use English - United States - International keyboard, I do not face the same issue you are having. Steps that work for me:

  1. Select a word
  2. Press "
  3. Press space

This surrounds the word with double quotes. The same applies to single quotes ('). Now, backtick (`) requires syntax specific languages to work, such as Markdown. When I use backtick in plain text, as soon as I press space, the word gets replaced by the space.

Have you tried to use the steps above in ST’s safe mode? It is possible that one of your custom key bindings is doing something else when you press space. Another possibility is that the Vintage package is not ignored. Vintage uses simple and double quotes for other things. Again, safe mode would address both cases.

I am on Windows 11, so this may be a macOS issue.

0 Likes

#8

This is less a dead keys, but a keyboard layout issue, which likely causes configured key bindings to not work as expected.

ST ignores keyboard layouts, using some sort of hardware key codes, assuming keys to be physically located at a certain position.

Depending on actual physical keyboard layouts those assumptions may not be correct, leading to unexpected behavior.

Example:

ST expects ctrl+backtick being hit to open console.

{ "keys": ["ctrl+`"], "command": "show_panel", "args": {"panel": "console", "toggle": true} },

I actually need to hit ctrl+ö to achieve that on my keyboard, and that’s not even the physical key, which would need to be pressed on an english keyboard! It would be : key.

So any of us using non-english keyboards have more or less hard times and weird edge cases to struggle with.

1 Like

#9

I tried everything. Remove Sublime and all its configuration, install it again and test. Safe mode and so on.

Also, tried layouts without dead keys and it worked.

Yeap, potentially something related with MacOS.

0 Likes

#10

After calling sublime.log_input(True) in console, it prints key code and character when pressing a key.

When pressing a dead key followed by space only a single input event should be registered.

You should receive the following single event, when entering double quote followed by space:

chr evt: " (0x22)
0 Likes

#11

For my layout:

When i press any of the quote it happens exaclty how you said, only one event is registered:

  • single quote: chr evt: ' (0x27)
  • double quote: chr evt: " (0x22)
  • backticks: chr evt: (0x60)`

But it still replaces the selected word.

I did uninstall and cleaned up all caches and application support files before install ST again.

0 Likes

#12

Those outputs look reasonable to me. No indicator about space key being received separately.

With both sublime.log_input(True) and sublime.log_commands(True) executed, I get the following outputs:

Non-dead key (hitting shift+2 to type a double quote):

chr evt: " (0x22)
command: insert_snippet {"contents": "\"$0\""}

dead key (hitting shift+´, followed by space to type a backtick `):

chr evt: ` (0x60)
command: insert_snippet {"contents": "`$0`"}

Note: Auto-pairing backticks is only enabled/configured in Markdown.

Hence it’s not yet clear, why it wouldn’t work.

Are the same commands executed on your end?

0 Likes

#13

This is the entire chain of events:

key evt: ctrl+`
command: show_panel {"panel": "console", "toggle": true}
command: drag_select {"event": {"button": 1, "x": 202.98046875, "y": 77.73828125}}
chr evt: t (0x74)
chr evt: e (0x65)
chr evt: s (0x73)
chr evt: t (0x74)
key evt: shift+alt+left
key evt: shift+alt+left
command: move {"by": "words", "forward": false, "extend":true}
chr evt: ' (0x27)
command: insert_snippet {"contents": "'$0'"}

This command: drag_select {"event": {"button": 1, "x": 202.98046875, "y": 77.73828125}} is when i click back in the main editor.

Not sure why it trigger the key evt: shift+alt+left twice.

0 Likes

#14

Single quotes seem to add auto-pair as expected.

How does it look for double quotes?

0 Likes

#15

Similar results

key evt: ctrl+`
command: show_panel {"panel": "console", "toggle": true}
command: drag_select {"event": {"button": 1, "x": 300.2265625, "y": 109.12109375}}
chr evt: t (0x74)
chr evt: e (0x65)
chr evt: s (0x73)
chr evt: t (0x74)
key evt: shift+alt+left
key evt: shift+alt+left
command: move {"by": "words", "forward": false, "extend":true}
chr evt: " (0x22)
command: insert_snippet {"contents": "\"$0\""}

In both cases the test word gets deleted.

It feels like the $0 does not contain the selected word.

0 Likes

#16

Well, for some reason your ST is triggering the wrong snippet. Is "auto_match_enabled": true, in your settings? What happens if you add the following lines to your custom key bindings?

{ "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"${0:$SELECTION}\""}, "context":
	[
		// { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
		{ "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }
	]
},
0 Likes

#17

I have uninstalled sublime and wiped all cache and configurations i have found.

Belo my full quote/single-quote settings:

	// Auto-pair quotes
	{ "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"$0\""}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true },
			{ "key": "preceding_text", "operator": "not_regex_contains", "operand": "[\"a-zA-Z0-9_]$", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true }
		]
	},
	{ "keys": ["\""], "command": "insert_snippet", "args": {"contents": "\"${0:$SELECTION}\""}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }
		]
	},
	{ "keys": ["\""], "command": "move", "args": {"by": "characters", "forward": true}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true },
			{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true },
		]
	},
	{ "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "preceding_text", "operator": "regex_contains", "operand": "\"$", "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^\"", "match_all": true },
			{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.double - punctuation.definition.string.end", "match_all": true },
		]
	},

	// Auto-pair single quotes
	{ "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'$0'"}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true },
			{ "key": "preceding_text", "operator": "not_regex_contains", "operand": "['a-zA-Z0-9_]$", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true }
		]
	},
	{ "keys": ["'"], "command": "insert_snippet", "args": {"contents": "'${0:$SELECTION}'"}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }
		]
	},
	{ "keys": ["'"], "command": "move", "args": {"by": "characters", "forward": true}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true },
			{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true },
		]
	},
	{ "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context":
		[
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{ "key": "preceding_text", "operator": "regex_contains", "operand": "'$", "match_all": true },
			{ "key": "following_text", "operator": "regex_contains", "operand": "^'", "match_all": true },
			{ "key": "selector", "operator": "not_equal", "operand": "punctuation.definition.string.begin", "match_all": true },
			{ "key": "eol_selector", "operator": "not_equal", "operand": "string.quoted.single - punctuation.definition.string.end", "match_all": true },
		]
	},
0 Likes

#18

Some news:

A few months ago i did have a similar issue with Teams where it was not able to recognize double backticks to create an inline markdown syntax.

At the time i learned that using Ctrl+Option+<backtick> inserted a “non dead key” version of the backtick and Teams was able to recognize it. A few patches later, Teams fixed the issue and i’m able to use my backtick without any workaround.

I did the same test for sublime and what a surprise. Ctrl+Option+' worked as can be seen below:

key evt: ctrl+`
command: show_panel {"panel": "console", "toggle": true}
command: drag_select {"event": {"button": 1, "x": 245.953125, "y": 151.140625}}
chr evt: t (0x74)
chr evt: e (0x65)
chr evt: s (0x73)
chr evt: t (0x74)
key evt: shift+alt+left
key evt: shift+alt+left
command: move {"by": "words", "forward": false, "extend":true}
key evt: ctrl+alt+'
key evt: ctrl+alt+'
chr evt: ' (0x27)
command: insert_snippet {"contents": "'${0:$SELECTION}'"}

For double quotes it didn’t work, maybe because I need to press shift to get a double quote so it ends up being Ctrl+Option+Shift+"

It also didn’t work for backticks because it interpret as a Ctrl+<backtick> which ends up opening the console.

0 Likes