-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add CamelCaseMotion plugin #3483
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
Changes from all commits
a4cd568
f55c6f6
2eb7e67
62d4a68
35d0e58
9e991a9
52002c1
f30eb37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| FROM node:8.15 | ||
| FROM node:10.15 | ||
|
|
||
| ARG DEBIAN_FRONTEND=noninteractive | ||
|
|
||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import { TextObjectMovement } from '../textobject'; | ||
| import { RegisterAction } from '../base'; | ||
| import { ModeName } from '../../mode/mode'; | ||
| import { Position } from '../../common/motion/position'; | ||
| import { VimState } from '../../state/vimState'; | ||
| import { IMovement, BaseMovement } from '../motion'; | ||
| import { TextEditor } from '../../textEditor'; | ||
| import { configuration } from '../../configuration/configuration'; | ||
| import { ChangeOperator } from '../operator'; | ||
|
|
||
| class CamelCaseBaseMovement extends BaseMovement { | ||
| public doesActionApply(vimState: VimState, keysPressed: string[]) { | ||
| return configuration.camelCaseMotion.enable && super.doesActionApply(vimState, keysPressed); | ||
| } | ||
|
|
||
| public couldActionApply(vimState: VimState, keysPressed: string[]) { | ||
| return configuration.camelCaseMotion.enable && super.couldActionApply(vimState, keysPressed); | ||
| } | ||
| } | ||
|
|
||
| class CamelCaseTextObjectMovement extends TextObjectMovement { | ||
| public doesActionApply(vimState: VimState, keysPressed: string[]) { | ||
| return configuration.camelCaseMotion.enable && super.doesActionApply(vimState, keysPressed); | ||
| } | ||
|
|
||
| public couldActionApply(vimState: VimState, keysPressed: string[]) { | ||
| return configuration.camelCaseMotion.enable && super.couldActionApply(vimState, keysPressed); | ||
| } | ||
| } | ||
|
|
||
| // based off of `MoveWordBegin` | ||
| @RegisterAction | ||
| class MoveCamelCaseWordBegin extends CamelCaseBaseMovement { | ||
| keys = ['<leader>', 'w']; | ||
|
|
||
| public async execAction(position: Position, vimState: VimState): Promise<Position> { | ||
| if ( | ||
| !configuration.changeWordIncludesWhitespace && | ||
| vimState.recordedState.operator instanceof ChangeOperator | ||
| ) { | ||
| // TODO use execForOperator? Or maybe dont? | ||
|
|
||
| // See note for w | ||
| return position.getCurrentCamelCaseWordEnd().getRight(); | ||
| } else { | ||
| return position.getCamelCaseWordRight(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // based off of `MoveWordEnd` | ||
| @RegisterAction | ||
| class MoveCamelCaseWordEnd extends CamelCaseBaseMovement { | ||
| keys = ['<leader>', 'e']; | ||
|
|
||
| public async execAction(position: Position, vimState: VimState): Promise<Position> { | ||
| return position.getCurrentCamelCaseWordEnd(); | ||
| } | ||
|
|
||
| public async execActionForOperator(position: Position, vimState: VimState): Promise<Position> { | ||
| let end = position.getCurrentCamelCaseWordEnd(); | ||
|
|
||
| return new Position(end.line, end.character + 1); | ||
| } | ||
| } | ||
|
|
||
| // based off of `MoveBeginningWord` | ||
| @RegisterAction | ||
| class MoveBeginningCamelCaseWord extends CamelCaseBaseMovement { | ||
| keys = ['<leader>', 'b']; | ||
|
|
||
| public async execAction(position: Position, vimState: VimState): Promise<Position> { | ||
| return position.getCamelCaseWordLeft(); | ||
| } | ||
| } | ||
|
|
||
| // based off of `SelectInnerWord` | ||
| @RegisterAction | ||
| export class SelectInnerCamelCaseWord extends CamelCaseTextObjectMovement { | ||
| modes = [ModeName.Normal, ModeName.Visual]; | ||
| keys = ['i', '<leader>', 'w']; | ||
|
|
||
| public async execAction(position: Position, vimState: VimState): Promise<IMovement> { | ||
| let start: Position; | ||
| let stop: Position; | ||
| const currentChar = TextEditor.getLineAt(position).text[position.character]; | ||
|
|
||
| if (/\s/.test(currentChar)) { | ||
| start = position.getLastCamelCaseWordEnd().getRight(); | ||
| stop = position.getCamelCaseWordRight().getLeftThroughLineBreaks(); | ||
| } else { | ||
| start = position.getCamelCaseWordLeft(true); | ||
| stop = position.getCurrentCamelCaseWordEnd(true); | ||
| } | ||
|
|
||
| if ( | ||
| vimState.currentMode === ModeName.Visual && | ||
| !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition) | ||
| ) { | ||
| start = vimState.cursorStartPosition; | ||
|
|
||
| if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) { | ||
| // If current cursor postion is before cursor start position, we are selecting words in reverser order. | ||
| if (/\s/.test(currentChar)) { | ||
| stop = position.getLastCamelCaseWordEnd().getRight(); | ||
| } else { | ||
| stop = position.getCamelCaseWordLeft(true); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| start: start, | ||
| stop: stop, | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,6 +101,7 @@ export class Position extends vscode.Position { | |
|
|
||
| private _nonWordCharRegex: RegExp; | ||
| private _nonBigWordCharRegex: RegExp; | ||
| private _nonCamelCaseWordCharRegex: RegExp; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we should move away from having As the changes you introduced to this file are specific to camel case, I wonder if we should create a new file under /plugins What do you think?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not opposed to the idea in principle, but if I were to move I'd either have to duplicate or make public all the |
||
| private _sentenceEndRegex: RegExp; | ||
| private _nonFileNameRegex: RegExp; | ||
|
|
||
|
|
@@ -109,6 +110,7 @@ export class Position extends vscode.Position { | |
|
|
||
| this._nonWordCharRegex = this.makeWordRegex(Position.NonWordCharacters); | ||
| this._nonBigWordCharRegex = this.makeWordRegex(Position.NonBigWordCharacters); | ||
| this._nonCamelCaseWordCharRegex = this.makeCamelCaseWordRegex(Position.NonWordCharacters); | ||
| this._sentenceEndRegex = /[\.!\?]{1}([ \n\t]+|$)/g; | ||
| this._nonFileNameRegex = this.makeWordRegex(Position.NonFileCharacters); | ||
| } | ||
|
|
@@ -516,6 +518,10 @@ export class Position extends vscode.Position { | |
| return this.getWordLeftWithRegex(this._nonBigWordCharRegex, inclusive); | ||
| } | ||
|
|
||
| public getCamelCaseWordLeft(inclusive: boolean = false): Position { | ||
| return this.getWordLeftWithRegex(this._nonCamelCaseWordCharRegex, inclusive); | ||
| } | ||
|
|
||
| public getFilePathLeft(inclusive: boolean = false): Position { | ||
| return this.getWordLeftWithRegex(this._nonFileNameRegex, inclusive); | ||
| } | ||
|
|
@@ -531,6 +537,10 @@ export class Position extends vscode.Position { | |
| return this.getWordRightWithRegex(this._nonBigWordCharRegex); | ||
| } | ||
|
|
||
| public getCamelCaseWordRight(inclusive: boolean = false): Position { | ||
| return this.getWordRightWithRegex(this._nonCamelCaseWordCharRegex); | ||
| } | ||
|
|
||
| public getFilePathRight(inclusive: boolean = false): Position { | ||
| return this.getWordRightWithRegex(this._nonFileNameRegex, inclusive); | ||
| } | ||
|
|
@@ -543,6 +553,10 @@ export class Position extends vscode.Position { | |
| return this.getLastWordEndWithRegex(this._nonBigWordCharRegex); | ||
| } | ||
|
|
||
| public getLastCamelCaseWordEnd(): Position { | ||
| return this.getLastWordEndWithRegex(this._nonCamelCaseWordCharRegex); | ||
| } | ||
|
|
||
| /** | ||
| * Inclusive is true if we consider the current position a valid result, false otherwise. | ||
| */ | ||
|
|
@@ -557,6 +571,13 @@ export class Position extends vscode.Position { | |
| return this.getCurrentWordEndWithRegex(this._nonBigWordCharRegex, inclusive); | ||
| } | ||
|
|
||
| /** | ||
| * Inclusive is true if we consider the current position a valid result, false otherwise. | ||
| */ | ||
| public getCurrentCamelCaseWordEnd(inclusive: boolean = false): Position { | ||
| return this.getCurrentWordEndWithRegex(this._nonCamelCaseWordCharRegex, inclusive); | ||
| } | ||
|
|
||
| /** | ||
| * Get the boundary position of the section. | ||
| */ | ||
|
|
@@ -831,6 +852,39 @@ export class Position extends vscode.Position { | |
| return result; | ||
| } | ||
|
|
||
| private makeCamelCaseWordRegex(characterSet: string): RegExp { | ||
| const escaped = characterSet && _.escapeRegExp(characterSet).replace(/-/g, '\\-'); | ||
| const segments: string[] = []; | ||
|
|
||
| // prettier-ignore | ||
| const firstSegment = | ||
| '(' + // OPEN: group for matching camel case words | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ermagod. Thank you for writing comments. |
||
| `[^\\s${escaped}]` + // words can start with any word character | ||
| '(?:' + // OPEN: group for characters after initial char | ||
| `(?:(?<=[A-Z_])[A-Z](?=[\\sA-Z0-9${escaped}_]))+` + // If first char was a capital | ||
| // the word can continue with all caps | ||
| '|' + // OR | ||
| `(?:(?<=[0-9_])[0-9](?=[\\sA-Z0-9${escaped}_]))+` + // If first char was a digit | ||
| // the word can continue with all digits | ||
| '|' + // OR | ||
| `(?:(?<=[_])[_](?=[\\s${escaped}_]))+` + // Continue with all underscores | ||
| '|' + // OR | ||
| `[^\\sA-Z0-9${escaped}_]*` + // Continue with regular characters | ||
| ')' + // END: group for characters after initial char | ||
| ')' + // END: group for matching camel case words | ||
| ''; | ||
|
|
||
| segments.push(firstSegment); | ||
| segments.push(`[${escaped}]+`); | ||
| segments.push(`$^`); | ||
|
|
||
| // it can be difficult to grok the behavior of the above regex | ||
| // feel free to check out https://regex101.com/r/mkVeiH/1 as a live example | ||
| const result = new RegExp(segments.join('|'), 'g'); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private getAllPositions(line: string, regex: RegExp): number[] { | ||
| let positions: number[] = []; | ||
| let result = regex.exec(line); | ||
|
|
@@ -987,7 +1041,7 @@ export class Position extends vscode.Position { | |
| .getRightThroughLineBreaks() | ||
| .compareTo(this); | ||
|
|
||
| return (newPositionBeforeThis && (index < this.character || currentLine < this.line)); | ||
| return newPositionBeforeThis && (index < this.character || currentLine < this.line); | ||
| }); | ||
|
|
||
| if (newCharacter !== undefined) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.