From c9fde20123c8c505727c2bfc35b5784ba9243f63 Mon Sep 17 00:00:00 2001
From: fetiew <feitew@umich.edu>
Date: Mon, 16 Apr 2018 23:51:40 -0400
Subject: [PATCH 1/5] Implement word part move and delete

---
 src/vs/base/common/strings.ts                 |  24 ++
 .../common/controller/cursorWordOperations.ts | 128 ++++++++++-
 .../test/wordPartOperations.test.ts           | 207 ++++++++++++++++++
 .../wordPartOperations/wordPartOperations.ts  | 113 ++++++++++
 src/vs/editor/editor.all.ts                   |   1 +
 5 files changed, 471 insertions(+), 2 deletions(-)
 create mode 100644 src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
 create mode 100644 src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts

diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
index deeb3ae1f999d..f78b437eb5355 100644
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -323,6 +323,30 @@ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.len
 	return -1;
 }
 
+export function lastWordPartEnd(str: string, startIndex: number = str.length-1): number{
+	for (let i = startIndex; i >= 0; i--) {
+		let chCode = str.charCodeAt(i);
+		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) {
+			return i-1;
+		}
+	}
+	return -1;
+}
+
+export function nextWordPartBegin(str: string, startIndex: number = str.length-1): number{
+	const checkLowerCase = str.charCodeAt(startIndex-1) === CharCode.Space; // does a lc char count as a part start?
+	for (let i = startIndex; i < str.length; ++i){
+		let chCode = str.charCodeAt(i);
+		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || (checkLowerCase && isLowerAsciiLetter(chCode))) {
+			return i+1;
+		}
+		if (chCode === CharCode.Underline){
+			return i+2;
+		}
+	}
+	return -1;
+}
+
 export function compare(a: string, b: string): number {
 	if (a < b) {
 		return -1;
diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts
index 24bbcac19c3d9..a0410aaf74c28 100644
--- a/src/vs/editor/common/controller/cursorWordOperations.ts
+++ b/src/vs/editor/common/controller/cursorWordOperations.ts
@@ -229,7 +229,7 @@ export class WordOperations {
 		return new Position(lineNumber, column);
 	}
 
-	private static _deleteWordLeftWhitespace(model: ICursorSimpleModel, position: Position): Range {
+	protected static _deleteWordLeftWhitespace(model: ICursorSimpleModel, position: Position): Range {
 		const lineContent = model.getLineContent(position.lineNumber);
 		const startIndex = position.column - 2;
 		const lastNonWhitespace = strings.lastNonWhitespaceIndex(lineContent, startIndex);
@@ -304,7 +304,7 @@ export class WordOperations {
 		return len;
 	}
 
-	private static _deleteWordRightWhitespace(model: ICursorSimpleModel, position: Position): Range {
+	protected static _deleteWordRightWhitespace(model: ICursorSimpleModel, position: Position): Range {
 		const lineContent = model.getLineContent(position.lineNumber);
 		const startIndex = position.column - 1;
 		const firstNonWhitespace = this._findFirstNonWhitespaceChar(lineContent, startIndex);
@@ -454,3 +454,127 @@ export class WordOperations {
 		return cursor.move(true, lineNumber, column, 0);
 	}
 }
+
+export class WordPartOperations extends WordOperations {
+	public static deleteWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
+		if (!selection.isEmpty()) {
+			return selection;
+		}
+
+		const position = new Position(selection.positionLineNumber, selection.positionColumn);
+
+		let lineNumber = position.lineNumber;
+		let column = position.column;
+
+		if (lineNumber === 1 && column === 1) {
+			// Ignore deleting at beginning of file
+			return null;
+		}
+
+		if (whitespaceHeuristics) {
+			let r = WordOperations._deleteWordLeftWhitespace(model, position);
+			if (r) {
+				return r;
+			}
+		}
+
+		const lineContent = model.getLineContent(position.lineNumber);
+		const startIndex = position.column - 2;
+		const lastWordPartEnd = strings.lastWordPartEnd(lineContent, startIndex);
+		const wordRange = WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+
+		if (lastWordPartEnd === -1 || (wordRange.startColumn > lastWordPartEnd && wordRange.startColumn < column)){
+			return wordRange;
+		}
+		else {
+			const range = new Range(lineNumber, column, lineNumber, lastWordPartEnd+2);
+			return range;
+		}
+
+	}
+
+	public static deleteWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
+		if (!selection.isEmpty()) {
+			return selection;
+		}
+
+		const position = new Position(selection.positionLineNumber, selection.positionColumn);
+
+		let lineNumber = position.lineNumber;
+		let column = position.column;
+
+		const lineCount = model.getLineCount();
+		const maxColumn = model.getLineMaxColumn(lineNumber);
+		if (lineNumber === lineCount && column === maxColumn) {
+			// Ignore deleting at end of file
+			return null;
+		}
+
+		if (whitespaceHeuristics) {
+			let r = WordOperations._deleteWordRightWhitespace(model, position);
+			if (r) {
+				return r;
+			}
+		}
+
+		const lineContent = model.getLineContent(position.lineNumber);
+		const startIndex = position.column;
+		const nextWordPartBegin = strings.nextWordPartBegin(lineContent, startIndex);
+		const wordRange = WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+
+		if (nextWordPartBegin === -1 || (wordRange && wordRange.endColumn < nextWordPartBegin && wordRange.endColumn >= column)){
+			return wordRange;
+		}
+		else {
+			return new Range(lineNumber, column, lineNumber, nextWordPartBegin);
+		}
+	}
+
+	public static moveWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
+		const startIndex = position.column - 2;
+		const lineNumber = position.lineNumber;
+		const column = position.column;
+		const wordPartCol = strings.lastWordPartEnd(model.getLineContent(lineNumber), startIndex);
+		const wordPos = WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType);
+
+		if (column === 1) {
+			if (lineNumber > 1) {
+				return new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber-1));
+			}
+			return null;
+		}
+
+		if (wordPartCol === -1 || (wordPos.column > wordPartCol && wordPos.column < column && wordPos.lineNumber === lineNumber)){
+			return wordPos;
+		}
+		else{
+			return new Position(lineNumber, wordPartCol+2);
+		}
+	}
+
+	public static moveWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
+		const startIndex = position.column;
+		const lineNumber = position.lineNumber;
+		const column = position.column;
+		const wordPartCol = strings.nextWordPartBegin(model.getLineContent(lineNumber), startIndex);
+		const wordPos = WordOperations.moveWordRight(wordSeparators, model, position, wordNavigationType);
+
+		const lineCount = model.getLineCount();
+		const maxColumn = model.getLineMaxColumn(lineNumber);
+		if (column === maxColumn) {
+			if (lineNumber < lineCount) {
+				return new Position(lineNumber + 1, 1);
+			}
+			return null;
+		}
+
+
+		if (wordPartCol === -1 || (wordPos.column < wordPartCol && wordPos.column > column && wordPos.lineNumber === lineNumber)){
+			return wordPos;
+		}
+		else {
+			return new Position(lineNumber, wordPartCol);
+		}
+	}
+
+}
diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
new file mode 100644
index 0000000000000..ac44bfe79bb06
--- /dev/null
+++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
@@ -0,0 +1,207 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import * as assert from 'assert';
+import { Position } from 'vs/editor/common/core/position';
+import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
+import {
+	DeleteWordPartLeft, DeleteWordPartRight,
+	CursorWordPartLeft, CursorWordPartRight
+} from 'vs/editor/contrib/wordPartOperations/wordPartOperations';
+import { EditorCommand } from 'vs/editor/browser/editorExtensions';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+
+suite('WordPartOperations', () => {
+	const _deleteWordPartLeft = new DeleteWordPartLeft();
+	const _deleteWordPartRight = new DeleteWordPartRight();
+	const _cursorWordPartLeft = new CursorWordPartLeft();
+	const _cursorWordPartRight = new CursorWordPartRight();
+
+	function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void {
+		command.runEditorCommand(null, editor, null);
+	}
+	function moveWordPartLeft(editor: ICodeEditor, inSelectionmode: boolean = false): void{
+		runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartLeft);
+	}
+	function moveWordPartRight(editor: ICodeEditor, inSelectionmode: boolean = false): void{
+		runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartRight);
+	}
+	function deleteWordPartLeft(editor: ICodeEditor): void{
+		runEditorCommand(editor, _deleteWordPartLeft);
+	}
+	function deleteWordPartRight(editor: ICodeEditor): void{
+		runEditorCommand(editor, _deleteWordPartRight);
+	}
+
+	test('move word part left basic', () => {
+		withTestCodeEditor([
+			'start line',
+			'thisIsACamelCaseVar  this_is_a_snake_case_var',
+			'end line'
+		], {}, (editor, _) => {
+			editor.setPosition(new Position(3, 8));
+			const expectedStops = [
+				[3, 5],
+				[3, 4],
+				[3, 1],
+				[2, 46],
+				[2, 42],
+				[2, 37],
+				[2, 31],
+				[2, 29],
+				[2, 26],
+				[2, 22],
+				[2, 21],
+				[2, 20],
+				[2, 17],
+				[2, 13],
+				[2, 8],
+				[2, 7],
+				[2, 5],
+				[2, 1],
+				[1, 11],
+				[1, 7],
+				[1, 6],
+				[1, 1]
+			];
+
+			let actualStops: number[][] = [];
+			for (let i = 0; i < expectedStops.length; i++) {
+				moveWordPartLeft(editor);
+				const pos = editor.getPosition();
+				actualStops.push([pos.lineNumber, pos.column]);
+			}
+
+			assert.deepEqual(actualStops, expectedStops);
+		});
+	});
+
+	test('move word part right basic', () => {
+		withTestCodeEditor([
+			'start line',
+			'thisIsACamelCaseVar  this_is_a_snake_case_var',
+			'end line'
+		], {}, (editor, _) => {
+			editor.setPosition(new Position(1, 1));
+			const expectedStops = [
+				[1, 6],
+				[1, 7],
+				[1, 11],
+				[2, 1],
+				[2, 5],
+				[2, 7],
+				[2, 8],
+				[2, 13],
+				[2, 17],
+				[2, 20],
+				[2, 21],
+				[2, 22],
+				[2, 27],
+				[2, 30],
+				[2, 32],
+				[2, 38],
+				[2, 43],
+				[2, 46],
+				[3, 1],
+				[3, 4],
+				[3, 5],
+				[3, 9]
+			];
+
+			let actualStops: number[][] = [];
+			for (let i = 0; i < expectedStops.length; i++) {
+				moveWordPartRight(editor);
+				const pos = editor.getPosition();
+				actualStops.push([pos.lineNumber, pos.column]);
+			}
+
+			assert.deepEqual(actualStops, expectedStops);
+		});
+	});
+
+	test('delete word part left basic', () => {
+		withTestCodeEditor([
+			'   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var'
+		], {}, (editor, _) => {
+			const model = editor.getModel();
+			editor.setPosition(new Position(1, 84));
+
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case', '001');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake', '002');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a', '003');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is', '004');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this', '005');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  ', '006');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar', '007');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamelCase', '008');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsACamel', '009');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIsA', '010');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  thisIs', '011');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  this', '012');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  ', '013');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */', '014');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 ', '015');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-', '016');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5', '017');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +', '018');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 ', '019');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= ', '020');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+=', '021');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a', '022');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text ', '023');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text', '024');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some ', '025');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some', '026');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just ', '027');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just', '028');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* ', '029');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /*', '030');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   ', '031');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '', '032');
+		});
+	});
+
+	test('delete word part right basic', () => {
+		withTestCodeEditor([
+			'   /* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var'
+		], {}, (editor, _) => {
+			const model = editor.getModel();
+			editor.setPosition(new Position(1, 1));
+
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '/* Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '001');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '002');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Just some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '003');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '004');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'some text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '005');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '006');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'text a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '007');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '008');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '009');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '+= 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '010');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' 3 +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '011');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' +5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '012');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '5-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '013');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '-3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '014');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '3 */  thisIsACamelCaseVar  this_is_a_snake_case_var', '015');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' */  thisIsACamelCaseVar  this_is_a_snake_case_var', '016');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '  thisIsACamelCaseVar  this_is_a_snake_case_var', '017');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'thisIsACamelCaseVar  this_is_a_snake_case_var', '018');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'IsACamelCaseVar  this_is_a_snake_case_var', '019');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'ACamelCaseVar  this_is_a_snake_case_var', '020');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CamelCaseVar  this_is_a_snake_case_var', '021');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CaseVar  this_is_a_snake_case_var', '022');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Var  this_is_a_snake_case_var', '023');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '  this_is_a_snake_case_var', '024');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'this_is_a_snake_case_var', '025');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'is_a_snake_case_var', '026');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a_snake_case_var', '027');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'snake_case_var', '028');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'case_var', '029');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'var', '030');
+			deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '', '031');
+		});
+	});
+});
diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
new file mode 100644
index 0000000000000..781d1ed588e8b
--- /dev/null
+++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
@@ -0,0 +1,113 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+'use strict';
+
+import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
+import { ITextModel } from 'vs/editor/common/model';
+import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
+import { Selection } from 'vs/editor/common/core/selection';
+import { registerEditorCommand } from 'vs/editor/browser/editorExtensions';
+import { Range } from 'vs/editor/common/core/range';
+import { WordNavigationType, WordPartOperations } from 'vs/editor/common/controller/cursorWordOperations';
+import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharacterClassifier';
+import { DeleteWordCommand, MoveWordCommand } from '../wordOperations/wordOperations';
+import { Position } from 'vs/editor/common/core/position';
+
+export class DeleteWordPartLeft extends DeleteWordCommand {
+	constructor() {
+		super({
+			whitespaceHeuristics: true,
+			wordNavigationType: WordNavigationType.WordStart,
+			id: 'deleteWordPartLeft',
+			precondition: EditorContextKeys.writable,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyCode.Backspace,
+				mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }
+			}
+		});
+	}
+
+	protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
+		let r = WordPartOperations.deleteWordPartLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+		if (r) {
+			return r;
+		}
+		return new Range(1, 1, 1, 1);
+	}
+}
+
+export class DeleteWordPartRight extends DeleteWordCommand {
+	constructor() {
+		super({
+			whitespaceHeuristics: true,
+			wordNavigationType: WordNavigationType.WordEnd,
+			id: 'deleteWordPartRight',
+			precondition: EditorContextKeys.writable,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyCode.Delete,
+				mac: { primary: KeyMod.CtrlCmd | KeyCode.Delete }
+			}
+		});
+	}
+
+	protected _delete(wordSeparators: WordCharacterClassifier, model: ITextModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
+		let r = WordPartOperations.deleteWordPartRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+		if (r) {
+			return r;
+		}
+		const lineCount = model.getLineCount();
+		const maxColumn = model.getLineMaxColumn(lineCount);
+		return new Range(lineCount, maxColumn, lineCount, maxColumn);
+	}
+}
+
+export class CursorWordPartLeft extends MoveWordCommand {
+	constructor() {
+		super({
+			inSelectionMode: false,
+			wordNavigationType: WordNavigationType.WordStart,
+			id: 'cursorWordPartStartLeft',
+			precondition: null,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyCode.LeftArrow,
+				mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow }
+			}
+		});
+	}
+
+	protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position {
+		return WordPartOperations.moveWordPartLeft(wordSeparators, model, position, wordNavigationType);
+	}
+}
+
+export class CursorWordPartRight extends MoveWordCommand {
+	constructor() {
+		super({
+			inSelectionMode: false,
+			wordNavigationType: WordNavigationType.WordEnd,
+			id: 'cursorWordPartRight',
+			precondition: null,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyCode.RightArrow,
+				mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow }
+			}
+		});
+	}
+
+	protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position {
+		return WordPartOperations.moveWordPartRight(wordSeparators, model, position, wordNavigationType);
+	}
+}
+
+
+registerEditorCommand(new DeleteWordPartLeft());
+registerEditorCommand(new DeleteWordPartRight());
+registerEditorCommand(new CursorWordPartLeft());
+registerEditorCommand(new CursorWordPartRight());
diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts
index 07cbf332285eb..edcf0f3b6d64c 100644
--- a/src/vs/editor/editor.all.ts
+++ b/src/vs/editor/editor.all.ts
@@ -41,3 +41,4 @@ import 'vs/editor/contrib/suggest/suggestController';
 import 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode';
 import 'vs/editor/contrib/wordHighlighter/wordHighlighter';
 import 'vs/editor/contrib/wordOperations/wordOperations';
