diff --git a/src/cmd_line/commands/let.ts b/src/cmd_line/commands/let.ts index 623199a0f76..7c5781ec961 100644 --- a/src/cmd_line/commands/let.ts +++ b/src/cmd_line/commands/let.ts @@ -1,6 +1,8 @@ // eslint-disable-next-line id-denylist import { alt, optWhitespace, Parser, seq, string, whitespace } from 'parsimmon'; import { env } from 'process'; +import { ErrorCode, VimError } from '../../error'; +import { Register, RegisterMode } from '../../register/register'; import { VimState } from '../../state/vimState'; import { StatusBar } from '../../statusBar'; import { ExCommand } from '../../vimscript/exCommand'; @@ -13,7 +15,8 @@ import { str, subtract, } from '../../vimscript/expression/build'; -import { EvaluationContext } from '../../vimscript/expression/evaluate'; +import { displayValue } from '../../vimscript/expression/displayValue'; +import { EvaluationContext, toString } from '../../vimscript/expression/evaluate'; import { envVariableParser, expressionParser, @@ -21,6 +24,7 @@ import { registerParser, variableParser, } from '../../vimscript/expression/parser'; +import { stringToRegisterMode } from '../../vimscript/expression/registerUtils'; import { EnvVariableExpression, Expression, @@ -28,8 +32,6 @@ import { RegisterExpression, VariableExpression, } from '../../vimscript/expression/types'; -import { displayValue } from '../../vimscript/expression/displayValue'; -import { ErrorCode, VimError } from '../../error'; export type LetCommandOperation = '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '.=' | '..='; export type LetCommandVariable = @@ -143,7 +145,23 @@ export class LetCommand extends ExCommand { } context.setVariable(variable, value, this.args.lock); } else if (variable.type === 'register') { - // TODO + if (this.args.operation !== '=') { + throw VimError.fromCode(ErrorCode.InvalidOperationForRegister, this.args.operation); + } + let registerIndex = 0; + const items = value.type === 'list' ? value.items : [value]; + for (const item of items) { + const registerMode = + item.type === 'register_val' + ? stringToRegisterMode(item.registerMode) + : RegisterMode.CharacterWise; + Register.put(vimState, toString(value), registerIndex, /* copyToUnamed */ false, { + registerName: variable.name, + registerMode, + forceOverwrite: true, + }); + registerIndex++; + } } else if (variable.type === 'option') { // TODO } else if (variable.type === 'env_variable') { diff --git a/src/error.ts b/src/error.ts index 45649f8525c..6bcb4dbe2b4 100644 --- a/src/error.ts +++ b/src/error.ts @@ -81,6 +81,10 @@ export enum ErrorCode { UsingABlobAsANumber = 974, CannotModifyExistingVariable = 995, CannotLockARegister = 996, + CannotAccessClipboardRegister = 997, + UsingARegisterAsANumber = 998, + CannotAccessRecordedStateRegister = 999, + InvalidOperationForRegister = 1000, } export const ErrorMessage: IErrorMessage = { @@ -162,6 +166,10 @@ export const ErrorMessage: IErrorMessage = { 974: 'Using a Blob as a Number', 995: 'Cannot modify existing variable', 996: 'Cannot lock a register', + 997: 'Cannot access clipboard register', + 998: 'Using a register as a Number', + 999: 'Cannot access recorded state register', + 1000: 'Invalid operation for register', }; export class VimError extends Error { diff --git a/src/register/register.ts b/src/register/register.ts index 6ae5f9c0447..74b883d2e59 100644 --- a/src/register/register.ts +++ b/src/register/register.ts @@ -55,9 +55,10 @@ export class Register { vimState: VimState, content: RegisterContent, multicursorIndex?: number, - copyToUnnamed?: boolean, + copyToUnnamed?: boolean, // TODO: This should be a part of the options object + options: { registerName?: string; registerMode?: RegisterMode; forceOverwrite?: boolean } = {}, ): void { - const register = vimState.recordedState.registerName; + const register = options.registerName || vimState.recordedState.registerName; if (!Register.isValidRegister(register)) { throw new Error(`Invalid register ${register}`); @@ -67,10 +68,22 @@ export class Register { return; } - if (Register.isValidUppercaseRegister(register)) { - Register.appendToRegister(vimState, register.toLowerCase(), content, multicursorIndex ?? 0); + if (Register.isValidUppercaseRegister(register) && !options.forceOverwrite) { + Register.appendToRegister( + vimState, + Register.decapitalize(register), + content, + multicursorIndex ?? 0, + options.registerMode, + ); } else { - Register.overwriteRegister(vimState, register, content, multicursorIndex ?? 0); + Register.overwriteRegister( + vimState, + Register.decapitalize(register), + content, + multicursorIndex ?? 0, + options.registerMode, + ); } if (copyToUnnamed && register !== '"') { @@ -78,6 +91,10 @@ export class Register { } } + private static decapitalize(register: string): string { + return Register.isValidUppercaseRegister(register) ? register.toLowerCase() : register; + } + public static isValidRegister(register: string): boolean { return ( Register.isValidLowercaseRegister(register) || @@ -95,7 +112,7 @@ export class Register { return registerName === '_'; } - private static isClipboardRegister(registerName: string): boolean { + public static isClipboardRegister(registerName: string): boolean { return registerName === '*' || registerName === '+'; } @@ -120,13 +137,14 @@ export class Register { register: string, content: RegisterContent, multicursorIndex: number, + registerMode?: RegisterMode, ): void { if (multicursorIndex === 0 || !Register.registers.has(register)) { Register.registers.set(register, []); } Register.registers.get(register)![multicursorIndex] = { - registerMode: vimState.currentRegisterMode, + registerMode: registerMode || vimState.currentRegisterMode, text: content, }; @@ -149,6 +167,7 @@ export class Register { register: string, content: RegisterContent, multicursorIndex: number, + registerMode?: RegisterMode, ): void { if (!Register.registers.has(register)) { Register.registers.set(register, []); @@ -162,11 +181,13 @@ export class Register { text: content, }; } else { - // Line-wise trumps other RegisterModes - const registerMode = - vimState.currentRegisterMode === RegisterMode.LineWise - ? RegisterMode.LineWise - : oldContent.registerMode; + if (!registerMode) { + // Line-wise trumps other RegisterModes + registerMode = + vimState.currentRegisterMode === RegisterMode.LineWise + ? RegisterMode.LineWise + : oldContent.registerMode; + } let newText: RegisterContent; if (oldContent.text instanceof RecordedState || content instanceof RecordedState) { newText = oldContent.text; @@ -271,13 +292,7 @@ export class Register { register: string, multicursorIndex = 0, ): Promise { - if (!Register.isValidRegister(register)) { - throw new Error(`Invalid register ${register}`); - } - - register = register.toLowerCase(); - - const contentByCursor = Register.registers.get(register); + const contentByCursor = Register.getRegisterArray(register); if (Register.isClipboardRegister(register)) { const clipboardContent = (await Clipboard.Paste()).replace(/\r\n/g, '\n'); @@ -302,6 +317,13 @@ export class Register { return contentByCursor?.[multicursorIndex]; } + public static getRegisterArray(register: string): IRegisterContent[] | undefined { + if (!Register.isValidRegister(register)) { + throw new Error(`Invalid register ${register}`); + } + return Register.registers.get(register.toLowerCase()); + } + public static has(register: string): boolean { return Register.registers.has(register); } diff --git a/src/vimscript/expression/build.ts b/src/vimscript/expression/build.ts index 7f3cd84c1f5..8ceff3971f6 100644 --- a/src/vimscript/expression/build.ts +++ b/src/vimscript/expression/build.ts @@ -1,21 +1,22 @@ import { - NumberValue, - Expression, - ListExpression, - UnaryExpression, - BinaryOp, BinaryExpression, - FunctionCallExpression, - StringValue, - LambdaExpression, - VariableExpression, - Namespace, + BinaryOp, + BlobValue, + DictionaryValue, + Expression, FloatValue, FuncRefValue, + FunctionCallExpression, + LambdaExpression, + ListExpression, ListValue, - DictionaryValue, + Namespace, + NumberValue, + RegisterValue, + StringValue, + UnaryExpression, Value, - BlobValue, + VariableExpression, } from './types'; export function int(value: number): NumberValue { @@ -66,6 +67,17 @@ export function blob(data: ArrayBuffer): BlobValue { }; } +export function register( + content: string, + registerMode: 'character' | 'line' | 'block', +): RegisterValue { + return { + type: 'register_val', + content, + registerMode, + }; +} + export function listExpr(items: Expression[]): ListExpression { return { type: 'list', diff --git a/src/vimscript/expression/displayValue.ts b/src/vimscript/expression/displayValue.ts index 3df94435645..5450aacf8d0 100644 --- a/src/vimscript/expression/displayValue.ts +++ b/src/vimscript/expression/displayValue.ts @@ -42,5 +42,7 @@ export function displayValue(value: Value, topLevel = true): string { .join('') .toUpperCase() ); + case 'register_val': + return `register('${value.content}', ${value.registerMode})`; } } diff --git a/src/vimscript/expression/evaluate.ts b/src/vimscript/expression/evaluate.ts index 73fbb340f4f..157dc61b642 100644 --- a/src/vimscript/expression/evaluate.ts +++ b/src/vimscript/expression/evaluate.ts @@ -1,10 +1,11 @@ import { all } from 'parsimmon'; -import { displayValue } from './displayValue'; import { configuration } from '../../configuration/configuration'; import { ErrorCode, VimError } from '../../error'; import { globalState } from '../../state/globalState'; -import { bool, float, funcref, listExpr, int, str, list, funcCall, blob } from './build'; +import { blob, bool, float, funcCall, funcref, int, list, listExpr, str } from './build'; +import { displayValue } from './displayValue'; import { expressionParser, numberParser } from './parser'; +import { lookupRegister } from './registerUtils'; import { BinaryOp, ComparisonOp, @@ -43,6 +44,8 @@ function toInt(value: Value): number { throw VimError.fromCode(ErrorCode.UsingAFuncrefAsANumber); case 'blob': throw VimError.fromCode(ErrorCode.UsingABlobAsANumber); + case 'register_val': + throw VimError.fromCode(ErrorCode.UsingARegisterAsANumber); } } @@ -57,6 +60,7 @@ function toFloat(value: Value): number { case 'dict_val': case 'funcref': case 'blob': + case 'register_val': throw VimError.fromCode(ErrorCode.NumberOrFloatRequired); } } @@ -77,6 +81,8 @@ export function toString(value: Value): string { throw VimError.fromCode(ErrorCode.UsingFuncrefAsAString); case 'blob': return displayValue(value); + case 'register_val': + return value.content; } } @@ -88,6 +94,7 @@ function toList(value: Value): ListValue { case 'funcref': case 'dict_val': case 'blob': + case 'register_val': throw VimError.fromCode(ErrorCode.ListRequired); case 'list': return value; @@ -102,6 +109,7 @@ function toDict(value: Value): DictionaryValue { case 'list': case 'funcref': case 'blob': + case 'register_val': throw VimError.fromCode(ErrorCode.DictionaryRequired); case 'dict_val': return value; @@ -147,6 +155,7 @@ export class EvaluationContext { case 'dict_val': case 'funcref': case 'blob': + case 'register_val': return expression; case 'list': return list(expression.items.map((x) => this.evaluate(x))); @@ -168,7 +177,7 @@ export class EvaluationContext { case 'variable': return this.evaluateVariable(expression); case 'register': - return str(''); // TODO + return lookupRegister(expression.name); case 'option': return str(''); // TODO case 'env_variable': @@ -395,6 +404,9 @@ export class EvaluationContext { const bytes = new Uint8Array(sequence.data); return int(bytes[toInt(index)]); } + case 'register_val': { + return this.evaluateIndex(str(toString(sequence)), index); + } } } @@ -438,6 +450,9 @@ export class EvaluationContext { case 'blob': { return blob(new Uint8Array(sequence.data).slice(_start, _end + 1)); } + case 'register_val': { + return this.evaluateSlice(str(toString(sequence)), start, end); + } } } @@ -1281,6 +1296,8 @@ export class EvaluationContext { // return int(7); case 'blob': return int(8); + case 'register_val': + return int(9); default: const guard: never = x; throw new Error('type() got unexpected type'); diff --git a/src/vimscript/expression/registerUtils.ts b/src/vimscript/expression/registerUtils.ts new file mode 100644 index 00000000000..50b0b9b7362 --- /dev/null +++ b/src/vimscript/expression/registerUtils.ts @@ -0,0 +1,47 @@ +import { ErrorCode, VimError } from '../../error'; +import { Register, RegisterMode } from '../../register/register'; +import { list, register } from './build'; +import { Value } from './types'; + +export function lookupRegister(registerName: string): Value { + if (Register.isClipboardRegister(registerName)) { + // Reading from the clipboard register is async, so for now is not supported + throw VimError.fromCode(ErrorCode.CannotAccessClipboardRegister, registerName); + } + const registerArray = Register.getRegisterArray(registerName); + if (registerArray === undefined || registerArray.length === 0) { + throw VimError.fromCode(ErrorCode.NothingInRegister, registerName); + } + const values = registerArray.map((val) => { + if (typeof val.text !== 'string') { + throw VimError.fromCode(ErrorCode.CannotAccessRecordedStateRegister, registerName); + } + return register(val.text, registerModeToString(val.registerMode)); + }); + if (values.length === 1) { + return values[0]; + } + return list(values); +} + +function registerModeToString(mode: RegisterMode): 'character' | 'line' | 'block' { + switch (mode) { + case RegisterMode.CharacterWise: + return 'character'; + case RegisterMode.LineWise: + return 'line'; + case RegisterMode.BlockWise: + return 'block'; + } +} + +export function stringToRegisterMode(mode: 'character' | 'line' | 'block'): RegisterMode { + switch (mode) { + case 'character': + return RegisterMode.CharacterWise; + case 'line': + return RegisterMode.LineWise; + case 'block': + return RegisterMode.BlockWise; + } +} diff --git a/src/vimscript/expression/types.ts b/src/vimscript/expression/types.ts index daaaa5f373d..f7e8dfc9c50 100644 --- a/src/vimscript/expression/types.ts +++ b/src/vimscript/expression/types.ts @@ -38,6 +38,12 @@ export type BlobValue = { data: ArrayBuffer; }; +export type RegisterValue = { + type: 'register_val'; + content: string; + registerMode: 'character' | 'line' | 'block'; +}; + export type Value = | NumberValue | FloatValue @@ -45,7 +51,8 @@ export type Value = | ListValue | DictionaryValue | FuncRefValue - | BlobValue; + | BlobValue + | RegisterValue; // -------------------- Expressions --------------------