Skip to content
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

2611 paredit delete backward races sync registration #2675

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changes to Calva.

## [Unreleased]

- Internally to Paredit, enabled commands to run synchronously, in preparation for fixing [Paredit garbles while backspacing rapidly](https://github.com/BetterThanTomorrow/calva/issues/2611)

## [2.0.482] - 2024-12-03

- Fix: [Added 'replace-refer-all-with-alias' & 'replace-refer-all-with-refer' actions to calva.](https://github.com/BetterThanTomorrow/calva/issues/2667)
Expand Down
128 changes: 83 additions & 45 deletions src/paredit/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ExtensionContext,
workspace,
ConfigurationChangeEvent,
TextEditorEdit,
} from 'vscode';
import * as paredit from '../cursor-doc/paredit';
import * as handlers from './commands';
Expand Down Expand Up @@ -45,13 +46,20 @@ function multiCursorEnabled(override?: boolean): boolean {
return override ?? workspace.getConfiguration().get('calva.paredit.multicursor');
}

// PareditCommand is for handlers that will not do anything async.
type PareditCommand = {
command: string;
handler: (doc: EditableDocument, arg?: any) => void | Promise<any> | Thenable<any>;
handler: (doc: EditableDocument, arg?: any, builder?: TextEditorEdit) => void;
};
// PareditAsyncCommand is for handlers that may do something async, e.g., TextEditor.edit
type PareditAsyncCommand = {
command: string;
asyncHandler: (doc: EditableDocument, arg?: any) => Promise<any> | Thenable<any>;
};

// only grab the custom, additional args after the first doc arg from the given command's handler
type CommandArgOf<C extends PareditCommand> = Parameters<C['handler']>[1];
type AsyncCommandArgOf<C extends PareditAsyncCommand> = Parameters<C['asyncHandler']>[1];

const pareditCommands = [
// NAVIGATING
Expand Down Expand Up @@ -237,72 +245,72 @@ const pareditCommands = [
// EDITING
{
command: 'paredit.slurpSexpForward',
handler: paredit.forwardSlurpSexp,
asyncHandler: paredit.forwardSlurpSexp,
},
{
command: 'paredit.barfSexpForward',
handler: paredit.forwardBarfSexp,
asyncHandler: paredit.forwardBarfSexp,
},
{
command: 'paredit.slurpSexpBackward',
handler: paredit.backwardSlurpSexp,
asyncHandler: paredit.backwardSlurpSexp,
},
{
command: 'paredit.barfSexpBackward',
handler: paredit.backwardBarfSexp,
asyncHandler: paredit.backwardBarfSexp,
},
{
command: 'paredit.splitSexp',
handler: paredit.splitSexp,
asyncHandler: paredit.splitSexp,
},
{
command: 'paredit.joinSexp',
handler: paredit.joinSexp,
asyncHandler: paredit.joinSexp,
},
{
command: 'paredit.spliceSexp',
handler: paredit.spliceSexp,
asyncHandler: paredit.spliceSexp,
},
// ['paredit.transpose', ], // TODO: Not yet implemented
{
command: 'paredit.raiseSexp',
handler: paredit.raiseSexp,
asyncHandler: paredit.raiseSexp,
},
{
command: 'paredit.transpose',
handler: paredit.transpose,
asyncHandler: paredit.transpose,
},
{
command: 'paredit.dragSexprBackward',
handler: paredit.dragSexprBackward,
asyncHandler: paredit.dragSexprBackward,
},
{
command: 'paredit.dragSexprForward',
handler: paredit.dragSexprForward,
asyncHandler: paredit.dragSexprForward,
},
{
command: 'paredit.dragSexprBackwardUp',
handler: paredit.dragSexprBackwardUp,
asyncHandler: paredit.dragSexprBackwardUp,
},
{
command: 'paredit.dragSexprForwardDown',
handler: paredit.dragSexprForwardDown,
asyncHandler: paredit.dragSexprForwardDown,
},
{
command: 'paredit.dragSexprForwardUp',
handler: paredit.dragSexprForwardUp,
asyncHandler: paredit.dragSexprForwardUp,
},
{
command: 'paredit.dragSexprBackwardDown',
handler: paredit.dragSexprBackwardDown,
asyncHandler: paredit.dragSexprBackwardDown,
},
{
command: 'paredit.convolute',
handler: paredit.convolute,
asyncHandler: paredit.convolute,
},
{
command: 'paredit.killRight',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.forwardHybridSexpRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -312,7 +320,7 @@ const pareditCommands = [
},
{
command: 'paredit.killLeft',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { copy: boolean }) => {
return handlers.killLeft(
doc,
// TODO: actually implement multicursor
Expand All @@ -323,7 +331,7 @@ const pareditCommands = [
},
{
command: 'paredit.killSexpForward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.forwardSexpRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -333,7 +341,7 @@ const pareditCommands = [
},
{
command: 'paredit.killSexpBackward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.backwardSexpRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -343,7 +351,7 @@ const pareditCommands = [
},
{
command: 'paredit.killListForward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.forwardListRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -353,7 +361,7 @@ const pareditCommands = [
}, // TODO: Implement with killRange
{
command: 'paredit.killListBackward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.backwardListRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -363,7 +371,7 @@ const pareditCommands = [
}, // TODO: Implement with killRange
{
command: 'paredit.spliceSexpKillForward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.forwardListRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -375,7 +383,7 @@ const pareditCommands = [
},
{
command: 'paredit.spliceSexpKillBackward',
handler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
asyncHandler: async (doc: EditableDocument, opts?: { copy: boolean }) => {
const range = paredit.backwardListRange(doc);
if (shouldKillAlsoCutToClipboard(opts?.copy)) {
await copyRangeToClipboard(doc, range);
Expand All @@ -387,96 +395,96 @@ const pareditCommands = [
},
{
command: 'paredit.wrapAroundParens',
handler: (doc: EditableDocument) => {
asyncHandler: (doc: EditableDocument) => {
return paredit.wrapSexpr(doc, '(', ')');
},
},
{
command: 'paredit.wrapAroundSquare',
handler: (doc: EditableDocument) => {
asyncHandler: (doc: EditableDocument) => {
return paredit.wrapSexpr(doc, '[', ']');
},
},
{
command: 'paredit.wrapAroundCurly',
handler: (doc: EditableDocument) => {
asyncHandler: (doc: EditableDocument) => {
return paredit.wrapSexpr(doc, '{', '}');
},
},
{
command: 'paredit.wrapAroundQuote',
handler: (doc: EditableDocument) => {
asyncHandler: (doc: EditableDocument) => {
return paredit.wrapSexpr(doc, '"', '"');
},
},
{
command: 'paredit.rewrapParens',
handler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
const isMulti = multiCursorEnabled(opts?.multicursor);
return handlers.rewrapParens(doc, isMulti);
},
},
{
command: 'paredit.rewrapSquare',
handler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
const isMulti = multiCursorEnabled(opts?.multicursor);
return handlers.rewrapSquare(doc, isMulti);
},
},
{
command: 'paredit.rewrapCurly',
handler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
const isMulti = multiCursorEnabled(opts?.multicursor);
return handlers.rewrapCurly(doc, isMulti);
},
},
{
command: 'paredit.rewrapSet',
handler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
const isMulti = multiCursorEnabled(opts?.multicursor);
return handlers.rewrapSet(doc, isMulti);
},
},
{
command: 'paredit.rewrapQuote',
handler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
asyncHandler: (doc: EditableDocument, opts?: { multicursor: boolean }) => {
const isMulti = multiCursorEnabled(opts?.multicursor);
return handlers.rewrapQuote(doc, isMulti);
},
},
{
command: 'paredit.deleteForward',
handler: async (doc: EditableDocument) => {
asyncHandler: async (doc: EditableDocument) => {
await paredit.deleteForward(doc);
},
},
{
command: 'paredit.deleteBackward',
handler: async (doc: EditableDocument) => {
asyncHandler: async (doc: EditableDocument) => {
await paredit.backspace(doc, await config.getConfig());
},
},
{
command: 'paredit.forceDeleteForward',
handler: () => {
asyncHandler: () => {
return vscode.commands.executeCommand('deleteRight');
},
},
{
command: 'paredit.forceDeleteBackward',
handler: () => {
asyncHandler: () => {
return vscode.commands.executeCommand('deleteLeft');
},
},
{
command: 'paredit.addRichComment',
handler: async (doc: EditableDocument) => {
asyncHandler: async (doc: EditableDocument) => {
await paredit.addRichComment(doc);
},
},
{
command: 'paredit.insertSemiColon',
handler: async (doc: EditableDocument) => {
asyncHandler: async (doc: EditableDocument) => {
await paredit.insertSemiColon(doc);
},
},
Expand All @@ -485,7 +493,7 @@ const pareditCommands = [
// ] as const satisfies readonly PareditCommand[];

function wrapPareditCommand<C extends PareditCommand>(command: C) {
return async (arg: CommandArgOf<C>) => {
return (textEditor: vscode.TextEditor, builder: vscode.TextEditorEdit, arg: CommandArgOf<C>) => {
try {
const textEditor = window.activeTextEditor;

Expand All @@ -495,7 +503,25 @@ function wrapPareditCommand<C extends PareditCommand>(command: C) {
if (!enabled || !languages.has(textEditor.document.languageId)) {
return;
}
return command.handler(mDoc, arg);
return command.handler(mDoc, arg, builder);
} catch (e) {
console.error(e.message);
}
};
}

function wrapPareditAsyncCommand<C extends PareditAsyncCommand>(command: C) {
return async (arg: AsyncCommandArgOf<C>) => {
try {
const textEditor = window.activeTextEditor;

assertIsDefined(textEditor, 'Expected window to have an activeTextEditor!');

const mDoc: EditableDocument = docMirror.getDocument(textEditor.document);
if (!enabled || !languages.has(textEditor.document.languageId)) {
return;
}
return command.asyncHandler(mDoc, arg);
} catch (e) {
console.error(e.message);
}
Expand Down Expand Up @@ -540,9 +566,21 @@ export function activate(context: ExtensionContext) {
setKeyMapConf();
}
}),
...pareditCommands.map((command) =>
commands.registerCommand(command.command, wrapPareditCommand(command))
),
...pareditCommands.map((command) => {
if (command['handler']) {
return commands.registerTextEditorCommand(
command.command,
wrapPareditCommand(command as PareditCommand)
);
} else if (command['asyncHandler']) {
return commands.registerCommand(
command.command,
wrapPareditAsyncCommand(command as PareditAsyncCommand)
);
} else {
throw 'Unexpected';
}
}),
commands.registerCommand('calva.diagnostics.printTextNotationFromDocument', () => {
const doc = vscode.window.activeTextEditor?.document;
if (doc && doc.languageId === 'clojure') {
Expand Down