+import 'vs/editor/contrib/wordPartOperations/wordPartOperations';

From 7415670044d2a0128c6d232a19192b2edcd3a422 Mon Sep 17 00:00:00 2001
From: fetiew <feitew@umich.edu>
Date: Tue, 17 Apr 2018 17:12:50 -0400
Subject: [PATCH 2/5] Fixed formatting issues

---
 src/vs/base/common/strings.ts                    | 16 ++++++++--------
 .../common/controller/cursorWordOperations.ts    | 16 ++++++++--------
 .../test/wordPartOperations.test.ts              |  8 ++++----
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
index f78b437eb5355..d423e60460932 100644
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -323,25 +323,25 @@ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.len
 	return -1;
 }
 
-export function lastWordPartEnd(str: string, startIndex: number = str.length-1): number{
+export function lastWordPartEnd(str: string, startIndex: number = str.length - 1): number {
 	for (let i = startIndex; i >= 0; i--) {
 		let chCode = str.charCodeAt(i);
 		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) {
-			return i-1;
+			return i - 1;
 		}
 	}
 	return -1;
 }
 
-export function nextWordPartBegin(str: string, startIndex: number = str.length-1): number{
-	const checkLowerCase = str.charCodeAt(startIndex-1) === CharCode.Space; // does a lc char count as a part start?
-	for (let i = startIndex; i < str.length; ++i){
+export function nextWordPartBegin(str: string, startIndex: number = str.length - 1): number {
+	const checkLowerCase = str.charCodeAt(startIndex - 1) === CharCode.Space; // does a lc char count as a part start?
+	for (let i = startIndex; i < str.length; ++i) {
 		let chCode = str.charCodeAt(i);
 		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || (checkLowerCase && isLowerAsciiLetter(chCode))) {
-			return i+1;
+			return i + 1;
 		}
-		if (chCode === CharCode.Underline){
-			return i+2;
+		if (chCode === CharCode.Underline) {
+			return i + 2;
 		}
 	}
 	return -1;
diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts
index a0410aaf74c28..1f620a7637873 100644
--- a/src/vs/editor/common/controller/cursorWordOperations.ts
+++ b/src/vs/editor/common/controller/cursorWordOperations.ts
@@ -483,11 +483,11 @@ export class WordPartOperations extends WordOperations {
 		const lastWordPartEnd = strings.lastWordPartEnd(lineContent, startIndex);
 		const wordRange = WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
 
-		if (lastWordPartEnd === -1 || (wordRange.startColumn > lastWordPartEnd && wordRange.startColumn < column)){
+		if (lastWordPartEnd === -1 || (wordRange.startColumn > lastWordPartEnd && wordRange.startColumn < column)) {
 			return wordRange;
 		}
 		else {
-			const range = new Range(lineNumber, column, lineNumber, lastWordPartEnd+2);
+			const range = new Range(lineNumber, column, lineNumber, lastWordPartEnd + 2);
 			return range;
 		}
 
@@ -522,7 +522,7 @@ export class WordPartOperations extends WordOperations {
 		const nextWordPartBegin = strings.nextWordPartBegin(lineContent, startIndex);
 		const wordRange = WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
 
-		if (nextWordPartBegin === -1 || (wordRange && wordRange.endColumn < nextWordPartBegin && wordRange.endColumn >= column)){
+		if (nextWordPartBegin === -1 || (wordRange && wordRange.endColumn < nextWordPartBegin && wordRange.endColumn >= column)) {
 			return wordRange;
 		}
 		else {
@@ -539,16 +539,16 @@ export class WordPartOperations extends WordOperations {
 
 		if (column === 1) {
 			if (lineNumber > 1) {
-				return new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber-1));
+				return new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1));
 			}
 			return null;
 		}
 
-		if (wordPartCol === -1 || (wordPos.column > wordPartCol && wordPos.column < column && wordPos.lineNumber === lineNumber)){
+		if (wordPartCol === -1 || (wordPos.column > wordPartCol && wordPos.column < column && wordPos.lineNumber === lineNumber)) {
 			return wordPos;
 		}
-		else{
-			return new Position(lineNumber, wordPartCol+2);
+		else {
+			return new Position(lineNumber, wordPartCol + 2);
 		}
 	}
 
@@ -569,7 +569,7 @@ export class WordPartOperations extends WordOperations {
 		}
 
 
-		if (wordPartCol === -1 || (wordPos.column < wordPartCol && wordPos.column > column && wordPos.lineNumber === lineNumber)){
+		if (wordPartCol === -1 || (wordPos.column < wordPartCol && wordPos.column > column && wordPos.lineNumber === lineNumber)) {
 			return wordPos;
 		}
 		else {
diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
index ac44bfe79bb06..e5fa29f253536 100644
--- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
+++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
@@ -23,16 +23,16 @@ suite('WordPartOperations', () => {
 	function runEditorCommand(editor: ICodeEditor, command: EditorCommand): void {
 		command.runEditorCommand(null, editor, null);
 	}
-	function moveWordPartLeft(editor: ICodeEditor, inSelectionmode: boolean = false): void{
+	function moveWordPartLeft(editor: ICodeEditor, inSelectionmode: boolean = false): void {
 		runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartLeft);
 	}
-	function moveWordPartRight(editor: ICodeEditor, inSelectionmode: boolean = false): void{
+	function moveWordPartRight(editor: ICodeEditor, inSelectionmode: boolean = false): void {
 		runEditorCommand(editor, inSelectionmode ? _cursorWordPartLeft : _cursorWordPartRight);
 	}
-	function deleteWordPartLeft(editor: ICodeEditor): void{
+	function deleteWordPartLeft(editor: ICodeEditor): void {
 		runEditorCommand(editor, _deleteWordPartLeft);
 	}
-	function deleteWordPartRight(editor: ICodeEditor): void{
+	function deleteWordPartRight(editor: ICodeEditor): void {
 		runEditorCommand(editor, _deleteWordPartRight);
 	}
 

From f41ee9c0f6a0fdd9e94e385af02d2e9e4d5fb51a Mon Sep 17 00:00:00 2001
From: Alex Dima <alexdima@microsoft.com>
Date: Wed, 20 Jun 2018 17:03:31 +0200
Subject: [PATCH 3/5] Stylistic edits

---
 src/vs/base/common/strings.ts                 | 28 +-----
 .../common/controller/cursorWordOperations.ts | 98 ++++++++++---------
 .../test/wordPartOperations.test.ts           |  2 +
 3 files changed, 54 insertions(+), 74 deletions(-)

diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts
index c8072e04a3e27..31dff819977ba 100644
--- a/src/vs/base/common/strings.ts
+++ b/src/vs/base/common/strings.ts
@@ -280,30 +280,6 @@ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.len
 	return -1;
 }
 
-export function lastWordPartEnd(str: string, startIndex: number = str.length - 1): number {
-	for (let i = startIndex; i >= 0; i--) {
-		let chCode = str.charCodeAt(i);
-		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) {
-			return i - 1;
-		}
-	}
-	return -1;
-}
-
-export function nextWordPartBegin(str: string, startIndex: number = str.length - 1): number {
-	const checkLowerCase = str.charCodeAt(startIndex - 1) === CharCode.Space; // does a lc char count as a part start?
-	for (let i = startIndex; i < str.length; ++i) {
-		let chCode = str.charCodeAt(i);
-		if (chCode === CharCode.Space || chCode === CharCode.Tab || isUpperAsciiLetter(chCode) || (checkLowerCase && isLowerAsciiLetter(chCode))) {
-			return i + 1;
-		}
-		if (chCode === CharCode.Underline) {
-			return i + 2;
-		}
-	}
-	return -1;
-}
-
 export function compare(a: string, b: string): number {
 	if (a < b) {
 		return -1;
@@ -357,11 +333,11 @@ export function compareIgnoreCase(a: string, b: string): number {
 	}
 }
 
-function isLowerAsciiLetter(code: number): boolean {
+export function isLowerAsciiLetter(code: number): boolean {
 	return code >= CharCode.a && code <= CharCode.z;
 }
 
-function isUpperAsciiLetter(code: number): boolean {
+export function isUpperAsciiLetter(code: number): boolean {
 	return code >= CharCode.A && code <= CharCode.Z;
 }
 
diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts
index 05afe78875e9b..b69d9799b5300 100644
--- a/src/vs/editor/common/controller/cursorWordOperations.ts
+++ b/src/vs/editor/common/controller/cursorWordOperations.ts
@@ -10,6 +10,7 @@ import { WordCharacterClassifier, WordCharacterClass, getMapForWordSeparators }
 import * as strings from 'vs/base/common/strings';
 import { Range } from 'vs/editor/common/core/range';
 import { Selection } from 'vs/editor/common/core/selection';
+import { CharCode } from 'vs/base/common/charCode';
 
 interface IFindWordResult {
 	/**
@@ -464,6 +465,30 @@ export class WordOperations {
 	}
 }
 
+export function _lastWordPartEnd(str: string, startIndex: number = str.length - 1): number {
+	for (let i = startIndex; i >= 0; i--) {
+		let chCode = str.charCodeAt(i);
+		if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) {
+			return i - 1;
+		}
+	}
+	return -1;
+}
+
+export function _nextWordPartBegin(str: string, startIndex: number = str.length - 1): number {
+	const checkLowerCase = str.charCodeAt(startIndex - 1) === CharCode.Space; // does a lc char count as a part start?
+	for (let i = startIndex; i < str.length; ++i) {
+		let chCode = str.charCodeAt(i);
+		if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || (checkLowerCase && strings.isLowerAsciiLetter(chCode))) {
+			return i + 1;
+		}
+		if (chCode === CharCode.Underline) {
+			return i + 2;
+		}
+	}
+	return str.length + 1;
+}
+
 export class WordPartOperations extends WordOperations {
 	public static deleteWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
 		if (!selection.isEmpty()) {
@@ -471,9 +496,8 @@ export class WordPartOperations extends WordOperations {
 		}
 
 		const position = new Position(selection.positionLineNumber, selection.positionColumn);
-
-		let lineNumber = position.lineNumber;
-		let column = position.column;
+		const lineNumber = position.lineNumber;
+		const column = position.column;
 
 		if (lineNumber === 1 && column === 1) {
 			// Ignore deleting at beginning of file
@@ -487,19 +511,14 @@ export class WordPartOperations extends WordOperations {
 			}
 		}
 
-		const lineContent = model.getLineContent(position.lineNumber);
-		const startIndex = position.column - 2;
-		const lastWordPartEnd = strings.lastWordPartEnd(lineContent, startIndex);
 		const wordRange = WordOperations.deleteWordLeft(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+		const lastWordPartEnd = _lastWordPartEnd(model.getLineContent(position.lineNumber), position.column - 2);
+		const wordPartRange = new Range(lineNumber, column, lineNumber, lastWordPartEnd + 2);
 
-		if (lastWordPartEnd === -1 || (wordRange.startColumn > lastWordPartEnd && wordRange.startColumn < column)) {
+		if (wordPartRange.getStartPosition().isBeforeOrEqual(wordRange.getStartPosition())) {
 			return wordRange;
 		}
-		else {
-			const range = new Range(lineNumber, column, lineNumber, lastWordPartEnd + 2);
-			return range;
-		}
-
+		return wordPartRange;
 	}
 
 	public static deleteWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
@@ -508,9 +527,8 @@ export class WordPartOperations extends WordOperations {
 		}
 
 		const position = new Position(selection.positionLineNumber, selection.positionColumn);
-
-		let lineNumber = position.lineNumber;
-		let column = position.column;
+		const lineNumber = position.lineNumber;
+		const column = position.column;
 
 		const lineCount = model.getLineCount();
 		const maxColumn = model.getLineMaxColumn(lineNumber);
@@ -526,64 +544,48 @@ export class WordPartOperations extends WordOperations {
 			}
 		}
 
-		const lineContent = model.getLineContent(position.lineNumber);
-		const startIndex = position.column;
-		const nextWordPartBegin = strings.nextWordPartBegin(lineContent, startIndex);
 		const wordRange = WordOperations.deleteWordRight(wordSeparators, model, selection, whitespaceHeuristics, wordNavigationType);
+		const nextWordPartBegin = _nextWordPartBegin(model.getLineContent(position.lineNumber), position.column);
+		const wordPartRange = new Range(lineNumber, column, lineNumber, nextWordPartBegin);
 
-		if (nextWordPartBegin === -1 || (wordRange && wordRange.endColumn < nextWordPartBegin && wordRange.endColumn >= column)) {
+		if (wordRange.getEndPosition().isBeforeOrEqual(wordPartRange.getEndPosition())) {
 			return wordRange;
 		}
-		else {
-			return new Range(lineNumber, column, lineNumber, nextWordPartBegin);
-		}
+		return wordPartRange;
 	}
 
 	public static moveWordPartLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
-		const startIndex = position.column - 2;
 		const lineNumber = position.lineNumber;
 		const column = position.column;
-		const wordPartCol = strings.lastWordPartEnd(model.getLineContent(lineNumber), startIndex);
-		const wordPos = WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType);
-
 		if (column === 1) {
-			if (lineNumber > 1) {
-				return new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1));
-			}
-			return null;
+			return (lineNumber > 1 ? new Position(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)) : position);
 		}
 
-		if (wordPartCol === -1 || (wordPos.column > wordPartCol && wordPos.column < column && wordPos.lineNumber === lineNumber)) {
+		const wordPos = WordOperations.moveWordLeft(wordSeparators, model, position, wordNavigationType);
+		const lastWordPartEnd = _lastWordPartEnd(model.getLineContent(lineNumber), column - 2);
+		const wordPartPos = new Position(lineNumber, lastWordPartEnd + 2);
+
+		if (wordPartPos.isBeforeOrEqual(wordPos)) {
 			return wordPos;
 		}
-		else {
-			return new Position(lineNumber, wordPartCol + 2);
-		}
+		return wordPartPos;
 	}
 
 	public static moveWordPartRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
-		const startIndex = position.column;
 		const lineNumber = position.lineNumber;
 		const column = position.column;
-		const wordPartCol = strings.nextWordPartBegin(model.getLineContent(lineNumber), startIndex);
-		const wordPos = WordOperations.moveWordRight(wordSeparators, model, position, wordNavigationType);
-
-		const lineCount = model.getLineCount();
 		const maxColumn = model.getLineMaxColumn(lineNumber);
 		if (column === maxColumn) {
-			if (lineNumber < lineCount) {
-				return new Position(lineNumber + 1, 1);
-			}
-			return null;
+			return (lineNumber < model.getLineCount() ? new Position(lineNumber + 1, 1) : position);
 		}
 
+		const wordPos = WordOperations.moveWordRight(wordSeparators, model, position, wordNavigationType);
+		const nextWordPartBegin = _nextWordPartBegin(model.getLineContent(lineNumber), column);
+		const wordPartPos = new Position(lineNumber, nextWordPartBegin);
 
-		if (wordPartCol === -1 || (wordPos.column < wordPartCol && wordPos.column > column && wordPos.lineNumber === lineNumber)) {
+		if (wordPos.isBeforeOrEqual(wordPartPos)) {
 			return wordPos;
 		}
-		else {
-			return new Position(lineNumber, wordPartCol);
-		}
+		return wordPartPos;
 	}
-
 }
diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
index e5fa29f253536..2074dd9cfc691 100644
--- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
+++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts
@@ -144,10 +144,12 @@ suite('WordPartOperations', () => {
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */  ', '013');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 */', '014');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3 ', '015');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-3', '015bis');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5-', '016');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +5', '017');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 +', '018');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3 ', '019');
+			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= 3', '019bis');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+= ', '020');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a+=', '021');
 			deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '   /* Just some text a', '022');

From f22e404cfe78bd1cda97b15678cbd459692d501b Mon Sep 17 00:00:00 2001
From: Alex Dima <alexdima@microsoft.com>
Date: Wed, 20 Jun 2018 17:17:05 +0200
Subject: [PATCH 4/5] Add word part selection commands & tweak mac keybindings

---
 .../wordPartOperations/wordPartOperations.ts  | 56 +++++++++++++++----
 1 file changed, 45 insertions(+), 11 deletions(-)

diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
index 781d1ed588e8b..8ad25d28fa860 100644
--- a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
+++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
@@ -26,7 +26,7 @@ export class DeleteWordPartLeft extends DeleteWordCommand {
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
 				primary: KeyMod.Alt | KeyCode.Backspace,
-				mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace }
 			}
 		});
 	}
@@ -50,7 +50,7 @@ export class DeleteWordPartRight extends DeleteWordCommand {
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
 				primary: KeyMod.Alt | KeyCode.Delete,
-				mac: { primary: KeyMod.CtrlCmd | KeyCode.Delete }
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Delete }
 			}
 		});
 	}
@@ -66,7 +66,12 @@ export class DeleteWordPartRight extends DeleteWordCommand {
 	}
 }
 
