diff --git a/go.work.sum b/go.work.sum index 132842daf..f41591a7f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,4 @@ +github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 h1:3/gm/JTX9bX8CpzTgIlrtYpB3EVBDxyg/GY/QdcIEZw= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= diff --git a/src/app/assets/fonts/MartianMono-VariableFont_wdth,wght.ttf b/public/fonts/MartianMono-VariableFont_wdth,wght.ttf similarity index 100% rename from src/app/assets/fonts/MartianMono-VariableFont_wdth,wght.ttf rename to public/fonts/MartianMono-VariableFont_wdth,wght.ttf diff --git a/public/fonts/hack-bold.woff2 b/public/fonts/hack-bold.woff2 new file mode 100644 index 000000000..1155477e9 Binary files /dev/null and b/public/fonts/hack-bold.woff2 differ diff --git a/public/fonts/hack-bolditalic.woff2 b/public/fonts/hack-bolditalic.woff2 new file mode 100644 index 000000000..46ff1c425 Binary files /dev/null and b/public/fonts/hack-bolditalic.woff2 differ diff --git a/public/fonts/hack-italic.woff2 b/public/fonts/hack-italic.woff2 new file mode 100644 index 000000000..1e7630cef Binary files /dev/null and b/public/fonts/hack-italic.woff2 differ diff --git a/public/fonts/hack-regular.woff2 b/public/fonts/hack-regular.woff2 new file mode 100644 index 000000000..524465cf5 Binary files /dev/null and b/public/fonts/hack-regular.woff2 differ diff --git a/src/app/app.less b/src/app/app.less index d7429b8dc..64cc31eb5 100644 --- a/src/app/app.less +++ b/src/app/app.less @@ -1,12 +1,5 @@ @import "./common/themes/themes.less"; -@font-face { - font-family: "Martian Mono"; - src: url("./assets/fonts/MartianMono-VariableFont_wdth,wght.ttf") format("truetype"); - font-weight: normal; - font-style: normal; -} - :root { --fa-style-family: "Font Awesome 6 Sharp"; } @@ -98,7 +91,7 @@ body a { } body code { - font-family: @terminal-font; + font-family: var(--termfontfamily); background-color: transparent; } @@ -312,7 +305,7 @@ a.a-block { } .mono { - font-family: @terminal-font; + font-family: var(--termfontfamily); } } diff --git a/src/app/app.tsx b/src/app/app.tsx index 381c18e3c..d6fca4502 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -105,8 +105,15 @@ class App extends React.Component<{}, {}> { if (dcWait) { setTimeout(() => this.updateDcWait(false), 0); } + // used to force a full reload of the application + let renderVersion = GlobalModel.renderVersion.get(); return ( -
+
diff --git a/src/app/clientsettings/clientsettings.less b/src/app/clientsettings/clientsettings.less index 2d7b97cd3..e0950e44e 100644 --- a/src/app/clientsettings/clientsettings.less +++ b/src/app/clientsettings/clientsettings.less @@ -26,6 +26,10 @@ padding: 0 18px 0 30px; } + .wave-dropdown { + width: 200px; + } + // just marked these as important since we're keeping the // settings-field styles below this intact until we figure out what do with them .settings-field { diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index dd772bc5e..b88884524 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -15,9 +15,6 @@ import "./clientsettings.less"; @mobxReact.observer class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hoveredItemId: string }> { - fontSizeDropdownActive: OV = mobx.observable.box(false, { - name: "clientSettings-fontSizeDropdownActive", - }); errorMessage: OV = mobx.observable.box(null, { name: "ClientSettings-errorMessage" }); @boundMethod @@ -30,7 +27,6 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove @boundMethod handleChangeFontSize(fontSize: string): void { const newFontSize = Number(fontSize); - this.fontSizeDropdownActive.set(false); if (GlobalModel.termFontSize.get() == newFontSize) { return; } @@ -39,10 +35,12 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove } @boundMethod - togglefontSizeDropdown(): void { - mobx.action(() => { - this.fontSizeDropdownActive.set(!this.fontSizeDropdownActive.get()); - })(); + handleChangeFontFamily(fontFamily: string): void { + if (GlobalModel.getTermFontFamily() == fontFamily) { + return; + } + const prtn = GlobalCommandRunner.setTermFontFamily(fontFamily, false); + commandRtnHandler(prtn, this.errorMessage); } @boundMethod @@ -75,6 +73,13 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove return availableFontSizes; } + getFontFamilies(): DropdownItem[] { + const availableFontFamilies: DropdownItem[] = []; + availableFontFamilies.push({ label: "JetBrains Mono", value: "JetBrains Mono" }); + availableFontFamilies.push({ label: "Hack", value: "Hack" }); + return availableFontFamilies; + } + @boundMethod inlineUpdateOpenAIModel(newModel: string): void { const prtn = GlobalCommandRunner.setClientOpenAISettings({ model: newModel }); @@ -140,6 +145,7 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens ); const curFontSize = GlobalModel.termFontSize.get(); + const curFontFamily = GlobalModel.getTermFontFamily(); return (
@@ -161,6 +167,17 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove />
+
+
Term Font Family
+
+ +
+
Client ID
{cdata.clientid}
diff --git a/src/app/common/elements/markdown.less b/src/app/common/elements/markdown.less index e6ef018c1..45102eb75 100644 --- a/src/app/common/elements/markdown.less +++ b/src/app/common/elements/markdown.less @@ -10,14 +10,14 @@ code { background-color: @markdown-highlight; color: @term-white; - font-family: @terminal-font; + font-family: var(--termfontfamily); border-radius: 4px; } code.inline { padding-top: 0; padding-bottom: 0; - font-family: @terminal-font; + font-family: var(--termfontfamily); } .title { diff --git a/src/app/common/prompt/prompt.less b/src/app/common/prompt/prompt.less index 8cef8da6b..fd3f6e1a9 100644 --- a/src/app/common/prompt/prompt.less +++ b/src/app/common/prompt/prompt.less @@ -1,7 +1,9 @@ @import "@/common/themes/themes.less"; .term-prompt { - font-weight: 300; + font-weight: normal; + font-family: var(--termfontfamily); + .icon { margin: 0 4px 0 2px; vertical-align: middle; diff --git a/src/app/common/themes/themes.less b/src/app/common/themes/themes.less index 709c4cd6a..b1b298581 100644 --- a/src/app/common/themes/themes.less +++ b/src/app/common/themes/themes.less @@ -69,7 +69,6 @@ // @font-face declaration lives in app.less @fixed-font: "Martian Mono", sans-serif; -@terminal-font: "JetBrains Mono", sans-serif; @text-s1-font: "Martian Mono", sans-serif; diff --git a/src/app/line/linecomps.tsx b/src/app/line/linecomps.tsx index 8a46619b8..cdeb506cf 100644 --- a/src/app/line/linecomps.tsx +++ b/src/app/line/linecomps.tsx @@ -492,7 +492,8 @@ class LineCmd extends React.Component< maxSize: screen.getMaxContentSize(), idealSize: screen.getIdealContentSize(), termOpts: cmd.getTermOpts(), - termFontSize: GlobalModel.termFontSize.get(), + termFontSize: GlobalModel.getTermFontSize(), + termFontFamily: GlobalModel.getTermFontFamily(), }; } diff --git a/src/app/line/lines.less b/src/app/line/lines.less index 6291d49e7..8d722de0d 100644 --- a/src/app/line/lines.less +++ b/src/app/line/lines.less @@ -132,7 +132,7 @@ position: relative; .cmd-rtnstate-label { - font-family: @terminal-font; + font-family: var(--termfontfamily); position: relative; font-size: 10px; z-index: 2; @@ -145,7 +145,7 @@ } .cmd-rtnstate-diff { - font-family: @terminal-font; + font-family: var(--termfontfamily); color: @term-white; white-space: pre; margin-left: 10px; diff --git a/src/app/workspace/cmdinput/cmdinput.less b/src/app/workspace/cmdinput/cmdinput.less index 0dc5b0397..fbb8d941d 100644 --- a/src/app/workspace/cmdinput/cmdinput.less +++ b/src/app/workspace/cmdinput/cmdinput.less @@ -137,7 +137,8 @@ overflow-wrap: anywhere; border-color: transparent; border: none; - font-family: @terminal-font; + font-family: var(--termfontfamily); + font-weight: normal; &.display-disabled { background-color: #444; } @@ -229,7 +230,8 @@ overflow-wrap: anywhere; border-color: transparent; border: none; - font-family: @terminal-font; + font-family: var(--termfontfamily); + font-weight: normal; flex-shrink: 0; flex-grow: 1; border-radius: 4px; diff --git a/src/electron/emain.ts b/src/electron/emain.ts index c6eb0a6fc..e9bfaa241 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -31,6 +31,7 @@ let oldConsoleLog = console.log; let wasActive = true; let wasInFg = true; let currentGlobalShortcut: string | null = null; +let initialClientData: ClientDataType = null; checkPromptMigrate(); ensureDir(waveHome); @@ -519,6 +520,11 @@ electron.ipcMain.on("wavesrv-status", (event) => { return; }); +electron.ipcMain.on("get-initial-termfontfamily", (event) => { + event.returnValue = initialClientData?.feopts?.termfontfamily; + return; +}); + electron.ipcMain.on("restart-server", (event) => { if (waveSrvProc != null) { waveSrvProc.kill(); @@ -709,6 +715,7 @@ async function createMainWindowWrap() { let clientData: ClientDataType | null = null; try { clientData = await getClientDataPoll(1); + initialClientData = clientData; } catch (e) { console.log("error getting wavesrv clientdata", e.toString()); } diff --git a/src/electron/preload.js b/src/electron/preload.js index cd807c652..66d71d37a 100644 --- a/src/electron/preload.js +++ b/src/electron/preload.js @@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld("api", { ipcRenderer.send("get-last-logs", numberOfLines); ipcRenderer.once("last-logs", (event, data) => callback(data)); }, + getInitialTermFontFamily: () => ipcRenderer.sendSync("get-initial-termfontfamily"), restartWaveSrv: () => ipcRenderer.sendSync("restart-server"), reloadWindow: () => ipcRenderer.sendSync("reload-window"), reregisterGlobalShortcut: (shortcut) => ipcRenderer.sendSync("reregister-global-shortcut", shortcut), diff --git a/src/index.ts b/src/index.ts index 2a2c699b5..dcd3c24ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,13 +9,18 @@ import { App } from "@/app/app"; import * as DOMPurify from "dompurify"; import { loadFonts } from "./util/util"; import * as textmeasure from "./util/textmeasure"; +import { getApi } from "@/models"; // @ts-ignore let VERSION = __WAVETERM_VERSION__; // @ts-ignore let BUILD = __WAVETERM_BUILD__; -loadFonts(); +let initialFontFamily = getApi().getInitialTermFontFamily(); +if (initialFontFamily == null) { + initialFontFamily = "JetBrains Mono"; +} +loadFonts(initialFontFamily); document.addEventListener("DOMContentLoaded", () => { let reactElem = React.createElement(App, null, null); @@ -26,6 +31,7 @@ document.addEventListener("DOMContentLoaded", () => { }); }); +// put some items on the window for debugging (window as any).mobx = mobx; (window as any).sprintf = sprintf; (window as any).DOMPurify = DOMPurify; diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index c2e3ab4f6..139c9b2e9 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -353,6 +353,14 @@ class CommandRunner { return GlobalModel.submitCommand("client", "set", null, kwargs, interactive); } + setTermFontFamily(fontFamily: string, interactive: boolean): Promise { + let kwargs = { + nohist: "1", + termfontfamily: fontFamily, + }; + return GlobalModel.submitCommand("client", "set", null, kwargs, interactive); + } + setClientOpenAISettings(opts: { model?: string; apitoken?: string; maxtokens?: string }): Promise { let kwargs = { nohist: "1", diff --git a/src/models/model.ts b/src/models/model.ts index f2f9dc980..1dd71b203 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -12,6 +12,7 @@ import { genMergeSimpleData, isModKeyPress, isBlank, + loadFonts, } from "@/util/util"; import { WSControl } from "./ws"; import { cmdStatusIsRunning } from "@/app/line/lineutil"; @@ -120,6 +121,10 @@ class Model { }); packetSeqNum: number = 0; + renderVersion: OV = mobx.observable.box(0, { + name: "renderVersion", + }); + private constructor() { this.clientId = getApi().getId(); this.isDev = getApi().getIsDev(); @@ -185,6 +190,12 @@ class Model { return (window as any).GlobalModel; } + bumpRenderVersion() { + mobx.action(() => { + this.renderVersion.set(this.renderVersion.get() + 1); + })(); + } + getNextPacketSeqNum(): number { this.packetSeqNum++; return this.packetSeqNum; @@ -314,6 +325,19 @@ class Model { return appconst.ProdServerEndpoint; } + getTermFontFamily(): string { + let cdata = this.clientData.get(); + let ff = cdata?.feopts?.termfontfamily; + if (ff == null) { + ff = "JetBrains Mono, monospace"; + } + return ff; + } + + getTermFontSize(): number { + return this.termFontSize.get(); + } + setTermFontSize(fontSize: number) { if (fontSize < appconst.MinFontSize) { fontSize = appconst.MinFontSize; @@ -323,6 +347,7 @@ class Model { } mobx.action(() => { this.termFontSize.set(fontSize); + this.bumpRenderVersion(); })(); } @@ -1114,6 +1139,15 @@ class Model { shortcut = clientData?.clientopts?.globalshortcut; } getApi().reregisterGlobalShortcut(shortcut); + let fontFamily = clientData?.feopts?.termfontfamily; + if (fontFamily == null) { + fontFamily = "JetBrains Mono"; + } + loadFonts(fontFamily); + document.fonts.ready.then(() => { + clearMonoFontCache(); + this.bumpRenderVersion(); + }); } submitCommandPacket(cmdPk: FeCmdPacketType, interactive: boolean): Promise { @@ -1452,4 +1486,4 @@ class Model { } } -export { Model }; +export { Model, getApi }; diff --git a/src/models/remotes.ts b/src/models/remotes.ts index db3c65d1c..b57cbd052 100644 --- a/src/models/remotes.ts +++ b/src/models/remotes.ts @@ -190,7 +190,8 @@ class RemotesModel { }, focusHandler: this.setRemoteTermWrapFocus.bind(this), isRunning: true, - fontSize: this.globalModel.termFontSize.get(), + fontSize: this.globalModel.getTermFontSize(), + fontFamily: this.globalModel.getTermFontFamily(), ptyDataSource: getTermPtyData, onUpdateContentHeight: null, }); diff --git a/src/models/screen.ts b/src/models/screen.ts index 624554711..ba8af8cab 100644 --- a/src/models/screen.ts +++ b/src/models/screen.ts @@ -588,7 +588,8 @@ class Screen { focusHandler: (focus: boolean) => this.setLineFocus(line.linenum, focus), isRunning: cmd.isRunning(), customKeyHandler: this.termCustomKeyHandler.bind(this), - fontSize: this.globalModel.termFontSize.get(), + fontSize: this.globalModel.getTermFontSize(), + fontFamily: this.globalModel.getTermFontFamily(), ptyDataSource: getTermPtyData, onUpdateContentHeight: (termContext: RendererContext, height: number) => { this.globalModel.setContentHeight(termContext, height); diff --git a/src/models/speciallinecontainer.ts b/src/models/speciallinecontainer.ts index b055631fa..77c7c98a2 100644 --- a/src/models/speciallinecontainer.ts +++ b/src/models/speciallinecontainer.ts @@ -90,7 +90,8 @@ class SpecialLineContainer { focusHandler: null, isRunning: cmd.isRunning(), customKeyHandler: null, - fontSize: this.globalModel.termFontSize.get(), + fontSize: this.globalModel.getTermFontSize(), + fontFamily: this.globalModel.getTermFontFamily(), ptyDataSource: getTermPtyData, onUpdateContentHeight: null, }); diff --git a/src/plugins/code/code.tsx b/src/plugins/code/code.tsx index 521276b73..69c4372ec 100644 --- a/src/plugins/code/code.tsx +++ b/src/plugins/code/code.tsx @@ -337,8 +337,8 @@ class SourceCodeRenderer extends React.Component< onMount={this.handleEditorDidMount} options={{ scrollBeyondLastLine: false, - fontSize: GlobalModel.termFontSize.get() * 0.9, - /* fontFamily: "Martian Mono", */ + fontSize: GlobalModel.getTermFontSize(), + fontFamily: GlobalModel.getTermFontFamily(), readOnly: !this.getAllowEditing(), }} onChange={this.handleEditorChange} diff --git a/src/plugins/terminal/term.ts b/src/plugins/terminal/term.ts index 2beb6baf0..cecb812e5 100644 --- a/src/plugins/terminal/term.ts +++ b/src/plugins/terminal/term.ts @@ -31,6 +31,7 @@ type TermWrapOpts = { isRunning: boolean; customKeyHandler?: (event: any, termWrap: TermWrap) => boolean; fontSize: number; + fontFamily: string; ptyDataSource: (termContext: TermContextUnion) => Promise; onUpdateContentHeight: (termContext: RendererContext, height: number) => void; }; @@ -53,6 +54,7 @@ class TermWrap { focusHandler: (focus: boolean) => void; isRunning: boolean; fontSize: number; + fontFamily: string; onUpdateContentHeight: (termContext: RendererContext, height: number) => void; ptyDataSource: (termContext: TermContextUnion) => Promise; initializing: boolean; @@ -67,6 +69,7 @@ class TermWrap { this.focusHandler = opts.focusHandler; this.isRunning = opts.isRunning; this.fontSize = opts.fontSize; + this.fontFamily = opts.fontFamily; this.ptyDataSource = opts.ptyDataSource; this.onUpdateContentHeight = opts.onUpdateContentHeight; this.initializing = true; @@ -88,7 +91,7 @@ class TermWrap { rows: this.termSize.rows, cols: this.termSize.cols, fontSize: opts.fontSize, - fontFamily: "JetBrains Mono", + fontFamily: opts.fontFamily, theme: { foreground: terminal.foreground, background: terminal.background }, }); this.terminal.loadAddon( diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index f93f76423..fb8b2354e 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -471,6 +471,7 @@ declare global { idealSize: WindowSize; termOpts: TermOptsType; termFontSize: number; + termFontFamily: string; }; type RendererOptsUpdate = { @@ -558,6 +559,7 @@ declare global { type FeOptsType = { termfontsize: number; + termfontfamily: string; }; type ConfirmFlagsType = { @@ -589,6 +591,13 @@ declare global { fullscreen: boolean; }; + type KeyModsType = { + meta?: boolean; + ctrl?: boolean; + alt?: boolean; + shift?: boolean; + }; + type ClientDataType = { clientid: string; userid: string; @@ -847,6 +856,7 @@ declare global { getPlatform: () => string; getAuthKey: () => string; getWaveSrvStatus: () => boolean; + getInitialTermFontFamily: () => string; restartWaveSrv: () => boolean; reloadWindow: () => void; openExternalLink: (url: string) => void; diff --git a/src/util/util.ts b/src/util/util.ts index 1ad3e7d26..33e2a863e 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -242,9 +242,19 @@ function incObs(inum: mobx.IObservableValue) { })(); } -// @check:font -function loadFonts() { - const jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", { +function addToFontFaceSet(fontFaceSet: FontFaceSet, fontFace: FontFace) { + // any cast to work around typing issue + (fontFaceSet as any).add(fontFace); +} + +let isJetBrainsMonoLoaded = false; + +function loadJetBrainsMonoFont() { + if (isJetBrainsMonoLoaded) { + return; + } + isJetBrainsMonoLoaded = true; + let jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", { style: "normal", weight: "400", }); @@ -256,19 +266,78 @@ function loadFonts() { style: "normal", weight: "700", }); - const faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", { - style: "normal", - weight: "normal", - }); - const docFonts: any = document.fonts; // work around ts typing issue - docFonts.add(jbmFontNormal); - docFonts.add(jbmFont200); - docFonts.add(jbmFont700); - docFonts.add(faFont); + addToFontFaceSet(document.fonts, jbmFontNormal); + addToFontFaceSet(document.fonts, jbmFont200); + addToFontFaceSet(document.fonts, jbmFont700); jbmFontNormal.load(); jbmFont200.load(); jbmFont700.load(); +} + +let isHackFontLoaded = false; + +function loadHackFont() { + if (isHackFontLoaded) { + return; + } + isHackFontLoaded = true; + let hackRegular = new FontFace("Hack", "url('public/fonts/hack-regular.woff2')", { + style: "normal", + weight: "400", + }); + let hackBold = new FontFace("Hack", "url('public/fonts/hack-bold.woff2')", { + style: "normal", + weight: "700", + }); + let hackItalic = new FontFace("Hack", "url('public/fonts/hack-italic.woff2')", { + style: "italic", + weight: "400", + }); + let hackBoldItalic = new FontFace("Hack", "url('public/fonts/hack-bolditalic.woff2')", { + style: "italic", + weight: "700", + }); + addToFontFaceSet(document.fonts, hackRegular); + addToFontFaceSet(document.fonts, hackBold); + addToFontFaceSet(document.fonts, hackItalic); + addToFontFaceSet(document.fonts, hackBoldItalic); + hackRegular.load(); + hackBold.load(); + hackItalic.load(); + hackBoldItalic.load(); +} + +let isBaseFontsLoaded = false; + +function loadBaseFonts() { + if (isBaseFontsLoaded) { + return; + } + isBaseFontsLoaded = true; + let faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", { + style: "normal", + weight: "normal", + }); + let mmFont = new FontFace("Martian Mono", "url(public/fonts/MartianMono-VariableFont_wdth,wght.ttf)", { + style: "normal", + weight: "normal", + }); + addToFontFaceSet(document.fonts, faFont); + addToFontFaceSet(document.fonts, mmFont); faFont.load(); + mmFont.load(); +} + +function loadFonts(termFont: string) { + loadBaseFonts(); + loadJetBrainsMonoFont(); + if (termFont == "Hack") { + loadHackFont(); + } + document.documentElement.style.setProperty( + "--termfontfamily", + '"' + termFont + '"' + ', "JetBrains Mono", monospace' + ); } const DOW_STRS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 5189942db..387b0ca34 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -5134,6 +5134,24 @@ func validateOpenAIModel(model string) error { return nil } +const MaxFontFamilyLen = 50 + +var fontfamilyRe = regexp.MustCompile(`^[a-zA-Z0-9_ -]+$`) + +func validateFontFamily(fontFamily string) error { + if len(fontFamily) == 0 { + return nil + } + if len(fontFamily) > MaxFontFamilyLen { + return fmt.Errorf("invalid font family, too long") + } + m := fontfamilyRe.MatchString(fontFamily) + if !m { + return fmt.Errorf("invalid font family, must match %q", fontfamilyRe.String()) + } + return nil +} + func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { @@ -5156,6 +5174,20 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc } varsUpdated = append(varsUpdated, "termfontsize") } + if fontFamilyStr, found := pk.Kwargs["termfontfamily"]; found { + newFontFamily := fontFamilyStr + err = validateFontFamily(newFontFamily) + if err != nil { + return nil, err + } + feOpts := clientData.FeOpts + feOpts.TermFontFamily = newFontFamily + err = sstore.UpdateClientFeOpts(ctx, feOpts) + if err != nil { + return nil, fmt.Errorf("error updating client feopts: %v", err) + } + varsUpdated = append(varsUpdated, "termfontfamily") + } if apiToken, found := pk.Kwargs["openaiapitoken"]; found { err = validateOpenAIAPIToken(apiToken) if err != nil { @@ -5244,7 +5276,7 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc } } if len(varsUpdated) == 0 { - return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices"}, "or", false)) + return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "termfontfamily", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices"}, "or", false)) } clientData, err = sstore.EnsureClientData(ctx) if err != nil { diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index df8e72e5d..7e4ba0807 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -295,7 +295,8 @@ type ClientOptsType struct { } type FeOptsType struct { - TermFontSize int `json:"termfontsize,omitempty"` + TermFontSize int `json:"termfontsize,omitempty"` + TermFontFamily string `json:"termfontfamily,omitempty"` } type ReleaseInfoType struct {