Skip to content

Commit

Permalink
Merge pull request #63 from aioutecism/feature/ci
Browse files Browse the repository at this point in the history
Support basic text objects
  • Loading branch information
aioutecism committed Apr 23, 2016
2 parents 82f7dda + 9219f44 commit 4d83fcb
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 39 deletions.
46 changes: 44 additions & 2 deletions src/Actions/Delete.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {window, commands, Range} from 'vscode';
import {window, commands, Range, Selection} from 'vscode';
import {PrototypeReflect} from '../LanguageExtensions/PrototypeReflect';
import {SymbolMetadata} from '../Symbols/Metadata';
import {ActionRegister} from './Register';
import {ActionReveal} from './Reveal';
import {Motion} from '../Motions/Motion';
import {TextObject} from '../TextObjects/TextObject';
import {UtilRange} from '../Utils/Range';

export class ActionDelete {
Expand Down Expand Up @@ -48,6 +49,47 @@ export class ActionDelete {
.then(() => ActionReveal.primaryCursor());
}

@PrototypeReflect.metadata(SymbolMetadata.Action.isChange, true)
static byTextObject(args: {
textObject: TextObject,
shouldYank?: boolean
}): Thenable<boolean> {
args.shouldYank = args.shouldYank === undefined ? false : args.shouldYank;

const activeTextEditor = window.activeTextEditor;

if (! activeTextEditor) {
return Promise.resolve(false);
}

let ranges: Range[] = [];

activeTextEditor.selections.forEach(selection => {
const match = args.textObject.apply(selection.active);
if (match) {
ranges.push(match);
}
});

ranges = UtilRange.unionOverlaps(ranges);

if (ranges.length === 0) {
return Promise.reject<boolean>(false);
}
else {
// Selections will be adjust to matched ranges' start.
activeTextEditor.selections = ranges.map(range => new Selection(range.start, range.start));
}

return (args.shouldYank ? ActionRegister.yankRanges(ranges) : Promise.resolve(true))
.then(() => {
return activeTextEditor.edit((editBuilder) => {
ranges.forEach((range) => editBuilder.delete(range));
});
})
.then(() => ActionReveal.primaryCursor());
}

@PrototypeReflect.metadata(SymbolMetadata.Action.isChange, true)
static selectionsOrLeft(args: {
isMultiLine?: boolean,
Expand Down Expand Up @@ -185,7 +227,7 @@ export class ActionDelete {
const document = activeTextEditor.document;

let ranges = activeTextEditor.selections.map(selection => {
return UtilRange.fitIntoDocument(document, new Range(
return document.validateRange(new Range(
selection.start.line, 0,
selection.end.line + 1, 0
));
Expand Down
24 changes: 23 additions & 1 deletion src/Actions/Register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {ActionSelection} from './Selection';
import {Motion} from '../Motions/Motion';
import {MotionCharacter} from '../Motions/Character';
import {MotionLine} from '../Motions/Line';
import {TextObject} from '../TextObjects/TextObject';
import {UtilRange} from '../Utils/Range';

enum PutDirection {Before, After};
Expand All @@ -25,7 +26,7 @@ export class ActionRegister {
const document = activeTextEditor.document;

ActionRegister.stash = ranges.map(range => {
return document.getText(UtilRange.fitIntoDocument(document, range));
return document.getText(document.validateRange(range));
}).join('');

return Promise.resolve(true);
Expand Down Expand Up @@ -55,6 +56,27 @@ export class ActionRegister {
return ActionRegister.yankRanges(ranges);
}

static yankByTextObject(args: {textObject: TextObject}): Thenable<boolean> {
const activeTextEditor = window.activeTextEditor;

if (! activeTextEditor) {
return Promise.resolve(false);
}

let ranges: Range[] = [];

activeTextEditor.selections.forEach(selection => {
const match = args.textObject.apply(selection.active);
if (match) {
ranges.push(match);
}
});

ranges = UtilRange.unionOverlaps(ranges);

return ActionRegister.yankRanges(ranges);
}

static yankSelections(): Thenable<boolean> {
const activeTextEditor = window.activeTextEditor;

Expand Down
2 changes: 2 additions & 0 deletions src/Mappers/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {GenericMapper, GenericMap, MatchResult, MatchResultKind} from './Generic
import {SpecialKeyN} from './SpecialKeys/N';
import {SpecialKeyChar} from './SpecialKeys/Char';
import {SpecialKeyMotion} from './SpecialKeys/Motion';
import {SpecialKeyTextObject} from './SpecialKeys/TextObject';

export interface CommandMatchResult extends MatchResult {
kind: MatchResultKind;
Expand All @@ -20,6 +21,7 @@ export class CommandMapper extends GenericMapper {
super([
new SpecialKeyN(),
new SpecialKeyMotion(),
new SpecialKeyTextObject(),
new SpecialKeyChar(),
]);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Mappers/SpecialKeys/N.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class SpecialKeyN implements SpecialKeyCommon {

indicator = '{N}';

private conflictRegExp = /^[1-9]|\{motion\}|\{char\}$/;
private conflictRegExp = /^[1-9]|\{motion\}|\{textObject\}|\{char\}$/;

unmapConflicts(node: RecursiveMap, keyToMap: string): void {
if (keyToMap === this.indicator) {
Expand Down
115 changes: 115 additions & 0 deletions src/Mappers/SpecialKeys/TextObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {GenericMapper, GenericMap, RecursiveMap, MatchResultKind} from '../Generic';
import {SpecialKeyCommon, SpecialKeyMatchResult} from './Common';
import {TextObject} from '../../TextObjects/TextObject';
import {TextObjectBlock} from '../../TextObjects/Block';
import {TextObjectQuotedString} from '../../TextObjects/QuotedString';

interface TextObjectGenerator {
(args?: {}): TextObject;
}

interface TextObjectMap extends GenericMap {
textObjectGenerator: TextObjectGenerator;
}

interface TextObjectMapInfo {
characters: string[];
method: (args: {isInclusive: boolean}) => TextObject;
}

export class SpecialKeyTextObject extends GenericMapper implements SpecialKeyCommon {

indicator = '{textObject}';

private conflictRegExp = /^[1-9]|\{N\}|\{char\}$/;

private mapInfos: TextObjectMapInfo[] = [
{
characters: ['b', '(', ')'],
method: TextObjectBlock.byParentheses,
},
{
characters: ['[', ']'],
method: TextObjectBlock.byBrackets,
},
{
characters: ['B', '{', '}'],
method: TextObjectBlock.byBraces,
},
{
characters: ['<', '>'],
method: TextObjectBlock.byChevrons,
},
{
characters: ['\''],
method: TextObjectQuotedString.bySingle,
},
{
characters: ['"'],
method: TextObjectQuotedString.byDouble,
},
{
characters: ['`'],
method: TextObjectQuotedString.byBackward,
},
];

private maps: TextObjectMap[] = [
// Reserved for special maps.
];

constructor() {
super();

this.mapInfos.forEach(mapInfo => {
mapInfo.characters.forEach(character => {
this.map(`a ${character}`, mapInfo.method, { isInclusive: true });
this.map(`i ${character}`, mapInfo.method, { isInclusive: false });
});
});

this.maps.forEach(map => {
this.map(map.keys, map.textObjectGenerator, map.args);
});
}

map(joinedKeys: string, textObjectGenerator: TextObjectGenerator, args?: {}): void {
const map = super.map(joinedKeys, args);
(map as TextObjectMap).textObjectGenerator = textObjectGenerator;
}

unmapConflicts(node: RecursiveMap, keyToMap: string): void {
if (keyToMap === this.indicator) {
Object.getOwnPropertyNames(node).forEach(key => {
this.conflictRegExp.test(key) && delete node[key];
});
}

if (this.conflictRegExp.test(keyToMap)) {
delete node[this.indicator];
}

// This class has lower priority than other keys.
}

matchSpecial(inputs: string[]): SpecialKeyMatchResult {
const {kind, map} = this.match(inputs);

if (kind === MatchResultKind.FAILED) {
return null;
}

let additionalArgs: {textObject?: TextObject} = {};
if (map) {
additionalArgs.textObject = (map as TextObjectMap).textObjectGenerator(map.args);
}

return {
specialKey: this,
kind,
matchedCount: inputs.length,
additionalArgs
};
}

}
5 changes: 4 additions & 1 deletion src/Modes/Mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ export abstract class Mode {

promise.then(
one.bind(this),
this.clearPendings.bind(this)
() => {
this.clearPendings();
this.executing = false;
}
);
};

Expand Down
9 changes: 8 additions & 1 deletion src/Modes/Normal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {ActionSelection} from '../Actions/Selection';
import {ActionHistory} from '../Actions/History';
import {ActionIndent} from '../Actions/Indent';
import {ActionMode} from '../Actions/Mode';
import {Motion} from '../Motions/Motion';
import {MotionCharacter} from '../Motions/Character';
import {MotionLine} from '../Motions/Line';

Expand Down Expand Up @@ -70,6 +69,7 @@ export class ModeNormal extends Mode {
{ keys: 'd d', actions: [ActionDelete.line], args: {shouldYank: true} },
{ keys: 'D', actions: [ActionDelete.byMotions], args: {motions: [MotionLine.end()], shouldYank: true} },
{ keys: 'd {motion}', actions: [ActionDelete.byMotions], args: {shouldYank: true} },
{ keys: 'd {textObject}', actions: [ActionDelete.byTextObject], args: {shouldYank: true} },
{ keys: 'C', actions: [
ActionDelete.byMotions,
ActionMode.toInsert,
Expand Down Expand Up @@ -100,13 +100,20 @@ export class ModeNormal extends Mode {
shouldYank: true,
cwNeedsFixup: true,
} },
{ keys: 'c {textObject}', actions: [
ActionDelete.byTextObject,
ActionMode.toInsert,
], args: {
shouldYank: true,
} },
{ keys: 'J', actions: [ActionJoinLines.onSelections] },

{ keys: 'r {char}', actions: [ActionReplace.characters] },

{ keys: 'y y', actions: [ActionRegister.yankLines] },
{ keys: 'Y', actions: [ActionRegister.yankLines] },
{ keys: 'y {motion}', actions: [ActionRegister.yankByMotions] },
{ keys: 'y {textObject}', actions: [ActionRegister.yankByTextObject] },
{ keys: 'p', actions: [ActionRegister.putAfter] },
{ keys: 'P', actions: [ActionRegister.putBefore] },

Expand Down
4 changes: 2 additions & 2 deletions src/Motions/Motion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {window, Position, Range} from 'vscode';

export class Motion {
export abstract class Motion {

isCharacterUpdated = true;

Expand All @@ -12,7 +12,7 @@ export class Motion {
this.characterDelta += characterDelta;
}

apply(from: Position, option?): Position {
apply(from: Position, option?: any): Position {
const activeTextEditor = window.activeTextEditor;

if (! activeTextEditor) {
Expand Down
Loading

0 comments on commit 4d83fcb

Please sign in to comment.