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 {