Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
5 changes: 5 additions & 0 deletions .changeset/slick-owls-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emacs-mcx": patch
---

Paredit config effective per document rather than globally
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ When true, line-move moves point by visual lines (same as an Emacs variable line

### `emacs-mcx.paredit.parentheses`

Key-value pairs of parentheses like the following example to be used in the ParEdit commands.
Key-value pairs of parentheses to be used in the ParEdit commands like the following example.

```json
{
Expand All @@ -199,6 +199,22 @@ Key-value pairs of parentheses like the following example to be used in the ParE
}
```

User-defined pairs are merged with the default pairs.
You can also override the default pairs or disable them by setting `null` as the value. For example:

```json
"emacs-mcx.paredit.parentheses": {
"<": ">", // New pair
"{": null, // Default pair disabled
}
// This will result in the following configuration for files of the language:
// {
// "[": "]",
// "(": ")",
// "<": ">"
// }
```

### `emacs-mcx.subwordMode`

When true, word-oriented move and edit commands, including M-f, M-b, M-d will
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,19 @@
"type": "object",
"patternProperties": {
"^\\S$": {
"type": "string",
"type": [
"string",
"null"
],
"pattern": "^\\S$"
}
},
"default": {
"[": "]",
"(": ")",
"{": "}"
}
},
"scope": "language-overridable"
},
"emacs-mcx.subwordMode": {
"type": "boolean",
Expand Down
25 changes: 24 additions & 1 deletion src/commands/paredit.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import * as paredit from "paredit.js";
import { TextDocument, Selection, Range, TextEditor, Position } from "vscode";
import * as vscode from "vscode";
import { EmacsCommand } from ".";
import { KillYankCommand } from "./kill";
import { AppendDirection } from "../kill-yank";
import { revealPrimaryActive } from "./helpers/reveal";
import { MessageManager } from "../message";
import { Logger } from "../logger";

const logger = Logger.get("paredit");

type PareditNavigatorFn = (ast: paredit.AST, idx: number) => number;

function getPareditParenthesesConfig(document: vscode.TextDocument): { [key: string]: string } {
const config = vscode.workspace.getConfiguration("emacs-mcx", document);
const parentheses = config.get<{ [key: string]: string | null }>("paredit.parentheses", {});
// parentheses[open] can be null to explicitly disable a pair
const filteredParentheses: { [key: string]: string } = {};
for (const open in parentheses) {
const close = parentheses[open];
if (close != null) {
filteredParentheses[open] = close;
}
}
return filteredParentheses;
}

// Languages in which semicolon represents comment
const languagesSemicolonComment = new Set(["clojure", "lisp", "scheme"]);

Expand All @@ -19,8 +37,13 @@ const makeSexpTravelFunc = (doc: TextDocument, pareditNavigatorFn: PareditNaviga
// However, in other languages, semicolon should be treated as one entity, but not comment for convenience.
// To do so, ";" is replaced with another character which is not treated as comment by paredit.js
// if the document is not lisp or lisp-like languages.
src = src.split(";").join("_"); // split + join = replaceAll
src = src.replaceAll(";", "_");
}

const parentheses = getPareditParenthesesConfig(doc);
logger.debug(`Using paredit parentheses: ${JSON.stringify(parentheses)}`);
paredit.reader.setParentheses(parentheses);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to paredit.reader.setParentheses modifies global state in the paredit.js library. If multiple paredit commands are executed concurrently on different documents with different configurations, they could interfere with each other. For example, if command A sets parentheses for document A, then command B sets different parentheses for document B before command A calls paredit.parse, command A will use the wrong configuration.

While this is likely a pre-existing architectural limitation of paredit.js rather than a new issue introduced by this PR, it's worth noting. A potential mitigation would be to ensure paredit operations are serialized, though this may be complex to implement correctly.

Copilot uses AI. Check for mistakes.

const ast = paredit.parse(src);

return (position: Position, repeat: number): Position => {
Expand Down
10 changes: 1 addition & 9 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@

import { Logger } from "../logger";
import * as vscode from "vscode";
import { IConfiguration, IDebugConfiguration, IPareditConfiguration } from "./iconfiguration";
import * as paredit from "paredit.js";
import { IConfiguration, IDebugConfiguration } from "./iconfiguration";

Check warning on line 7 in src/configuration/configuration.ts

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (iconfiguration)

export class Configuration implements IConfiguration, vscode.Disposable {
/**
Expand Down Expand Up @@ -81,10 +80,6 @@
return this.scrollDownCommandBehavior;
}

public paredit: IPareditConfiguration = {
parentheses: { "[": "]", "(": ")", "{": "}" },
};

public debug: IDebugConfiguration = {
silent: false,
loggingLevelForAlert: "error",
Expand Down Expand Up @@ -124,9 +119,6 @@
}

Logger.configChanged(this);

// Update configs in the third-party libraries.
paredit.reader.setParentheses(this.paredit.parentheses);
}

private static unproxify(obj: { [key: string]: unknown }) {
Expand Down
9 changes: 0 additions & 9 deletions src/configuration/iconfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export interface IPareditConfiguration {
parentheses: { [key: string]: string };
}

export interface IDebugConfiguration {
/**
* Boolean indicating whether all logs should be suppressed
Expand Down Expand Up @@ -59,11 +55,6 @@ export interface IConfiguration {
scrollDownCommandBehavior: "vscode" | "emacs";
wordNavigationStyle: "vscode" | "emacs";

/**
* Paredit configuration
*/
paredit: IPareditConfiguration;

/**
* Extension debugging settings
*/
Expand Down
2 changes: 1 addition & 1 deletion src/test/suite/extension.native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ suite("package.json", () => {
return false;
}

if (keyFirstSegment == "subwordMode") {
if (keyFirstSegment && ["subwordMode", "paredit"].includes(keyFirstSegment)) {
// Special case subwordMode. It's handled by wordSeparators.ts.
return false;
}
Expand Down
Loading