fix(frontend): preserve ansi colors in terminal effects
Limit stroke and shadow effects to default-foreground terminal text so ANSI-colored output keeps its intended visual meaning. Also enable terminal text stroke and shadow by default in frontend and backend fallbacks, and align the dark preset and default xterm theme with the new green night palette.
This commit is contained in:
@@ -37,6 +37,7 @@ let observedElement: HTMLElement | null = null; // +++ Store the observed elemen
|
||||
let debounceTimer: number | null = null; // 用于防抖的计时器 ID
|
||||
let selectionListenerDisposable: IDisposable | null = null; // +++ 提升声明并添加类型 +++
|
||||
let scrollListenerDisposable: IDisposable | null = null;
|
||||
let renderListenerDisposable: IDisposable | null = null;
|
||||
let lastResizeObserverWidth = 0;
|
||||
let lastResizeObserverHeight = 0;
|
||||
const RESIZE_THRESHOLD = 0.5; // px
|
||||
@@ -132,6 +133,25 @@ const restoreViewportSnapshot = (term: Terminal, snapshot?: TerminalViewportSnap
|
||||
syncViewportTracking(term);
|
||||
};
|
||||
|
||||
const markExplicitForegroundSpans = () => {
|
||||
const hostElement = terminalRef.value;
|
||||
if (!hostElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rowSpans = hostElement.querySelectorAll<HTMLElement>('.xterm-rows span');
|
||||
rowSpans.forEach((span) => {
|
||||
const hasExplicitForeground =
|
||||
span.className.includes('xterm-fg-') || span.style.color !== '';
|
||||
|
||||
if (hasExplicitForeground) {
|
||||
span.dataset.explicitForeground = 'true';
|
||||
} else {
|
||||
delete span.dataset.explicitForeground;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 防抖处理由 ResizeObserver 触发的 resize 事件
|
||||
const debouncedEmitResize = debounce((term: Terminal) => {
|
||||
if (term && props.isActive) { // 仅当标签仍处于活动状态时才发送防抖后的 resize
|
||||
@@ -311,6 +331,7 @@ onMounted(() => {
|
||||
// terminal.open() 同步执行完毕后,可以认为 Xterm 已尝试附加到 DOM
|
||||
isTerminalDomReady.value = true; // +++ 直接在此处设置 DOM 准备就绪状态 +++
|
||||
console.log(`[Terminal ${props.sessionId}] Xterm open() called, considering DOM ready for initial style checks.`);
|
||||
markExplicitForegroundSpans();
|
||||
|
||||
// 适应容器大小
|
||||
fitAndEmitResizeNow(terminal);
|
||||
@@ -320,6 +341,10 @@ onMounted(() => {
|
||||
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data });
|
||||
});
|
||||
|
||||
renderListenerDisposable = terminal.onRender(() => {
|
||||
markExplicitForegroundSpans();
|
||||
});
|
||||
|
||||
scrollListenerDisposable = terminal.onScroll(() => {
|
||||
if (terminal && props.isActive) {
|
||||
syncViewportTracking(terminal);
|
||||
@@ -665,6 +690,10 @@ onBeforeUnmount(() => {
|
||||
scrollListenerDisposable.dispose();
|
||||
}
|
||||
|
||||
if (renderListenerDisposable) {
|
||||
renderListenerDisposable.dispose();
|
||||
}
|
||||
|
||||
|
||||
// 确保在卸载时移除右键监听器
|
||||
removeContextMenuListener();
|
||||
@@ -739,6 +768,7 @@ const applyTerminalTextStyles = () => {
|
||||
} else {
|
||||
hostElement.style.removeProperty('--terminal-shadow');
|
||||
}
|
||||
markExplicitForegroundSpans();
|
||||
// console.log('[Terminal] Applied text styles. Stroke enabled:', terminalTextStrokeEnabled.value, 'Shadow enabled:', terminalTextShadowEnabled.value);
|
||||
}
|
||||
};
|
||||
@@ -814,9 +844,7 @@ watchEffect(() => {
|
||||
}
|
||||
|
||||
/* 文字描边和阴影样式 */
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows span),
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows div > span), /* 更具体地针对嵌套 span */
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows div) { /* 针对直接包含文本的 div */
|
||||
.terminal-inner-container.has-text-stroke :deep(.xterm-rows span:not([data-explicit-foreground="true"])) {
|
||||
-webkit-text-stroke-width: var(--terminal-stroke-width);
|
||||
-webkit-text-stroke-color: var(--terminal-stroke-color);
|
||||
text-stroke-width: var(--terminal-stroke-width);
|
||||
@@ -826,9 +854,7 @@ watchEffect(() => {
|
||||
-webkit-paint-order: stroke fill; /* 兼容 WebKit */
|
||||
}
|
||||
|
||||
.terminal-inner-container.has-text-shadow :deep(.xterm-rows span),
|
||||
.terminal-inner-container.has-text-shadow :deep(.xterm-rows div > span),
|
||||
.terminal-inner-container.has-text-shadow :deep(.xterm-rows div) {
|
||||
.terminal-inner-container.has-text-shadow :deep(.xterm-rows span:not([data-explicit-foreground="true"])) {
|
||||
text-shadow: var(--terminal-shadow);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,11 @@ const {
|
||||
const editableTerminalFontFamily = ref('');
|
||||
const editableTerminalFontSize = ref(14);
|
||||
|
||||
const editableTerminalTextStrokeEnabled = ref(false);
|
||||
const editableTerminalTextStrokeEnabled = ref(true);
|
||||
const editableTerminalTextStrokeWidth = ref(1);
|
||||
const editableTerminalTextStrokeColor = ref('#000000');
|
||||
|
||||
const editableTerminalTextShadowEnabled = ref(false);
|
||||
const editableTerminalTextShadowEnabled = ref(true);
|
||||
const editableTerminalTextShadowOffsetX = ref(0);
|
||||
const editableTerminalTextShadowOffsetY = ref(0);
|
||||
const editableTerminalTextShadowBlur = ref(0);
|
||||
@@ -725,4 +725,4 @@ watch(() => props.isEditingTheme, (isEditing) => {
|
||||
<button @click="handleSaveEditingTheme" class="px-4 md:px-5 py-2 rounded font-bold border border-button bg-button text-button-text hover:bg-button-hover hover:border-button-hover disabled:opacity-60 disabled:cursor-not-allowed text-sm md:text-base">{{ t('common.save') }}</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -18,30 +18,30 @@ const themeParseError = ref<string | null>(null);
|
||||
|
||||
// 定义黑暗模式主题变量
|
||||
const darkModeTheme = {
|
||||
'--app-bg-color': '#212529',
|
||||
'--text-color': '#e9ecef',
|
||||
'--text-color-secondary': '#adb5bd',
|
||||
'--border-color': '#495057',
|
||||
'--link-color': '#BB86FC',
|
||||
'--link-hover-color': '#D1A9FF',
|
||||
'--link-active-color': '#A06CD5',
|
||||
'--link-active-bg-color': 'rgba(160, 108, 213, 0.2)',
|
||||
'--app-bg-color': '#161816',
|
||||
'--text-color': '#d8e6d2',
|
||||
'--text-color-secondary': '#8d9887',
|
||||
'--border-color': '#2b332c',
|
||||
'--link-color': '#37c66a',
|
||||
'--link-hover-color': '#62e38e',
|
||||
'--link-active-color': '#45d978',
|
||||
'--link-active-bg-color': 'rgba(69, 217, 120, 0.14)',
|
||||
'--nav-item-active-bg-color': 'var(--link-active-bg-color)',
|
||||
'--header-bg-color': '#343a40',
|
||||
'--footer-bg-color': '#343a40',
|
||||
'--button-bg-color': 'var(--link-active-color)',
|
||||
'--button-text-color': '#ffffff',
|
||||
'--button-hover-bg-color': '#8E44AD',
|
||||
'--header-bg-color': '#1b1f1b',
|
||||
'--footer-bg-color': '#1b1f1b',
|
||||
'--button-bg-color': '#203126',
|
||||
'--button-text-color': '#9aefad',
|
||||
'--button-hover-bg-color': '#294232',
|
||||
'--icon-color': 'var(--text-color-secondary)',
|
||||
'--icon-hover-color': 'var(--link-hover-color)',
|
||||
'--split-line-color': 'var(--border-color)',
|
||||
'--split-line-hover-color': 'var(--border-color)',
|
||||
'--split-line-hover-color': '#3b6045',
|
||||
'--input-focus-border-color': 'var(--link-active-color)',
|
||||
'--input-focus-glow': 'var(--link-active-color)',
|
||||
'--overlay-bg-color': 'rgba(0, 0, 0, 0.8)',
|
||||
'--color-success': '#5cb85c',
|
||||
'--color-error': '#d9534f',
|
||||
'--color-warning': '#f0ad4e',
|
||||
'--overlay-bg-color': 'rgba(0, 0, 0, 0.84)',
|
||||
'--color-success': '#3fdc78',
|
||||
'--color-error': '#d86a4d',
|
||||
'--color-warning': '#d1a445',
|
||||
'--font-family-sans-serif': 'sans-serif',
|
||||
'--base-padding': '1rem',
|
||||
'--base-margin': '0.5rem'
|
||||
@@ -265,4 +265,4 @@ defineExpose({
|
||||
</div>
|
||||
<p v-if="themeParseError" class="text-error-text bg-error/10 border border-error/30 px-3 py-2 rounded text-sm mt-2">{{ themeParseError }}</p>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user