Skip to content

Commit 0d60950

Browse files
authored
default bracketed paste mode ON, add context menu (#2639)
We were too conservative by defaulting ignorebrackedpastemode in xterm.js to true. it should have been set to false. this solves issues with pasting multi-line strings into zsh, claude code, etc. add a new context menu option to more easily disable if you're in a weird ssh session or a REPL that doesn't support bracketedpaste.
1 parent 4449895 commit 0d60950

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

frontend/app/view/term/term-model.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ export class TermViewModel implements ViewModel {
5555
fontSizeAtom: jotai.Atom<number>;
5656
termThemeNameAtom: jotai.Atom<string>;
5757
termTransparencyAtom: jotai.Atom<number>;
58+
termBPMAtom: jotai.Atom<boolean>;
5859
noPadding: jotai.PrimitiveAtom<boolean>;
5960
endIconButtons: jotai.Atom<IconButtonDecl[]>;
6061
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
6162
shellProcStatus: jotai.Atom<string>;
6263
shellProcStatusUnsubFn: () => void;
64+
termBPMUnsubFn: () => void;
6365
isCmdController: jotai.Atom<boolean>;
6466
isRestarting: jotai.PrimitiveAtom<boolean>;
6567
searchAtoms?: SearchAtoms;
@@ -204,6 +206,7 @@ export class TermViewModel implements ViewModel {
204206
return true;
205207
});
206208
this.filterOutNowsh = jotai.atom(false);
209+
this.termBPMAtom = getOverrideConfigAtom(blockId, "term:allowbracketedpaste");
207210
this.termThemeNameAtom = useBlockAtom(blockId, "termthemeatom", () => {
208211
return jotai.atom<string>((get) => {
209212
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
@@ -313,6 +316,12 @@ export class TermViewModel implements ViewModel {
313316
const fullStatus = get(this.shellProcFullStatus);
314317
return fullStatus?.shellprocstatus ?? "init";
315318
});
319+
this.termBPMUnsubFn = globalStore.sub(this.termBPMAtom, () => {
320+
if (this.termRef.current?.terminal) {
321+
const allowBPM = globalStore.get(this.termBPMAtom) ?? true;
322+
this.termRef.current.terminal.options.ignoreBracketedPasteMode = !allowBPM;
323+
}
324+
});
316325
}
317326

318327
getShellIntegrationIconButton(get: jotai.Getter): IconButtonDecl | null {
@@ -447,6 +456,9 @@ export class TermViewModel implements ViewModel {
447456
if (this.shellProcStatusUnsubFn) {
448457
this.shellProcStatusUnsubFn();
449458
}
459+
if (this.termBPMUnsubFn) {
460+
this.termBPMUnsubFn();
461+
}
450462
}
451463

452464
giveFocus(): boolean {
@@ -579,6 +591,7 @@ export class TermViewModel implements ViewModel {
579591
const termThemeKeys = Object.keys(termThemes);
580592
const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme"));
581593
const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12;
594+
const defaultAllowBracketedPaste = globalStore.get(getSettingsKeyAtom("term:allowbracketedpaste")) ?? true;
582595
const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency"));
583596
const blockData = globalStore.get(this.blockAtom);
584597
const overrideFontSize = blockData?.meta?.["term:fontsize"];
@@ -674,6 +687,45 @@ export class TermViewModel implements ViewModel {
674687
label: "Transparency",
675688
submenu: transparencySubMenu,
676689
});
690+
const allowBracketedPaste = blockData?.meta?.["term:allowbracketedpaste"];
691+
fullMenu.push({
692+
label: "Allow Bracketed Paste Mode",
693+
submenu: [
694+
{
695+
label: "Default (" + (defaultAllowBracketedPaste ? "On" : "Off") + ")",
696+
type: "checkbox",
697+
checked: allowBracketedPaste == null,
698+
click: () => {
699+
RpcApi.SetMetaCommand(TabRpcClient, {
700+
oref: WOS.makeORef("block", this.blockId),
701+
meta: { "term:allowbracketedpaste": null },
702+
});
703+
},
704+
},
705+
{
706+
label: "On",
707+
type: "checkbox",
708+
checked: allowBracketedPaste === true,
709+
click: () => {
710+
RpcApi.SetMetaCommand(TabRpcClient, {
711+
oref: WOS.makeORef("block", this.blockId),
712+
meta: { "term:allowbracketedpaste": true },
713+
});
714+
},
715+
},
716+
{
717+
label: "Off",
718+
type: "checkbox",
719+
checked: allowBracketedPaste === false,
720+
click: () => {
721+
RpcApi.SetMetaCommand(TabRpcClient, {
722+
oref: WOS.makeORef("block", this.blockId),
723+
meta: { "term:allowbracketedpaste": false },
724+
});
725+
},
726+
},
727+
],
728+
});
677729
fullMenu.push({ type: "separator" });
678730
fullMenu.push({
679731
label: "Force Restart Controller",

frontend/app/view/term/term.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,6 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
245245
const fullConfig = globalStore.get(atoms.fullConfigAtom);
246246
const termThemeName = globalStore.get(model.termThemeNameAtom);
247247
const termTransparency = globalStore.get(model.termTransparencyAtom);
248-
const termBPMAtom = getOverrideConfigAtom(blockId, "term:allowbracketedpaste");
249248
const termMacOptionIsMetaAtom = getOverrideConfigAtom(blockId, "term:macoptionismeta");
250249
const [termTheme, _] = computeTheme(fullConfig, termThemeName, termTransparency);
251250
let termScrollback = 2000;
@@ -261,7 +260,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
261260
if (termScrollback > 50000) {
262261
termScrollback = 50000;
263262
}
264-
const termAllowBPM = globalStore.get(termBPMAtom) ?? false;
263+
const termAllowBPM = globalStore.get(model.termBPMAtom) ?? true;
265264
const termMacOptionIsMeta = globalStore.get(termMacOptionIsMetaAtom) ?? false;
266265
const wasFocused = model.termRef.current != null && globalStore.get(model.nodeModel.isFocused);
267266
const termWrap = new TermWrap(

pkg/blockcontroller/shellcontroller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ func (sc *ShellController) resetTerminalState(logCtx context.Context) {
204204
buf.WriteString("\x1b[?25h") // show cursor
205205
buf.WriteString("\x1b[?1000l") // disable mouse tracking
206206
buf.WriteString("\x1b[?1007l") // disable alternate scroll mode
207+
buf.WriteString("\x1b[?2004l") // disable bracketed paste mode
207208
buf.WriteString(shellutil.FormatOSC(16162, "R")) // OSC 16162 "R" - disable alternate screen mode (only if active), reset "shell integration" status.
208209
buf.WriteString("\r\n\r\n")
209210
err := HandleAppendBlockFile(sc.BlockId, wavebase.BlockFile_Term, buf.Bytes())

0 commit comments

Comments
 (0)