Skip to content
Open
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
3 changes: 3 additions & 0 deletions frontend/app/store/keymodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ function appHandleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
if (globalKeybindingsDisabled) {
return false;
}
if (waveEvent.isComposing) {
return false;
}
const nativeEvent = (waveEvent as any).nativeEvent;
if (lastHandledEvent != null && nativeEvent != null && lastHandledEvent === nativeEvent) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/view/term/ijson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ body {
}

.fixed-font {
normal 12px / normal "Hack", monospace;
font: normal 12px / normal "Hack", "Noto Sans Mono CJK KR", "Noto Sans Mono CJK JP", "Noto Sans Mono CJK SC", "Noto Sans Mono CJK TC", "Noto Sans CJK KR", "Noto Sans CJK JP", "Noto Sans CJK SC", "Noto Sans CJK TC", "Apple SD Gothic Neo", "Hiragino Sans", "PingFang SC", "PingFang TC", "Microsoft YaHei", "Malgun Gothic", monospace;
}
`}
</style>
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/view/term/term-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,9 @@ export class TermViewModel implements ViewModel {
}

handleTerminalKeydown(event: KeyboardEvent): boolean {
if (event.isComposing || event.keyCode == 229) {
return true;
}
const waveEvent = keyutil.adaptFromReactOrNativeKeyEvent(event);
if (waveEvent.type != "keydown") {
return true;
Expand Down
9 changes: 7 additions & 2 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ import { TermWrap } from "./termwrap";
import "./xterm.css";

const dlog = debug("wave:term");
const DefaultTermFontFamily =
"Hack, 'Noto Sans Mono CJK KR', 'Noto Sans Mono CJK JP', 'Noto Sans Mono CJK SC', 'Noto Sans Mono CJK TC', " +
"'Noto Sans CJK KR', 'Noto Sans CJK JP', 'Noto Sans CJK SC', 'Noto Sans CJK TC', " +
"'Apple SD Gothic Neo', 'Hiragino Sans', 'PingFang SC', 'PingFang TC', 'Microsoft YaHei', 'Malgun Gothic', monospace";

interface TerminalViewProps {
blockId: string;
Expand Down Expand Up @@ -292,6 +296,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
const termMacOptionIsMeta = globalStore.get(termMacOptionIsMetaAtom) ?? false;
const termCursorStyle = normalizeCursorStyle(globalStore.get(getOverrideConfigAtom(blockId, "term:cursor")));
const termCursorBlink = globalStore.get(getOverrideConfigAtom(blockId, "term:cursorblink")) ?? false;
const termDisableWebGl = termSettings?.["term:disablewebgl"] ?? true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for documentation or comments about WebGL default change

# Check for WebGL-related documentation
rg -n -C3 --type=md 'webgl|WebGL' | head -50

# Check for any TODO/FIXME about WebGL defaults
rg -n 'TODO|FIXME' --iglob '*webgl*' --iglob '*term*'

# Look for changelog or migration guide entries
fd -e md -e txt | xargs rg -l 'changelog|CHANGELOG|migration|breaking'

Repository: wavetermdev/waveterm

Length of output: 242


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the toggle logic around the referenced lines
sed -n '250,360p' frontend/app/view/term/term.tsx | nl -ba | sed -n '40,140p'

# 2) Search for the setting key and related strings
rg -n --hidden --no-ignore -S 'term:disablewebgl|disablewebgl|Default terminal rendering to non-WebGL|non-WebGL|useWebGl' frontend/app | head -200

# 3) Search docs/releases/changelog/migration/breaking notes
rg -n -S --hidden --no-ignore '(WebGL|webgl|non-WebGL|nonwebgl|disablewebgl|term:disablewebgl|breaking|migration|changelog|RELEASES\.md|Release note|release notes)' \
  . | head -200

# 4) Specifically inspect RELEASES.md for any WebGL-related entry
if [ -f RELEASES.md ]; then
  rg -n -S 'WebGL|webgl|non-WebGL|disablewebgl|term:disablewebgl|breaking|migration' RELEASES.md | head -200 || true
  # show nearby sections if any match lines
  MATCH_LINES=$(rg -n -S 'WebGL|webgl|non-WebGL|disablewebgl|term:disablewebgl|breaking|migration' RELEASES.md | cut -d: -f1 | head -20 | tr '\n' ' ')
  if [ -n "${MATCH_LINES:-}" ]; then
    for ln in $MATCH_LINES; do
      echo "---- RELEASES.md around line $ln ----"
      sed -n "$((ln-20)),$((ln+20))p" RELEASES.md
    done
  fi
else
  echo "RELEASES.md not found at repo root."
fi

Repository: wavetermdev/waveterm

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect toggle logic around referenced lines (with line numbers)
sed -n '260,340p' frontend/app/view/term/term.tsx | cat -n | sed -n '1,120p'

# 2) Search for the setting key and related strings (scoped to term code + docs)
rg -n -S --hidden --no-ignore 'term:disablewebgl|disablewebgl|Default terminal rendering to non-WebGL|non-WebGL|useWebGl|useWebGL' frontend/app . | head -200

# 3) Search docs/releases/changelog/migration/breaking notes
rg -n -S --hidden --no-ignore '(WebGL|webgl|non-WebGL|nonwebgl|disablewebgl|term:disablewebgl|breaking|migration|changelog|RELEASES\.md)' . | head -200

# 4) Specifically inspect RELEASES.md
if [ -f RELEASES.md ]; then
  echo "---- RELEASES.md matches ----"
  rg -n -S 'WebGL|webgl|non-WebGL|nonwebgl|disablewebgl|term:disablewebgl|breaking|migration|non-WebGL' RELEASES.md | head -200 || true
else
  echo "RELEASES.md not found at repo root."
fi

Repository: wavetermdev/waveterm

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show termdisablewebgl docs/defaults/schema with tight context (exclude node_modules)
rg -n -S 'term:disablewebgl' pkg/wconfig/defaultconfig/settings.json schema/settings.json docs/docs/config.mdx frontend/types/gotypes.d.ts frontend/app/view/term/termwrap.ts frontend/app/view/term/term.tsx

echo "---- docs/docs/config.mdx ----"
sed -n '40,90p' docs/docs/config.mdx

echo "---- pkg/wconfig/defaultconfig/settings.json ----"
sed -n '1,80p' pkg/wconfig/defaultconfig/settings.json

echo "---- schema/settings.json (term:disablewebgl section) ----"
python3 - <<'PY'
import json
from pathlib import Path

p = Path("schema/settings.json")
data = json.loads(p.read_text())
# brute-force search for key in nested structures
def find_key(obj, key):
    if isinstance(obj, dict):
        for k,v in obj.items():
            if k == key:
                return v
            r = find_key(v, key)
            if r is not None:
                return r
    elif isinstance(obj, list):
        for it in obj:
            r = find_key(it, key)
            if r is not None:
                return r
    return None

v = find_key(data, "term:disablewebgl")
print(v)
PY

echo "---- frontend/app/view/term/termwrap.ts (renderer selection) ----"
sed -n '140,230p' frontend/app/view/term/termwrap.ts

# Search RELEASES.md for WebGL/non-WebGL without blowing up output (exclude node_modules)
if [ -f RELEASES.md ]; then
  rg -n -S 'WebGL|webgl|non-WebGL|nonwebgl|disablewebgl|term:disablewebgl' RELEASES.md | head -200 || true
fi

# Also search docs/releases folder if exists (avoid node_modules)
if [ -d docs ]; then
  rg -n -S '(WebGL|webgl|non-WebGL|nonwebgl|disablewebgl|term:disablewebgl)' docs | head -200 || true
fi

Repository: wavetermdev/waveterm

Length of output: 23101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for release/note mentions (exclude node_modules)
for f in RELEASES.md docs/RELEASES.md CHANGELOG.md docs/CHANGELOG.md; do
  if [ -f "$f" ]; then
    echo "---- $f ----"
    rg -n -S 'non-WebGL|nonwebgl|WebGL|webgl|term:disablewebgl|disablewebgl|disable WebGL' "$f" || true
  fi
done

# Search docs + repo for the specific phrase the review comment mentioned
rg -n -S --hidden --no-ignore --glob '!**/node_modules/**' \
  'Default terminal rendering to non-WebGL|non-WebGL|term:disablewebgl|disablewebgl|useWebGl' . | head -200

# Check config default description inconsistency
echo "---- docs/docs/config.mdx line with term:disablewebgl ----"
rg -n -S 'term:disablewebgl' docs/docs/config.mdx

Repository: wavetermdev/waveterm

Length of output: 1621


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "DefaultTermFontFamily" frontend/app/view/term/term.tsx

Repository: wavetermdev/waveterm

Length of output: 207


Fix WebGL default inconsistency for term:disablewebgl (and document it)

  • frontend/app/view/term/term.tsx (299): termDisableWebGl = termSettings?.["term:disablewebgl"] ?? true => WebGL is disabled when the setting is unset; (323) useWebGl: !termDisableWebGl => defaults to false.
  • pkg/wconfig/defaultconfig/settings.json (35) also sets "term:disablewebgl": true.
  • docs/docs/config.mdx (63) states term:disablewebgl default is false, which conflicts with the code/config defaults above.
  • No release/changelog/migration note mentioning term:disablewebgl / WebGL (or “non-WebGL”) was found in RELEASES.md / docs release files.

Update the docs (and/or add release/migration notes if this default changed for existing users).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/app/view/term/term.tsx` at line 299, The code currently sets
termDisableWebGl via termSettings?.["term:disablewebgl"] ?? true which makes
termDisableWebGl true (WebGL disabled) by default and useWebGl is computed as
!termDisableWebGl; this conflicts with the docs that state the default is false;
update the documentation to reflect the actual default (term:disablewebgl
defaults to true) or change the default in code/config to false to match the
docs, and add a short migration/release note documenting the default behavior
change for the setting (referencing the termDisableWebGl variable and the
useWebGl computation) so users are informed of the change.

