diff --git a/src/app/line/line.less b/src/app/line/line.less index 661cda984..9519c2d81 100644 --- a/src/app/line/line.less +++ b/src/app/line/line.less @@ -235,7 +235,7 @@ .cmdtext-expanded { overflow: auto; - max-height: calc(var(--termlineheight) * 3.3); + max-height: calc(var(--termlineheight) * 3); white-space: pre; color: var(--term-bright-white); font-weight: bold; diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index b525fb391..d2c645f3a 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -25,9 +25,31 @@ import * as lineutil from "./lineutil"; import { ErrorBoundary } from "@/common/error/errorboundary"; import * as appconst from "@/app/appconst"; import * as util from "@/util/util"; +import * as textmeasure from "@/util/textmeasure"; import "./line.less"; +const DebugHeightProblems = false; +const MinLine = 0; +const MaxLine = 1000; +let heightLog = {}; +(window as any).heightLog = heightLog; +(window as any).findHeightProblems = function () { + for (let linenum in heightLog) { + let lh = heightLog[linenum]; + if (lh.heightArr == null || lh.heightArr.length < 2) { + continue; + } + let firstHeight = lh.heightArr[0]; + for (let i = 1; i < lh.heightArr.length; i++) { + if (lh.heightArr[i] != firstHeight) { + console.log("line", linenum, "heights", lh.heightArr); + break; + } + } + } +}; + dayjs.extend(localizedFormat); function cmdHasError(cmd: Cmd): boolean { @@ -459,6 +481,12 @@ class LineCmd extends React.Component< if (elem != null) { curHeight = elem.offsetHeight; } + let linenum = line.linenum; + if (DebugHeightProblems && linenum >= MinLine && linenum <= MaxLine) { + heightLog[linenum] = heightLog[linenum] || {}; + heightLog[linenum].heightArr = heightLog[linenum].heightArr || []; + heightLog[linenum].heightArr.push(curHeight); + } if (this.lastHeight == curHeight) { return; } @@ -486,12 +514,11 @@ class LineCmd extends React.Component< getTerminalRendererHeight(cmd: Cmd): number { const { screen, line, width } = this.props; - let height = 45 + 24; // height of zero height terminal const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); - if (usedRows > 0) { - height = 48 + 24 + termHeightFromRows(usedRows, GlobalModel.getTermFontSize(), cmd.getTermMaxRows()); + if (usedRows == 0) { + return 0; } - return height; + return termHeightFromRows(usedRows, GlobalModel.getTermFontSize(), cmd.getTermMaxRows()); } @boundMethod @@ -512,18 +539,18 @@ class LineCmd extends React.Component< renderSimple() { const { screen, line } = this.props; const cmd = screen.getCmd(line); - let height: number = 0; + let contentHeight: number = 0; if (isBlank(line.renderer) || line.renderer == "terminal") { - height = this.getTerminalRendererHeight(cmd); + contentHeight = this.getTerminalRendererHeight(cmd); } else { - // header is 16px tall with hide-prompt, 36px otherwise const { screen, line, width } = this.props; - const hidePrompt = getIsHidePrompt(line); - const usedRows = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); - height = (hidePrompt ? 16 + 6 : 36 + 6) + usedRows; + contentHeight = screen.getUsedRows(lineutil.getRendererContext(line), line, cmd, width); + } + const mainDivCn = cn("line", "line-cmd"); + if (DebugHeightProblems && line.linenum >= MinLine && line.linenum <= MaxLine) { + heightLog[line.linenum] = heightLog[line.linenum] || {}; + heightLog[line.linenum].contentHeight = contentHeight; } - const formattedTime = lineutil.getLineDateTimeStr(line.ts); - const mainDivCn = cn("line", "line-cmd", "line-simple"); return (
-
- -
{formattedTime}
-
+ +
); } @@ -695,6 +722,12 @@ class LineCmd extends React.Component< const termFontSize = GlobalModel.getTermFontSize(); const containerType = screen.getContainerType(); const isMinimized = line.linestate["wave:min"] && containerType == appconst.LineContainer_Main; + const lhv: LineChromeHeightVars = { + numCmdLines: lineutil.countCmdLines(cmd.getCmdStr()), + zeroHeight: isMinimized, + hasLine2: !hidePrompt, + }; + const chromeHeight = textmeasure.calcLineChromeHeight(GlobalModel.lineHeightEnv, lhv); return (
this.sidebarSize.set(size))(); diff --git a/src/app/workspace/workspaceview.tsx b/src/app/workspace/workspaceview.tsx index db81e444a..eee8db511 100644 --- a/src/app/workspace/workspaceview.tsx +++ b/src/app/workspace/workspaceview.tsx @@ -12,7 +12,7 @@ import { CmdInput } from "./cmdinput/cmdinput"; import { ScreenView } from "./screen/screenview"; import { ScreenTabs } from "./screen/tabs"; import { ErrorBoundary } from "@/common/error/errorboundary"; -import { MagicLayout } from "../magiclayout"; +import * as textmeasure from "@/util/textmeasure"; import "./workspace.less"; dayjs.extend(localizedFormat); @@ -34,7 +34,7 @@ class WorkspaceView extends React.Component<{}, {}> { let activeScreen = session.getActiveScreen(); let cmdInputHeight = model.inputModel.cmdInputHeight.get(); if (cmdInputHeight == 0) { - cmdInputHeight = MagicLayout.CmdInputHeight; // this is the base size of cmdInput (measured using devtools) + cmdInputHeight = textmeasure.baseCmdInputHeight(GlobalModel.lineHeightEnv); // this is the base size of cmdInput (measured using devtools) } let isHidden = GlobalModel.activeMainView.get() != "session"; let mainSidebarModel = GlobalModel.mainSidebarModel; diff --git a/src/models/forwardlinecontainer.ts b/src/models/forwardlinecontainer.ts index d04b6abb8..948b6f50d 100644 --- a/src/models/forwardlinecontainer.ts +++ b/src/models/forwardlinecontainer.ts @@ -8,6 +8,7 @@ import { Model } from "./model"; import { GlobalCommandRunner } from "./global"; import { Cmd } from "./cmd"; import { Screen } from "./screen"; +import * as lineutil from "@/app/line/lineutil"; class ForwardLineContainer { globalModel: Model; @@ -30,7 +31,7 @@ class ForwardLineContainer { if (termWrap != null) { let fontSize = this.globalModel.getTermFontSize(); let cols = windowWidthToCols(winSize.width, fontSize); - let rows = windowHeightToRows(winSize.height, fontSize); + let rows = windowHeightToRows(Model.getInstance().lineHeightEnv, this.winSize.height); termWrap.resizeCols(cols); GlobalCommandRunner.resizeScreen(this.screen.screenId, rows, cols, { include: [this.lineId] }); } diff --git a/src/models/model.ts b/src/models/model.ts index 94452043e..025fd4e63 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -104,6 +104,7 @@ class Model { name: "devicePixelRatio", }); remotesModel: RemotesModel; + lineHeightEnv: LineHeightEnv; inputModel: InputModel; pluginsModel: PluginsModel; @@ -343,27 +344,31 @@ class Model { return this.termFontSize.get(); } - updateTermFontSizeVars(fontSize: number, force: boolean) { - if (!force && fontSize == this.termFontSize.get()) { - return; - } - if (fontSize < appconst.MinFontSize) { - fontSize = appconst.MinFontSize; - } - if (fontSize > appconst.MaxFontSize) { - fontSize = appconst.MaxFontSize; - } + updateTermFontSizeVars() { + let lhe = this.recomputeLineHeightEnv(); + mobx.action(() => { + this.bumpRenderVersion(); + this.setStyleVar("--termfontsize", lhe.fontSize + "px"); + this.setStyleVar("--termlineheight", lhe.lineHeight + "px"); + this.setStyleVar("--termpad", lhe.pad + "px"); + this.setStyleVar("--termfontsize-sm", lhe.fontSizeSm + "px"); + this.setStyleVar("--termlineheight-sm", lhe.lineHeightSm + "px"); + })(); + } + + recomputeLineHeightEnv(): LineHeightEnv { + const fontSize = this.getTermFontSize(); const fontSizeSm = fontSize - 2; const monoFontSize = getMonoFontSize(fontSize); const monoFontSizeSm = getMonoFontSize(fontSizeSm); - mobx.action(() => { - this.bumpRenderVersion(); - this.setStyleVar("--termfontsize", fontSize + "px"); - this.setStyleVar("--termlineheight", monoFontSize.height + "px"); - this.setStyleVar("--termpad", monoFontSize.pad + "px"); - this.setStyleVar("--termfontsize-sm", fontSizeSm + "px"); - this.setStyleVar("--termlineheight-sm", monoFontSizeSm.height + "px"); - })(); + this.lineHeightEnv = { + fontSize: fontSize, + fontSizeSm: fontSizeSm, + lineHeight: monoFontSize.height, + lineHeightSm: monoFontSizeSm.height, + pad: monoFontSize.pad, + }; + return this.lineHeightEnv; } setStyleVar(name: string, value: string) { @@ -1181,11 +1186,11 @@ class Model { loadFonts(newFontFamily); document.fonts.ready.then(() => { clearMonoFontCache(); - this.updateTermFontSizeVars(this.termFontSize.get(), true); // forces an update of css vars + this.updateTermFontSizeVars(); // forces an update of css vars this.bumpRenderVersion(); }); } else if (fsUpdated) { - this.updateTermFontSizeVars(newFontSize, true); + this.updateTermFontSizeVars(); } } diff --git a/src/models/screen.ts b/src/models/screen.ts index 0bec6e49c..6b88e29db 100644 --- a/src/models/screen.ts +++ b/src/models/screen.ts @@ -16,6 +16,7 @@ import { GlobalCommandRunner } from "./global"; import { Cmd } from "./cmd"; import { ScreenLines } from "./screenlines"; import { getTermPtyData } from "@/util/modelutil"; +import * as textmeasure from "@/util/textmeasure"; class Screen { globalModel: Model; @@ -402,8 +403,9 @@ class Screen { return; } this.lastScreenSize = winSize; + let useableHeight = winSize.height - textmeasure.calcMaxLineChromeHeight(this.globalModel.lineHeightEnv); let cols = windowWidthToCols(winSize.width, this.globalModel.getTermFontSize()); - let rows = windowHeightToRows(winSize.height, this.globalModel.getTermFontSize()); + let rows = windowHeightToRows(this.globalModel.lineHeightEnv, winSize.height); this._termSizeCallback(rows, cols); } @@ -417,7 +419,8 @@ class Screen { let minSize = MagicLayout.ScreenMinContentSize; let maxSize = MagicLayout.ScreenMaxContentSize; let width = boundInt(winSize.width - MagicLayout.ScreenMaxContentWidthBuffer, minSize, maxSize); - let height = boundInt(winSize.height - MagicLayout.ScreenMaxContentHeightBuffer, minSize, maxSize); + let maxLineBuffer = textmeasure.calcMaxLineChromeHeight(this.globalModel.lineHeightEnv); + let height = boundInt(winSize.height - maxLineBuffer, minSize, maxSize); return { width, height }; } diff --git a/src/plugins/openai/openai.less b/src/plugins/openai/openai.less index 359b2b561..01071aa51 100644 --- a/src/plugins/openai/openai.less +++ b/src/plugins/openai/openai.less @@ -1,4 +1,8 @@ .openai-renderer { + font-family: var(--termfontfamily); + font-size: var(--termfontsize); + line-height: var(--termlineheight); + .openai-message { display: flex; flex-direction: row; @@ -19,11 +23,11 @@ .openai-content-user { color: var(--app-text-color); font-family: var(--markdown-font); - font-size: var(--markdown-font-size); font-weight: normal; } .openai-content-assistant { + font-family: var(--markdown-font); color: var(--app-text-color); } diff --git a/src/plugins/terminal/term.ts b/src/plugins/terminal/term.ts index aa5258f27..f39417d90 100644 --- a/src/plugins/terminal/term.ts +++ b/src/plugins/terminal/term.ts @@ -305,7 +305,7 @@ class TermWrap { resizeWindow(size: WindowSize): void { let cols = windowWidthToCols(size.width, this.fontSize); - let rows = windowHeightToRows(size.height, this.fontSize); + let rows = windowHeightToRows(GlobalModel.lineHeightEnv, size.height); this.resize({ rows, cols }); } diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index a4187d49c..56a286269 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -845,6 +845,22 @@ declare global { getContainerType(): LineContainerStrs; }; + // the "environment" for computing a line's height (stays constant for a given term font family / size) + type LineHeightEnv = { + fontSize: number; + fontSizeSm: number; + lineHeight: number; + lineHeightSm: number; + pad: number; + }; + + // the "variables" for computing a line's height (changes per line) + type LineChromeHeightVars = { + numCmdLines: number; + zeroHeight: boolean; + hasLine2: boolean; + }; + type MonoFontSize = { height: number; width: number; diff --git a/src/util/textmeasure.ts b/src/util/textmeasure.ts index 67ea95243..0d7c24c2e 100644 --- a/src/util/textmeasure.ts +++ b/src/util/textmeasure.ts @@ -71,9 +71,8 @@ function windowWidthToCols(width: number, fontSize: number): number { return cols; } -function windowHeightToRows(height: number, fontSize: number): number { - let dr = getMonoFontSize(fontSize); - let rows = Math.floor((height - MagicLayout.ScreenMaxContentHeightBuffer) / dr.height) - 1; +function windowHeightToRows(lhe: LineHeightEnv, height: number): number { + let rows = Math.floor((height - calcMaxLineChromeHeight(lhe)) / lhe.lineHeight) - 1; if (rows <= 0) { rows = 1; } @@ -99,6 +98,28 @@ function termHeightFromRows(rows: number, fontSize: number, totalRows: number): return Math.ceil(realHeight * rows); } +function calcLineChromeHeight(lhe: LineHeightEnv, lhv: LineChromeHeightVars): number { + const topPadding = lhe.pad * 2; + const botPadding = lhe.pad * 2 + 1; + const headerLine1 = lhe.lineHeightSm; + const headerLine2 = lhv.hasLine2 ? lhe.lineHeight * Math.min(lhv.numCmdLines, 3) + 2 : 0; + const contentSpacer = lhv.zeroHeight ? 0 : lhe.pad + 2; + return topPadding + botPadding + headerLine1 + headerLine2 + contentSpacer; +} + +function calcMaxLineChromeHeight(lhe: LineHeightEnv): number { + return calcLineChromeHeight(lhe, { numCmdLines: 3, hasLine2: true, zeroHeight: false }); +} + +function baseCmdInputHeight(lhe: LineHeightEnv): number { + const topPadding = lhe.pad * 2; + const botPadding = lhe.pad * 2; + const border = 2; + const cmdInputContext = lhe.lineHeight; + const textArea = lhe.lineHeight + lhe.pad * 2 + lhe.pad * 2; // lineHeight + innerPad + outerPad + return topPadding + botPadding + border + cmdInputContext + textArea; +} + export { measureText, getMonoFontSize, @@ -108,4 +129,7 @@ export { termHeightFromRows, clearMonoFontCache, MonoFontSizes, + calcLineChromeHeight, + calcMaxLineChromeHeight, + baseCmdInputHeight, };