-export class CursorWordPartLeft extends MoveWordCommand {
+export class WordPartLeftCommand extends MoveWordCommand {
+	protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position {
+		return WordPartOperations.moveWordPartLeft(wordSeparators, model, position, wordNavigationType);
+	}
+}
+export class CursorWordPartLeft extends WordPartLeftCommand {
 	constructor() {
 		super({
 			inSelectionMode: false,
@@ -76,17 +81,33 @@ export class CursorWordPartLeft extends MoveWordCommand {
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
 				primary: KeyMod.Alt | KeyCode.LeftArrow,
-				mac: { primary: KeyMod.CtrlCmd | KeyCode.LeftArrow }
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.LeftArrow }
 			}
 		});
 	}
+}
+export class CursorWordPartLeftSelect extends WordPartLeftCommand {
+	constructor() {
+		super({
+			inSelectionMode: true,
+			wordNavigationType: WordNavigationType.WordStart,
+			id: 'cursorWordPartStartLeftSelect',
+			precondition: null,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow,
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow }
+			}
+		});
+	}
+}
 
+export class WordPartRightCommand extends MoveWordCommand {
 	protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position {
-		return WordPartOperations.moveWordPartLeft(wordSeparators, model, position, wordNavigationType);
+		return WordPartOperations.moveWordPartRight(wordSeparators, model, position, wordNavigationType);
 	}
 }
