Skip to content

Enable word selection with a key modifier. #130215

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,55 @@
This repository contains a commit to enable word selection with the Control key in Visual Studio Code.

This feature has been requested in many different places and unfortunately my commit has not been merged.

## Release
You can download a compiled Release for Windows in the [Release](https://github.com/manuelxmarquez/vscode/releases) tab.

The release should also contain the changes as described [here](https://stackoverflow.com/questions/37143536/no-extensions-found-when-running-visual-studio-code-from-source) in order to get the Extension Gallery working.

## Instructions
Once in Visual Studio Code you can enable the feature or add the JSON property:
```
"editor.wordSelection": true
```
![image](https://user-images.githubusercontent.com/30425852/128424656-017e60b2-5d4b-4b93-a563-e95156ec4b5f.png)

## Extensions
In order to get an experience similar to Visual Studio download [Visual Studio Keymap](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vs-keybindings) for key bindings.

I submitted a pull request for a few extra key bindings but they were not merged. You can add these additional key bindings.
```
{
"key": "ctrl+shift+u",
"command": "editor.action.transformToUppercase",
"when": "editorTextFocus && !editorReadonly"
},
{
"key": "ctrl+u",
"command": "editor.action.transformToLowercase",
"when": "editorTextFocus && !editorReadonly"
},
{
"key": "ctrl+m ctrl+o",
"command": "editor.foldAll",
"when": "editorTextFocus && foldingEnabled"
},
{
"key": "ctrl+m ctrl+m",
"command": "editor.toggleFold",
"when": "editorTextFocus && foldingEnabled"
}
```

## Links
My post on [GitHub Issue](https://github.com/microsoft/vscode/issues/23957#issuecomment-893863456)

[StackOverflow Question](https://stackoverflow.com/questions/37143536/no-extensions-found-when-running-visual-studio-code-from-source)





# Visual Studio Code - Open Source ("Code - OSS")
[![Feature Requests](https://img.shields.io/github/issues/microsoft/vscode/feature-request.svg)](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc)
[![Bugs](https://img.shields.io/github/issues/microsoft/vscode/bug.svg)](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug)
Expand Down
10 changes: 10 additions & 0 deletions src/vs/editor/browser/view/viewController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ export class ViewController {
}
}

private _hasWordSelectionModifier(data: IMouseDispatchData): boolean {
return this.configuration.options.get(EditorOption.wordSelection) && this._hasNonMulticursorModifier(data);
}

public dispatchMouse(data: IMouseDispatchData): void {
const options = this.configuration.options;
const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard));
Expand Down Expand Up @@ -192,6 +196,12 @@ export class ViewController {
}
}
}
} else if (this._hasWordSelectionModifier(data)) {
if (data.inSelectionMode) {
this._wordSelectDrag(data.position, data.revealType);
} else {
this._wordSelect(data.position, data.revealType);
}
} else {
if (data.inSelectionMode) {
if (data.altKey) {
Expand Down
10 changes: 10 additions & 0 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@ export interface IEditorOptions {
* Defaults to 'spread'.
*/
multiCursorPaste?: 'spread' | 'full';
/**
* Enable word selection with the mouse and a key modifier. This replaces the click link to gesture feature.
* Defaults to false
*/
wordSelection?: boolean;
/**
* Configure the editor's accessibility support.
* Defaults to 'auto'. It is best to leave this to 'auto'.
Expand Down Expand Up @@ -4724,6 +4729,7 @@ export const enum EditorOption {
unusualLineTerminators,
useShadowDOM,
useTabStops,
wordSelection,
wordSeparators,
wordWrap,
wordWrapBreakAfterCharacters,
Expand Down Expand Up @@ -5101,6 +5107,10 @@ export const EditorOptions = {
markdownDescription: nls.localize('multiCursorPaste', "Controls pasting when the line count of the pasted text matches the cursor count.")
}
)),
wordSelection: register(new EditorBooleanOption(
EditorOption.wordSelection, 'wordSelection', false,
{ description: nls.localize('wordSelection', "Enable word selection with the mouse and a key modifier. This replaces the click link to gesture feature.") }
)),
occurrencesHighlight: register(new EditorBooleanOption(
EditorOption.occurrencesHighlight, 'occurrencesHighlight', true,
{ description: nls.localize('occurrencesHighlight', "Controls whether the editor should highlight semantic symbol occurrences.") }
Expand Down
33 changes: 17 additions & 16 deletions src/vs/editor/common/standalone/standaloneEnums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,22 +291,23 @@ export enum EditorOption {
unusualLineTerminators = 116,
useShadowDOM = 117,
useTabStops = 118,
wordSeparators = 119,
wordWrap = 120,
wordWrapBreakAfterCharacters = 121,
wordWrapBreakBeforeCharacters = 122,
wordWrapColumn = 123,
wordWrapOverride1 = 124,
wordWrapOverride2 = 125,
wrappingIndent = 126,
wrappingStrategy = 127,
showDeprecated = 128,
inlayHints = 129,
editorClassName = 130,
pixelRatio = 131,
tabFocusMode = 132,
layoutInfo = 133,
wrappingInfo = 134
wordSelection = 119,
wordSeparators = 120,
wordWrap = 121,
wordWrapBreakAfterCharacters = 122,
wordWrapBreakBeforeCharacters = 123,
wordWrapColumn = 124,
wordWrapOverride1 = 125,
wordWrapOverride2 = 126,
wrappingIndent = 127,
wrappingStrategy = 128,
showDeprecated = 129,
inlayHints = 130,
editorClassName = 131,
pixelRatio = 132,
tabFocusMode = 133,
layoutInfo = 134,
wrappingInfo = 135
}

/**
Expand Down
32 changes: 20 additions & 12 deletions src/vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class ClickLinkMouseEvent {
this.isLeftClick = source.event.leftButton;
this.isMiddleClick = source.event.middleButton;
this.isRightClick = source.event.rightButton;
this.hasTriggerModifier = hasModifier(source.event, opts.triggerModifier);
this.hasTriggerModifier = hasModifier(source.event, opts.triggerModifier) && !opts.isWordSelectionEnabled;
this.hasSideBySideModifier = hasModifier(source.event, opts.triggerSideBySideModifier);
this.isNoneOrSingleMouseDown = (source.event.detail <= 1);
}
Expand All @@ -50,9 +50,9 @@ export class ClickLinkKeyboardEvent {
public readonly hasTriggerModifier: boolean;

constructor(source: IKeyboardEvent, opts: ClickLinkOptions) {
this.keyCodeIsTriggerKey = (source.keyCode === opts.triggerKey);
this.keyCodeIsTriggerKey = (source.keyCode === opts.triggerKey && !opts.isWordSelectionEnabled);
this.keyCodeIsSideBySideKey = (source.keyCode === opts.triggerSideBySideKey);
this.hasTriggerModifier = hasModifier(source, opts.triggerModifier);
this.hasTriggerModifier = hasModifier(source, opts.triggerModifier) && !opts.isWordSelectionEnabled;
}
}
export type TriggerModifier = 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey';
Expand All @@ -63,17 +63,20 @@ export class ClickLinkOptions {
public readonly triggerModifier: TriggerModifier;
public readonly triggerSideBySideKey: KeyCode;
public readonly triggerSideBySideModifier: TriggerModifier;
public readonly isWordSelectionEnabled: boolean;

constructor(
triggerKey: KeyCode,
triggerModifier: TriggerModifier,
triggerSideBySideKey: KeyCode,
triggerSideBySideModifier: TriggerModifier
triggerSideBySideModifier: TriggerModifier,
isWordSelectionEnabled: boolean
) {
this.triggerKey = triggerKey;
this.triggerModifier = triggerModifier;
this.triggerSideBySideKey = triggerSideBySideKey;
this.triggerSideBySideModifier = triggerSideBySideModifier;
this.isWordSelectionEnabled = isWordSelectionEnabled;
}

public equals(other: ClickLinkOptions): boolean {
Expand All @@ -82,22 +85,23 @@ export class ClickLinkOptions {
&& this.triggerModifier === other.triggerModifier
&& this.triggerSideBySideKey === other.triggerSideBySideKey
&& this.triggerSideBySideModifier === other.triggerSideBySideModifier
&& this.isWordSelectionEnabled === other.isWordSelectionEnabled
);
}
}

function createOptions(multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey'): ClickLinkOptions {
function createOptions(multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey', isWordSelectionEnabled: boolean): ClickLinkOptions {
if (multiCursorModifier === 'altKey') {
if (platform.isMacintosh) {
return new ClickLinkOptions(KeyCode.Meta, 'metaKey', KeyCode.Alt, 'altKey');
return new ClickLinkOptions(KeyCode.Meta, 'metaKey', KeyCode.Alt, 'altKey', isWordSelectionEnabled);
}
return new ClickLinkOptions(KeyCode.Ctrl, 'ctrlKey', KeyCode.Alt, 'altKey');
return new ClickLinkOptions(KeyCode.Ctrl, 'ctrlKey', KeyCode.Alt, 'altKey', isWordSelectionEnabled);
}

if (platform.isMacintosh) {
return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Meta, 'metaKey');
return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Meta, 'metaKey', isWordSelectionEnabled);
}
return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Ctrl, 'ctrlKey');
return new ClickLinkOptions(KeyCode.Alt, 'altKey', KeyCode.Ctrl, 'ctrlKey', isWordSelectionEnabled);
}

export class ClickLinkGesture extends Disposable {
Expand All @@ -124,15 +128,19 @@ export class ClickLinkGesture extends Disposable {

this._editor = editor;
this._alwaysFireExecuteOnMouseUp = alwaysFireOnMouseUp;
this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier));
this._opts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier), this._editor.getOption(EditorOption.wordSelection));

this._lastMouseMoveEvent = null;
this._hasTriggerKeyOnMouseDown = false;
this._lineNumberOnMouseDown = 0;

if (this._editor.getOption(EditorOption.wordSelection)) {
return;
}

this._register(this._editor.onDidChangeConfiguration((e) => {
if (e.hasChanged(EditorOption.multiCursorModifier)) {
const newOpts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier));
if (e.hasChanged(EditorOption.multiCursorModifier) || e.hasChanged(EditorOption.wordSelection)) {
const newOpts = createOptions(this._editor.getOption(EditorOption.multiCursorModifier), this._editor.getOption(EditorOption.wordSelection));
if (this._opts.equals(newOpts)) {
return;
}
Expand Down
39 changes: 23 additions & 16 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3217,6 +3217,11 @@ declare namespace monaco.editor {
* Defaults to 'spread'.
*/
multiCursorPaste?: 'spread' | 'full';
/**
* Enable word selection with the mouse and a key modifier. This replaces the click link to gesture feature.
* Defaults to false
*/
wordSelection?: boolean;
/**
* Configure the editor's accessibility support.
* Defaults to 'auto'. It is best to leave this to 'auto'.
Expand Down Expand Up @@ -4540,22 +4545,23 @@ declare namespace monaco.editor {
unusualLineTerminators = 116,
useShadowDOM = 117,
useTabStops = 118,
wordSeparators = 119,
wordWrap = 120,
wordWrapBreakAfterCharacters = 121,
wordWrapBreakBeforeCharacters = 122,
wordWrapColumn = 123,
wordWrapOverride1 = 124,
wordWrapOverride2 = 125,
wrappingIndent = 126,
wrappingStrategy = 127,
showDeprecated = 128,
inlayHints = 129,
editorClassName = 130,
pixelRatio = 131,
tabFocusMode = 132,
layoutInfo = 133,
wrappingInfo = 134
wordSelection = 119,
wordSeparators = 120,
wordWrap = 121,
wordWrapBreakAfterCharacters = 122,
wordWrapBreakBeforeCharacters = 123,
wordWrapColumn = 124,
wordWrapOverride1 = 125,
wordWrapOverride2 = 126,
wrappingIndent = 127,
wrappingStrategy = 128,
showDeprecated = 129,
inlayHints = 130,
editorClassName = 131,
pixelRatio = 132,
tabFocusMode = 133,
layoutInfo = 134,
wrappingInfo = 135
}

export const EditorOptions: {
Expand Down Expand Up @@ -4633,6 +4639,7 @@ declare namespace monaco.editor {
multiCursorMergeOverlapping: IEditorOption<EditorOption.multiCursorMergeOverlapping, boolean>;
multiCursorModifier: IEditorOption<EditorOption.multiCursorModifier, 'altKey' | 'metaKey' | 'ctrlKey'>;
multiCursorPaste: IEditorOption<EditorOption.multiCursorPaste, 'spread' | 'full'>;
wordSelection: IEditorOption<EditorOption.wordSelection, boolean>;
occurrencesHighlight: IEditorOption<EditorOption.occurrencesHighlight, boolean>;
overviewRulerBorder: IEditorOption<EditorOption.overviewRulerBorder, boolean>;
overviewRulerLanes: IEditorOption<EditorOption.overviewRulerLanes, number>;
Expand Down