Sublime Forum

How to set "Word boundaries"?

#1

I’m sorry if this was already asked, but i don’t even know what words to use in the title

What i’d need is to alter the behaviour of the “CTRL+arrow” (or SHIFT+CTRL+arrow), which is movement or selection by “whole words”.
My use case is with C and C++ files:
I’d like that the selections stops at each parenthesis, instead of going further to the end of the word next to it.

In general, i would like to add characters (like “(” , “)” …) , or strings (like “//”) so that ST stops at them.
I don’t even know if it is a global setting or a specific syntax one, and as i said, i can’t find even the right term to look for it.

Thanks

0 Likes

Deleting a Word
Deleting a Word
#2

Globally used word boundaries are configured via Preferences.sublime-settings, but can be overridden by syntax specific settings (e.g. C++.sublime-settings).

The following code snippet contains global default word and subword boundaries.

	// Characters that are considered to separate words
	"word_separators": "./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}`~?",

	// Characters that are considered to separate sub-words
	"sub_word_separators": "_",

Note: For subword navigation to properly work (at least on Windows), various customized key bindings are required.

0 Likes

#3

Thanks.
So, it should ALREADY stops after a “(” ?

Would you like to expand?

0 Likes

#4

So, it should ALREADY stops after a “(” ?

Yes, ctrl+right and friends stop on punctuation characters such as ( or ) by default.

Would you like to expand?

Default key bindings such as

  • ctrl+right or <kbd>ctrl+shift+right</kbd> use characters specified in word_separators to look for word boundaries
  • alt+right or <kbd>alt+shift+right</kbd> use characters specified in sub_word_separators to look for word boundaries.

However various other bindings like ctrl+backspace, which also use word_separators don’t have sub-word counter-parts, which may cause incomplete UX.

I’ve added the following bindings to my user config, which are the ones I missed at most. There may however be more worth for complete sub-word experience.

	{
		"keys": ["alt+backspace"],
		"command": "delete_word", "args": { "sub_words": true, "forward": false }
	},
	{
		"keys": ["alt+delete"],
		"command": "delete_word", "args": { "sub_words": true, "forward": true }
	},
0 Likes

#5

uh …
this is what happens with three SHIFT + CTRL + RIGHT to me, starting from the line beginning:

You’re telling me that it should stop before "strctBuffer (as i’d like)?
If yes, why isn’t working on my ST? My word_separators like looks just like the one you showed me.

0 Likes

#6

Well, in the scenaio I had in mind, it actually does.

Animation

Even though moving forward and backward over ( seems inconsistent aka. different.

I can however confirm caret moving here the same way as in your screenshots.

It moves by words,

forward:

  1. from word begin to word end
  2. and then from word end to next word’s end

backward:

  1. from word end to word begin
  2. and then from word begin to previous word’s begin

It appears forward direction could be tweaked by moving forward by 1 sub-word after BufferReset, but it would still move caret behind ( but in front of structBuffer).

In this case, I don’t think your desired caret movement phillosophy can be achieved with simple settings. I guess custom plugin-based solution could achieve it, but otherwise it would be up to sublimehq to change default behavior or make it configurable.

That’s a feature request then: https://github.com/sublimehq/sublime_text/issues

EDIT:

Just realized ctrl+right unconditionally moves caret forward by word_ends.

	{ "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} },
	{ "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} },

Adding the following key bindings might result in behavior closer to your expectations. I used \W in context pattern, but it may be tweaked (maybe only whitespace or something along those lines), in order to tweak conditions when to move between words.

	{
		"keys": ["ctrl+left"],
		"command": "move", "args": {"by": "word_ends", "forward": false},
		"context": [
			{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"}
		]
	},
	{
		"keys": ["ctrl+right"],
		"command": "move", "args": {"by": "words", "forward": true},
		"context": [
			{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"}
		]
	},
1 Like

#7

Oh! That’s great. Thanks!

So, i’m trying to wrap my head around this all and it is not easy.

Given your assertion here, what does the new context added to the keycode? I mean, how could the added logic be described in plain words?

Also, I immediately tried to add the same on the other keybindings

{ "keys": ["ctrl+left"],        "command": "move", "args": {"by": "word_ends",    "forward": false}                , "context": [{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"} ] },
{ "keys": ["ctrl+right"],       "command": "move", "args": {"by": "words",        "forward": true }                , "context": [{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"} ] },
{ "keys": ["ctrl+shift+left"],  "command": "move", "args": {"by": "words",        "forward": false, "extend": true}, "context": [{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"} ] },
{ "keys": ["ctrl+shift+right"], "command": "move", "args": {"by": "word_ends",    "forward": true , "extend": true}, "context": [{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"} ] },
{ "keys": ["alt+left"],         "command": "move", "args": {"by": "subwords",     "forward": false}                , "context": [{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"} ] },
{ "keys": ["alt+right"],        "command": "move", "args": {"by": "subword_ends", "forward": true }                , "context": [{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"} ] },
{ "keys": ["alt+shift+left"],   "command": "move", "args": {"by": "subwords",     "forward": false, "extend": true}, "context": [{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"} ] },
{ "keys": ["alt+shift+right"],  "command": "move", "args": {"by": "subword_ends", "forward": true , "extend": true}, "context": [{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"} ] },

But the only working are ctrl+left ctrl+right… so I’m obviously missing something :slight_smile:

0 Likes

#8

Basic descriptions about key bindings and what context is meant for, can be found at https://docs.sublimetext.io/guide/customization/key_bindings.html. A reference and detailed description is located at https://docs.sublimetext.io/reference/key_bindings.html.

ST merges all *.sublime-keymap files in package loading order, starting with the one from Default package and ending with any file in User package.

This way an identical binding in User package overrides a binding in Default package.

The context key basically specifies conditions which need to be met in order for a key press calling specified command.

This means in particular:

  1. following bindings in Default/Default.sublime-keymap are responsible for default behavior:

    Always move caret from word beginning to word beginning in backward direction when hitting ctrl+left:

     { "keys": ["ctrl+left"], "command": "move", "args": {"by": "words", "forward": false} },
    

    Always move caret from word ending to word ending when hitting ctrl+right:

     { "keys": ["ctrl+right"], "command": "move", "args": {"by": "word_ends", "forward": true} },
    
  2. key bindings in User/Default.sublime-keymap override default bindings or those from any other package.

    The following says:

    When hitting ctrl+left, override default key binding and move caret to the end of the previous word, if the text in front of the caret, ends with ($) non-alphanumeric character (\W).

     {
     	"keys": ["ctrl+left"],
     	"command": "move", "args": {"by": "word_ends", "forward": false},
     	"context": [
     		{ "key": "preceding_text", "operator": "regex_contains", "operand": "\\W$"}
     	]
     },
    

    When hitting ctrl+right, override default key binding and move to beginning of the next word, if the text after the caret, starts with (^) non-alphanumeric character (\W).

     {
     	"keys": ["ctrl+right"],
     	"command": "move", "args": {"by": "words", "forward": true},
     	"context": [
     		{ "key": "following_text", "operator": "regex_contains", "operand": "^\\W"}
     	]
     },
    

Sublime Text uses

  1. word_separators setting to specify meaning of words, word_ends,
  2. sub_word_separators setting to specify meaning of subwords and subword_ends.

Unfortunatelly it might not always be the same as \W pattern, but it should work reliable enough. If not, you might want to replace it by a pattern which matches your preferred word-separators. But this may fairly quickly end up in various syntax specific key bindings, as various syntaxes have different demands on word boundaries.

Ideally it would provide a context which tells us, whether caret is at the beginning or end of a word or subword, to tweak those bindings.

0 Likes