-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2d7657e
commit 22dafea
Showing
20 changed files
with
804 additions
and
2 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
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
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); | ||
} | ||
} |
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,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); | ||
} |
Oops, something went wrong.