const wasFocused = model.termRef.current != null && globalStore.get(model.nodeModel.isFocused);
const termWrap = new TermWrap(
tabModel.tabId,
Expand All @@ -300,7 +305,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
{
theme: termTheme,
fontSize: termFontSize,
fontFamily: termSettings?.["term:fontfamily"] ?? connFontFamily ?? "Hack",
fontFamily: termSettings?.["term:fontfamily"] ?? connFontFamily ?? DefaultTermFontFamily,
drawBoldTextInBrightColors: false,
fontWeight: "normal",
fontWeightBold: "bold",
Expand All @@ -315,7 +320,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
},
{
keydownHandler: model.handleTerminalKeydown.bind(model),
useWebGl: !termSettings?.["term:disablewebgl"],
useWebGl: !termDisableWebGl,
sendDataHandler: model.sendDataToController.bind(model),
nodeModel: model.nodeModel,
}
Expand Down
143 changes: 141 additions & 2 deletions frontend/app/view/term/termwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ export class TermWrap {
lastPasteData: string = "";
lastPasteTime: number = 0;

// IME composition ordering
compositionActive: boolean = false;
compositionRecentlyEndedUntil: number = 0;
pendingCompositionSuffix: { data: string; timeout: ReturnType<typeof setTimeout> } | null = null;
disposed: boolean = false;

// dev only (for debugging)
recentWrites: { idx: number; data: string; ts: number }[] = [];
recentWritesCounter: number = 0;
Expand Down Expand Up @@ -272,6 +278,12 @@ export class TermWrap {
})
);
this.terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => {
if (e.isComposing || e.keyCode == 229) {
return true;
}
if (this.shouldBypassWaveKeydownForComposition(e)) {
return true;
}
if (!waveOptions.keydownHandler) {
return true;
}
Expand All @@ -282,6 +294,7 @@ export class TermWrap {
this.heldData = [];
this.handleResize_debounced = debounce(50, this.handleResize.bind(this));
this.terminal.open(this.connectElem);
this.registerCompositionEventHandlers();

const dragoverHandler = (e: DragEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -442,6 +455,7 @@ export class TermWrap {
}

dispose() {
this.disposed = true;
this.promptMarkers.forEach((marker) => {
try {
marker.dispose();
Expand All @@ -450,6 +464,7 @@ export class TermWrap {
}
});
this.promptMarkers = [];
this.cancelPendingCompositionSuffix();
this.webglContextLossDisposable?.dispose();
this.webglContextLossDisposable = null;
this.terminal.dispose();
Expand All @@ -463,15 +478,139 @@ export class TermWrap {
this.mainFileSubject.release();
}

handleTermData(data: string) {
if (!this.loaded) {
registerCompositionEventHandlers() {
const textarea = this.terminal.textarea;
if (textarea == null) {
return;
}
const compositionStartHandler = () => {
this.compositionActive = true;
this.compositionRecentlyEndedUntil = 0;
this.flushPendingCompositionSuffix();
};
const compositionEndHandler = () => {
this.compositionActive = false;
this.compositionRecentlyEndedUntil = Date.now() + 75;
};
textarea.addEventListener("compositionstart", compositionStartHandler);
textarea.addEventListener("compositionend", compositionEndHandler);
this.toDispose.push({
dispose: () => {
textarea.removeEventListener("compositionstart", compositionStartHandler);
textarea.removeEventListener("compositionend", compositionEndHandler);
this.cancelPendingCompositionSuffix();
},
});
}

shouldBypassWaveKeydownForComposition(event: KeyboardEvent): boolean {
if (this.compositionActive) {
return true;
}
if (Date.now() > this.compositionRecentlyEndedUntil) {
return false;
}
return !event.ctrlKey && !event.metaKey && !event.altKey && this.isCompositionSuffixData(event.key);
}

sendTermData(data: string) {
this.sendDataHandler?.(data);
this.multiInputCallback?.(data);
}

flushPendingCompositionSuffix() {
if (this.pendingCompositionSuffix == null) {
return;
}
const pendingData = this.pendingCompositionSuffix.data;
clearTimeout(this.pendingCompositionSuffix.timeout);
this.pendingCompositionSuffix = null;
if (!this.loaded || this.disposed) {
return;
}
this.sendTermData(pendingData);
}

cancelPendingCompositionSuffix() {
if (this.pendingCompositionSuffix == null) {
return;
}
clearTimeout(this.pendingCompositionSuffix.timeout);
this.pendingCompositionSuffix = null;
}

isLikelyCompositionText(data: string): boolean {
if (data.length === 0) {
return false;
}
let hasNonAscii = false;
for (const ch of data) {
const codePoint = ch.codePointAt(0);
if (codePoint == null || codePoint <= 0x1f || codePoint === 0x7f) {
return false;
}
if (codePoint > 0x7f) {
hasNonAscii = true;
}
}
return hasNonAscii;
}

isCompositionSuffixData(data: string): boolean {
if (data.length === 0) {
return false;
}
for (const ch of data) {
const codePoint = ch.codePointAt(0);
if (codePoint == null || codePoint < 0x20 || codePoint > 0x7e) {
return false;
}
}
return true;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

handleTermData(data: string) {
if (!this.loaded) {
return;
}

if (this.pendingCompositionSuffix != null) {
if (this.isLikelyCompositionText(data)) {
const pendingData = this.pendingCompositionSuffix.data;
clearTimeout(this.pendingCompositionSuffix.timeout);
this.pendingCompositionSuffix = null;
this.sendTermData(data);
this.sendTermData(pendingData);
return;
}
if (this.isCompositionSuffixData(data) && Date.now() <= this.compositionRecentlyEndedUntil) {
clearTimeout(this.pendingCompositionSuffix.timeout);
this.pendingCompositionSuffix.data += data;
this.pendingCompositionSuffix.timeout = setTimeout(() => {
this.flushPendingCompositionSuffix();
}, 30);
return;
}
this.flushPendingCompositionSuffix();
}

if (
this.isCompositionSuffixData(data) &&
!this.compositionActive &&
Date.now() <= this.compositionRecentlyEndedUntil
) {
this.pendingCompositionSuffix = {
data,
timeout: setTimeout(() => {
this.flushPendingCompositionSuffix();
}, 30),
};
return;
}

this.sendTermData(data);
}

addFocusListener(focusFn: () => void) {
this.terminal.textarea.addEventListener("focus", focusFn);
}
Expand Down
1 change: 1 addition & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,7 @@ declare global {
code: string;
repeat?: boolean;
location?: number;
isComposing?: boolean;
shift?: boolean;
control?: boolean;
alt?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions frontend/util/keyutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ function adaptFromReactOrNativeKeyEvent(event: React.KeyboardEvent | KeyboardEve
rtn.key = event.key;
rtn.location = event.location;
(rtn as any).nativeEvent = event;
rtn.isComposing = event.isComposing || (event as any).keyCode == 229;
if (event.type == "keydown" || event.type == "keyup" || event.type == "keypress") {
rtn.type = event.type;
} else {
Expand Down Expand Up @@ -268,6 +269,7 @@ function adaptFromElectronKeyEvent(event: any): WaveKeyboardEvent {
rtn.location = event.location;
rtn.code = event.code;
rtn.key = event.key;
rtn.isComposing = event.isComposing || event.keyCode == 229;
return rtn;
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/vdom/vdom_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ type WaveKeyboardEvent struct {
Code string `json:"code"` // KeyboardEvent.code
Repeat bool `json:"repeat,omitempty"`
Location int `json:"location,omitempty"` // KeyboardEvent.location
// True while an IME composition is active. These key events should not trigger app shortcuts.
IsComposing bool `json:"isComposing,omitempty"`

// modifiers
Shift bool `json:"shift,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions pkg/wconfig/defaultconfig/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"telemetry:enabled": true,
"term:bellsound": false,
"term:bellindicator": true,
"term:disablewebgl": true,
"term:fontfamily": "Hack, 'Noto Sans Mono CJK KR', 'Noto Sans Mono CJK JP', 'Noto Sans Mono CJK SC', 'Noto Sans Mono CJK TC', 'Noto Sans CJK KR', 'Noto Sans CJK JP', 'Noto Sans CJK SC', 'Noto Sans CJK TC', 'Apple SD Gothic Neo', 'Hiragino Sans', 'PingFang SC', 'PingFang TC', 'Microsoft YaHei', 'Malgun Gothic', monospace",
"term:osc52": "always",
"term:cursor": "block",
"term:cursorblink": false,
Expand Down