Skip to content

Commit

Permalink
Feat implementation leap plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
cuixiaorui committed Jan 8, 2023
1 parent 2d7657e commit 22dafea
Show file tree
Hide file tree
Showing 20 changed files with 804 additions and 2 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ VSCodeVim is a Vim emulator for [Visual Studio Code](https://code.visualstudio.c
- [🖱️ Multi-Cursor Mode](#️-multi-cursor-mode)
- [🔌 Emulated Plugins](#-emulated-plugins)
- [vim-airline](#vim-airline)
- [vim-leap](#vim-leap)
- [vim-easymotion](#vim-easymotion)
- [vim-surround](#vim-surround)
- [vim-commentary](#vim-commentary)
Expand Down Expand Up @@ -521,6 +522,16 @@ Change the color of the status bar based on the current mode. Once enabled, conf
"vim.statusBarColors.easymotioninputmode": "#007ACC",
"vim.statusBarColors.surroundinputmode": "#007ACC",
```
### vim-leap

Based on [vim-leap](https://github.com/ggandor/leap.nvim) and configured through the following settings:

| Setting | Description | Type | Default Value | Value |
| -------------------------- | -------------------------------------------- | ------- | ------------------------------ | ------------------ |
| vim.leap | Enable/disable leap plugin | Boolean | false | |
| vim.leapShowMarkerPosition | Set the position of the marker point display | String | "after" | "after" , "target" |
| vim.leapLabels | The characters used for jump marker name | String | "sklyuiopnm,qwertzxcvbahdgjf;" | |
| vim.leapCaseSensitive | Whether to consider case in search patterns | Boolean | false | |

### vim-easymotion

Expand Down
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,26 @@
"markdownDescription": "Enable the [CamelCaseMotion](https://github.com/bkad/CamelCaseMotion) plugin for Vim.",
"default": false
},
"vim.leap": {
"type": "boolean",
"markdownDescription": "Enable the [Leap](https://github.com/ggandor/leap.nvim) plugin for Vim.",
"default": false
},
"vim.leapShowMarkerPosition": {
"type": "string",
"markdownDescription": "Set the position of the marker point display for Leap markers",
"default": "after"
},
"vim.leapLabels": {
"type": "string",
"markdownDescription": "Set the characters used for jump marker label.",
"default": "sklyuiopnm,qwertzxcvbahdgjf;"
},
"vim.leapCaseSensitive": {
"type": "boolean",
"markdownDescription": "Set to consider case sensitive in search patterns",
"default": false
},
"vim.easymotion": {
"type": "boolean",
"markdownDescription": "Enable the [EasyMotion](https://github.com/easymotion/vim-easymotion) plugin for Vim.",
Expand Down
10 changes: 8 additions & 2 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,11 +902,15 @@ class CommandClearLine extends BaseCommand {

// Don't clash with sneak
public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {
return super.doesActionApply(vimState, keysPressed) && !configuration.sneak;
return (
super.doesActionApply(vimState, keysPressed) && !configuration.sneak && !configuration.leap
);
}

public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {
return super.couldActionApply(vimState, keysPressed) && !configuration.sneak;
return (
super.couldActionApply(vimState, keysPressed) && !configuration.sneak && !configuration.leap
);
}
}

Expand Down Expand Up @@ -2214,6 +2218,7 @@ class ActionChangeChar extends BaseCommand {
return (
super.doesActionApply(vimState, keysPressed) &&
!configuration.sneak &&
!configuration.leap &&
!vimState.recordedState.operator
);
}
Expand All @@ -2222,6 +2227,7 @@ class ActionChangeChar extends BaseCommand {
return (
super.couldActionApply(vimState, keysPressed) &&
!configuration.sneak &&
!configuration.leap &&
!vimState.recordedState.operator
);
}
Expand Down
1 change: 1 addition & 0 deletions src/actions/include-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ import './plugins/sneak';
import './plugins/replaceWithRegister';
import './plugins/surround';
import './plugins/targets/targets';
import './plugins/leap/register';
1 change: 1 addition & 0 deletions src/actions/include-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ import './plugins/sneak';
import './plugins/replaceWithRegister';
import './plugins/surround';
import './plugins/targets/targets';
import './plugins/leap/register';
139 changes: 139 additions & 0 deletions src/actions/plugins/leap/LeapAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { Mode } from '../../../mode/mode';
import { BaseCommand, RegisterAction } from '../../base';
import { Position } from 'vscode';
import { VimState } from '../../../state/vimState';
import { configuration } from '../../../configuration/configuration';
import { LeapSearchDirection, createLeap } from './leap';
import { getMatches, generateMarkerRegex, generatePrepareRegex } from './match';
import { StatusBar } from '../../../statusBar';
import { Marker } from './Marker';
import { VimError, ErrorCode } from '../../../error';

@RegisterAction
export class LeapPrepareAction extends BaseCommand {
modes = [Mode.Normal];
keys = [
['s', '<character>'],
['S', '<character>'],
];

public override doesActionApply(vimState: VimState, keysPressed: string[]) {
return super.doesActionApply(vimState, keysPressed) && configuration.leap;
}

public override async exec(cursorPosition: Position, vimState: VimState): Promise<void> {
if (!configuration.leap) return;

if (this.keysPressed[1] === '\n') {
this.execRepeatLastSearch(vimState);
} else {
await this.execPrepare(cursorPosition, vimState);
}
}

private async execPrepare(cursorPosition: Position, vimState: VimState) {
const direction = this.getDirection();
const firstSearchString = this.keysPressed[1];

const leap = createLeap(vimState, direction, firstSearchString);
vimState.leap = leap;
vimState.leap.previousMode = vimState.currentMode;

const matches = getMatches(
generatePrepareRegex(firstSearchString),
direction,
cursorPosition,
vimState.document
);

vimState.leap.createMarkers(matches);
vimState.leap.showMarkers();
await vimState.setCurrentMode(Mode.LeapPrepareMode);
}

private execRepeatLastSearch(vimState: VimState) {
if (vimState.leap?.leapAction) {
vimState.leap.isRepeatLastSearch = true;
vimState.leap.direction = this.getDirection();
vimState.leap.leapAction.fire();
} else {
StatusBar.displayError(vimState, VimError.fromCode(ErrorCode.LeapNoPreviousSearch));
}
}

private getDirection() {
return this.keysPressed[0] === 's' ? LeapSearchDirection.Backward : LeapSearchDirection.Forward;
}
}

@RegisterAction
export class LeapAction extends BaseCommand {
modes = [Mode.LeapPrepareMode];
keys = ['<character>'];
override isJump = true;
private vimState!: VimState;
private searchString: string = '';
public override async exec(cursorPosition: Position, vimState: VimState): Promise<void> {
if (!configuration.leap) return;
this.vimState = vimState;
this.searchString = vimState.leap.firstSearchString + this.keysPressed[0];
const markers: Marker[] = this.getMarkers(cursorPosition);

if (markers.length === 0) {
await this.handleNoFoundMarkers();
return;
}

// When the leapAction is executed, it needs to be logged
// This is to repeat the last search command
// As long as it is recorded, it means that the search was successfully executed once.
// As long as the search has been executed successfully, it will be ok when we execute "repeat last search".
vimState.leap.leapAction = this;

if (markers.length === 1) {
await this.handleOneMarkers(markers[0]);
return;
}

await this.handleMultipleMarkers();
}
private async handleMultipleMarkers() {
this.vimState.leap.keepMarkersBySearchString(this.searchString);
await this.vimState.setCurrentMode(Mode.LeapMode);
}

private async handleOneMarkers(marker: Marker) {
this.vimState.cursorStopPosition = marker.matchPosition;
this.vimState.leap.cleanupMarkers();
await this.vimState.setCurrentMode(this.vimState.leap.previousMode);
}

private async handleNoFoundMarkers() {
StatusBar.displayError(
this.vimState,
VimError.fromCode(ErrorCode.LeapNoFoundSearchString, this.searchString)
);
this.vimState.leap.cleanupMarkers();
await this.vimState.setCurrentMode(this.vimState.leap.previousMode);
}

private getMarkers(cursorPosition: Position) {
if (this.vimState.leap.isRepeatLastSearch) {
const matches = getMatches(
generateMarkerRegex(this.searchString),
this.vimState.leap.direction!,
cursorPosition,
this.vimState.document
);
this.vimState.leap.createMarkers(matches);
this.vimState.leap.showMarkers();
return this.vimState.leap.markers;
} else {
return this.vimState.leap.findMarkersBySearchString(this.searchString);
}
}

public fire() {
this.exec(this.vimState.cursorStopPosition, this.vimState);
}
}
147 changes: 147 additions & 0 deletions src/actions/plugins/leap/Marker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Match } from './match';
import * as vscode from 'vscode';
import { configuration } from './../../../configuration/configuration';

export class Marker {
private decoration!: MarkerDecoration;
label: string = '';
searchString: string;
matchPosition: vscode.Position;
constructor({ position, searchString }: Match, editor: vscode.TextEditor) {
this.matchPosition = position;
this.searchString = searchString;
this.decoration = new MarkerDecoration(editor, this);
}

get prefix() {
return this.label.length > 1 ? this.label[0] : '';
}

deletePrefix() {
this.label = this.label.slice(-1);
}

update() {
this.show();
}

show() {
this.decoration.show();
}

dispose() {
this.decoration.dispose();
}
}

class MarkerDecoration {
private range!: vscode.Range;
private editor!: vscode.TextEditor;
private marker!: Marker;
private textEditorDecorationType: vscode.TextEditorDecorationType;

private static backgroundColors = ['#ccff88', '#99ccff'];
constructor(editor: vscode.TextEditor, marker: Marker) {
this.editor = editor;
this.marker = marker;
this.textEditorDecorationType = vscode.window.createTextEditorDecorationType({});
this.createRange();
}

private createRange() {
let position = this.marker.matchPosition;
if (configuration.leapShowMarkerPosition === 'after') {
position = new vscode.Position(position.line, position.character + 2);
}
this.range = new vscode.Range(
position.line,
position.character,
position.line,
position.character
);
}

private calcDecorationBackgroundColor() {
const labels = configuration.leapLabels.split('').reverse().join('');

let index = 0;
if (this.marker.prefix) {
const prefixIndex = labels.indexOf(this.marker.prefix);
if (prefixIndex !== -1) {
index = (prefixIndex + 1) % 2 === 0 ? 0 : 1;
}
}

return MarkerDecoration.backgroundColors[index];
}

show() {
this.editor.setDecorations(this.textEditorDecorationType, this.getRangesOrOptions());
}

private getRangesOrOptions() {
const secondCharRenderOptions: vscode.ThemableDecorationInstanceRenderOptions = {
before: {
contentText: this.marker.label,
backgroundColor: this.calcDecorationBackgroundColor(),
color: '#000000',
margin: `0 -1ch 0 0;
position: absolute;
font-weight: normal;`,
height: '100%',
},
};

return [
{
range: this.range,
renderOptions: {
dark: secondCharRenderOptions,
light: secondCharRenderOptions,
},
},
];
}

dispose() {
this.textEditorDecorationType.dispose();
}
}

export function generateMarkerNames(count: number) {
const leapLabels = configuration.leapLabels;
const result = [];

const prefixCount = Math.floor(count / leapLabels.length);
const prefixes = leapLabels
.slice(0 - prefixCount)
.split('')
.reverse()
.join('');

const firstGroupValues = leapLabels.slice(0, leapLabels.length - prefixCount);
const secondGroupValues = leapLabels;

for (let i = 0; i < count; i++) {
let value;
let prefixIndex;
const isFirstGroup = i < firstGroupValues.length;
if (isFirstGroup) {
value = firstGroupValues[i % firstGroupValues.length];
prefixIndex = Math.floor(i / firstGroupValues.length);
} else {
const ii = i - firstGroupValues.length;
value = secondGroupValues[ii % secondGroupValues.length];
prefixIndex = Math.floor(ii / secondGroupValues.length) + 1;
}

const prefixValue = prefixIndex === 0 ? '' : prefixes[prefixIndex - 1];
result.push(prefixValue + value);
}

return result;
}

export function createMarker(match: Match, editor: vscode.TextEditor) {
return new Marker(match, editor);
}
Loading

0 comments on commit 22dafea

Please sign in to comment.