diff --git a/frontend/app/view/term/term-model.ts b/frontend/app/view/term/term-model.ts index 0a5c39ad86..0e783c7ee5 100644 --- a/frontend/app/view/term/term-model.ts +++ b/frontend/app/view/term/term-model.ts @@ -55,11 +55,13 @@ export class TermViewModel implements ViewModel { fontSizeAtom: jotai.Atom; termThemeNameAtom: jotai.Atom; termTransparencyAtom: jotai.Atom; + termBPMAtom: jotai.Atom; noPadding: jotai.PrimitiveAtom; endIconButtons: jotai.Atom; shellProcFullStatus: jotai.PrimitiveAtom; shellProcStatus: jotai.Atom; shellProcStatusUnsubFn: () => void; + termBPMUnsubFn: () => void; isCmdController: jotai.Atom; isRestarting: jotai.PrimitiveAtom; searchAtoms?: SearchAtoms; @@ -204,6 +206,7 @@ export class TermViewModel implements ViewModel { return true; }); this.filterOutNowsh = jotai.atom(false); + this.termBPMAtom = getOverrideConfigAtom(blockId, "term:allowbracketedpaste"); this.termThemeNameAtom = useBlockAtom(blockId, "termthemeatom", () => { return jotai.atom((get) => { return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme; @@ -313,6 +316,12 @@ export class TermViewModel implements ViewModel { const fullStatus = get(this.shellProcFullStatus); return fullStatus?.shellprocstatus ?? "init"; }); + this.termBPMUnsubFn = globalStore.sub(this.termBPMAtom, () => { + if (this.termRef.current?.terminal) { + const allowBPM = globalStore.get(this.termBPMAtom) ?? true; + this.termRef.current.terminal.options.ignoreBracketedPasteMode = !allowBPM; + } + }); } getShellIntegrationIconButton(get: jotai.Getter): IconButtonDecl | null { @@ -447,6 +456,9 @@ export class TermViewModel implements ViewModel { if (this.shellProcStatusUnsubFn) { this.shellProcStatusUnsubFn(); } + if (this.termBPMUnsubFn) { + this.termBPMUnsubFn(); + } } giveFocus(): boolean { @@ -579,6 +591,7 @@ export class TermViewModel implements ViewModel { const termThemeKeys = Object.keys(termThemes); const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme")); const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12; + const defaultAllowBracketedPaste = globalStore.get(getSettingsKeyAtom("term:allowbracketedpaste")) ?? true; const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency")); const blockData = globalStore.get(this.blockAtom); const overrideFontSize = blockData?.meta?.["term:fontsize"]; @@ -674,6 +687,45 @@ export class TermViewModel implements ViewModel { label: "Transparency", submenu: transparencySubMenu, }); + const allowBracketedPaste = blockData?.meta?.["term:allowbracketedpaste"]; + fullMenu.push({ + label: "Allow Bracketed Paste Mode", + submenu: [ + { + label: "Default (" + (defaultAllowBracketedPaste ? "On" : "Off") + ")", + type: "checkbox", + checked: allowBracketedPaste == null, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:allowbracketedpaste": null }, + }); + }, + }, + { + label: "On", + type: "checkbox", + checked: allowBracketedPaste === true, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:allowbracketedpaste": true }, + }); + }, + }, + { + label: "Off", + type: "checkbox", + checked: allowBracketedPaste === false, + click: () => { + RpcApi.SetMetaCommand(TabRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "term:allowbracketedpaste": false }, + }); + }, + }, + ], + }); fullMenu.push({ type: "separator" }); fullMenu.push({ label: "Force Restart Controller", diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 2656e83aa8..387155752d 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -245,7 +245,6 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => const fullConfig = globalStore.get(atoms.fullConfigAtom); const termThemeName = globalStore.get(model.termThemeNameAtom); const termTransparency = globalStore.get(model.termTransparencyAtom); - const termBPMAtom = getOverrideConfigAtom(blockId, "term:allowbracketedpaste"); const termMacOptionIsMetaAtom = getOverrideConfigAtom(blockId, "term:macoptionismeta"); const [termTheme, _] = computeTheme(fullConfig, termThemeName, termTransparency); let termScrollback = 2000; @@ -261,7 +260,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps) => if (termScrollback > 50000) { termScrollback = 50000; } - const termAllowBPM = globalStore.get(termBPMAtom) ?? false; + const termAllowBPM = globalStore.get(model.termBPMAtom) ?? true; const termMacOptionIsMeta = globalStore.get(termMacOptionIsMetaAtom) ?? false; const wasFocused = model.termRef.current != null && globalStore.get(model.nodeModel.isFocused); const termWrap = new TermWrap( diff --git a/pkg/blockcontroller/shellcontroller.go b/pkg/blockcontroller/shellcontroller.go index aca0864600..2f54c2c365 100644 --- a/pkg/blockcontroller/shellcontroller.go +++ b/pkg/blockcontroller/shellcontroller.go @@ -204,6 +204,7 @@ func (sc *ShellController) resetTerminalState(logCtx context.Context) { buf.WriteString("\x1b[?25h") // show cursor buf.WriteString("\x1b[?1000l") // disable mouse tracking buf.WriteString("\x1b[?1007l") // disable alternate scroll mode + buf.WriteString("\x1b[?2004l") // disable bracketed paste mode buf.WriteString(shellutil.FormatOSC(16162, "R")) // OSC 16162 "R" - disable alternate screen mode (only if active), reset "shell integration" status. buf.WriteString("\r\n\r\n") err := HandleAppendBlockFile(sc.BlockId, wavebase.BlockFile_Term, buf.Bytes())