forked from microsoft/vscode
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request microsoft#107126 from Timmmm/atomic_tabs
Add atomic tabs option
- Loading branch information
Showing
9 changed files
with
613 additions
and
240 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
160 changes: 160 additions & 0 deletions
160
src/vs/editor/common/controller/cursorAtomicMoveOperations.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
import { CharCode } from 'vs/base/common/charCode'; | ||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; | ||
|
||
export const enum Direction { | ||
Left, | ||
Right, | ||
Nearest, | ||
} | ||
|
||
export class AtomicTabMoveOperations { | ||
/** | ||
* Get the visible column at the position. If we get to a non-whitespace character first | ||
* or past the end of string then return -1. | ||
* | ||
* **Note** `position` and the return value are 0-based. | ||
*/ | ||
public static whitespaceVisibleColumn(lineContent: string, position: number, tabSize: number): [number, number, number] { | ||
const lineLength = lineContent.length; | ||
let visibleColumn = 0; | ||
let prevTabStopPosition = -1; | ||
let prevTabStopVisibleColumn = -1; | ||
for (let i = 0; i < lineLength; i++) { | ||
if (i === position) { | ||
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn]; | ||
} | ||
if (visibleColumn % tabSize === 0) { | ||
prevTabStopPosition = i; | ||
prevTabStopVisibleColumn = visibleColumn; | ||
} | ||
const chCode = lineContent.charCodeAt(i); | ||
switch (chCode) { | ||
case CharCode.Space: | ||
visibleColumn += 1; | ||
break; | ||
case CharCode.Tab: | ||
// Skip to the next multiple of tabSize. | ||
visibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize); | ||
break; | ||
default: | ||
return [-1, -1, -1]; | ||
} | ||
} | ||
if (position === lineLength) { | ||
return [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn]; | ||
} | ||
return [-1, -1, -1]; | ||
} | ||
|
||
/** | ||
* Return the position that should result from a move left, right or to the | ||
* nearest tab, if atomic tabs are enabled. Left and right are used for the | ||
* arrow key movements, nearest is used for mouse selection. It returns | ||
* -1 if atomic tabs are not relevant and you should fall back to normal | ||
* behaviour. | ||
* | ||
* **Note**: `position` and the return value are 0-based. | ||
*/ | ||
public static atomicPosition(lineContent: string, position: number, tabSize: number, direction: Direction): number { | ||
const lineLength = lineContent.length; | ||
|
||
// Get the 0-based visible column corresponding to the position, or return | ||
// -1 if it is not in the initial whitespace. | ||
const [prevTabStopPosition, prevTabStopVisibleColumn, visibleColumn] = AtomicTabMoveOperations.whitespaceVisibleColumn(lineContent, position, tabSize); | ||
|
||
if (visibleColumn === -1) { | ||
return -1; | ||
} | ||
|
||
// Is the output left or right of the current position. The case for nearest | ||
// where it is the same as the current position is handled in the switch. | ||
let left: boolean; | ||
switch (direction) { | ||
case Direction.Left: | ||
left = true; | ||
break; | ||
case Direction.Right: | ||
left = false; | ||
break; | ||
case Direction.Nearest: | ||
// The code below assumes the output position is either left or right | ||
// of the input position. If it is the same, return immediately. | ||
if (visibleColumn % tabSize === 0) { | ||
return position; | ||
} | ||
// Go to the nearest indentation. | ||
left = visibleColumn % tabSize <= (tabSize / 2); | ||
break; | ||
} | ||
|
||
// If going left, we can just use the info about the last tab stop position and | ||
// last tab stop visible column that we computed in the first walk over the whitespace. | ||
if (left) { | ||
if (prevTabStopPosition === -1) { | ||
return -1; | ||
} | ||
// If the direction is left, we need to keep scanning right to ensure | ||
// that targetVisibleColumn + tabSize is before non-whitespace. | ||
// This is so that when we press left at the end of a partial | ||
// indentation it only goes one character. For example ' foo' with | ||
// tabSize 4, should jump from position 6 to position 5, not 4. | ||
let currentVisibleColumn = prevTabStopVisibleColumn; | ||
for (let i = prevTabStopPosition; i < lineLength; ++i) { | ||
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) { | ||
// It is a full indentation. | ||
return prevTabStopPosition; | ||
} | ||
|
||
const chCode = lineContent.charCodeAt(i); | ||
switch (chCode) { | ||
case CharCode.Space: | ||
currentVisibleColumn += 1; | ||
break; | ||
case CharCode.Tab: | ||
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize); | ||
break; | ||
default: | ||
return -1; | ||
} | ||
} | ||
if (currentVisibleColumn === prevTabStopVisibleColumn + tabSize) { | ||
return prevTabStopPosition; | ||
} | ||
// It must have been a partial indentation. | ||
return -1; | ||
} | ||
|
||
// We are going right. | ||
const targetVisibleColumn = CursorColumns.nextRenderTabStop(visibleColumn, tabSize); | ||
|
||
// We can just continue from where whitespaceVisibleColumn got to. | ||
let currentVisibleColumn = visibleColumn; | ||
for (let i = position; i < lineLength; i++) { | ||
if (currentVisibleColumn === targetVisibleColumn) { | ||
return i; | ||
} | ||
|
||
const chCode = lineContent.charCodeAt(i); | ||
switch (chCode) { | ||
case CharCode.Space: | ||
currentVisibleColumn += 1; | ||
break; | ||
case CharCode.Tab: | ||
currentVisibleColumn = CursorColumns.nextRenderTabStop(currentVisibleColumn, tabSize); | ||
break; | ||
default: | ||
return -1; | ||
} | ||
} | ||
// This condition handles when the target column is at the end of the line. | ||
if (currentVisibleColumn === targetVisibleColumn) { | ||
return lineLength; | ||
} | ||
return -1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.