-
-export class CursorWordPartRight extends MoveWordCommand {
+export class CursorWordPartRight extends WordPartRightCommand {
 	constructor() {
 		super({
 			inSelectionMode: false,
@@ -96,13 +117,24 @@ export class CursorWordPartRight extends MoveWordCommand {
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
 				primary: KeyMod.Alt | KeyCode.RightArrow,
-				mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow }
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.RightArrow }
 			}
 		});
 	}
-
-	protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position {
-		return WordPartOperations.moveWordPartRight(wordSeparators, model, position, wordNavigationType);
+}
+export class CursorWordPartRightSelect extends WordPartRightCommand {
+	constructor() {
+		super({
+			inSelectionMode: true,
+			wordNavigationType: WordNavigationType.WordEnd,
+			id: 'cursorWordPartRightSelect',
+			precondition: null,
+			kbOpts: {
+				kbExpr: EditorContextKeys.textInputFocus,
+				primary: KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow,
+				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow }
+			}
+		});
 	}
 }
 
@@ -110,4 +142,6 @@ export class CursorWordPartRight extends MoveWordCommand {
 registerEditorCommand(new DeleteWordPartLeft());
 registerEditorCommand(new DeleteWordPartRight());
 registerEditorCommand(new CursorWordPartLeft());
+registerEditorCommand(new CursorWordPartLeftSelect());
 registerEditorCommand(new CursorWordPartRight());
+registerEditorCommand(new CursorWordPartRightSelect());

From 66eb17581b3ade75da683c02f936b2355ab89627 Mon Sep 17 00:00:00 2001
From: Alex Dima <alexdima@microsoft.com>
Date: Wed, 20 Jun 2018 17:42:32 +0200
Subject: [PATCH 5/5] Tweak keybindings on Windows

---
 .../contrib/wordPartOperations/wordPartOperations.ts | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
index 8ad25d28fa860..28829ba7b317e 100644
--- a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
+++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts
@@ -25,7 +25,7 @@ export class DeleteWordPartLeft extends DeleteWordCommand {
 			precondition: EditorContextKeys.writable,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyCode.Backspace,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace }
 			}
 		});
@@ -49,7 +49,7 @@ export class DeleteWordPartRight extends DeleteWordCommand {
 			precondition: EditorContextKeys.writable,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyCode.Delete,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Delete,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Delete }
 			}
 		});
@@ -80,7 +80,7 @@ export class CursorWordPartLeft extends WordPartLeftCommand {
 			precondition: null,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyCode.LeftArrow,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.LeftArrow }
 			}
 		});
@@ -95,7 +95,7 @@ export class CursorWordPartLeftSelect extends WordPartLeftCommand {
 			precondition: null,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow }
 			}
 		});
@@ -116,7 +116,7 @@ export class CursorWordPartRight extends WordPartRightCommand {
 			precondition: null,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyCode.RightArrow,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.RightArrow }
 			}
 		});
@@ -131,7 +131,7 @@ export class CursorWordPartRightSelect extends WordPartRightCommand {
 			precondition: null,
 			kbOpts: {
 				kbExpr: EditorContextKeys.textInputFocus,
-				primary: KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow,
+				primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow,
 				mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow }
 			}
 		});