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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
.DS_Store
/promo
/voice
.kigi
.mimosa
.rpi
30 changes: 26 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ anyhow = "1"
base64 = "0.22"
base64-simd = "0.8"
dirs = "7"
ely-gpui-component = { git = "https://github.com/ELY-Open-Source/Ely-GPUI-Components.git", rev = "294a8323c08a06bd8e7782d16acee43b6bb0c5eb" }
ely-gpui-component = { path = "vendor/ely" }
env_logger = "0.11"
flate2 = { version = "1.1", default-features = false, features = ["rust_backend"] }
futures = "0.3"
Expand All @@ -23,6 +23,7 @@ getrandom = "0.3"
gpui = { package = "gpui-pre", version = "=0.3.8", default-features = false }
gpui_platform = { package = "gpui-pre-platform", version = "=0.3.8", features = ["font-kit", "wayland", "x11"] }
image = { version = "0.25", default-features = false, features = ["png"] }
lzma-rs = "0.3"
log = "0.4"
memchr = "2.8"
polling = "3"
Expand Down Expand Up @@ -77,7 +78,8 @@ windows-sys = { version = "0.61", features = ["Win32_Security", "Win32_System_IO
alacritty_terminal = { git = "https://github.com/ELY-Open-Source/alacritty.git", rev = "d2781caf4908e58404a31efa0bbac03c562380bc" }
vte = { git = "https://github.com/ELY-Open-Source/vte.git", rev = "8fa83a9b51cadcad3269e1ef3ab059672d875983" }
xim-ctext = { path = "vendor/xim-ctext" }
gpui-pre-windows = { git = "https://github.com/ELY-Open-Source/gpui-pre-windows.git", rev = "8b9ce598956472934fcd54a060a9153575877475" }
gpui-pre = { path = "vendor/gpui-pre" }
gpui-pre-windows = { path = "vendor/gpui-pre-windows" }
gpui-pre-macos = { git = "https://github.com/ELY-Open-Source/gpui-pre-macos.git", rev = "b3e25d5c00b0e97c5c8f2e3fe0c22da16aa293bb" }
gpui-pre-linux = { git = "https://github.com/ELY-Open-Source/gpui-pre-linux.git", rev = "db0ff3c44e3fb1a3ee6ba6a06a44d94bf5d1ff32" }

Expand Down
Binary file removed assets/fonts/JetBrainsMonoNerdFontMono-Regular.ttf
Binary file not shown.
Binary file added assets/fonts/JetBrainsMonoNerdFontMono.tar.xz
Binary file not shown.
8 changes: 6 additions & 2 deletions scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --quiet
fail=0
files=$(git ls-files -co --exclude-standard '*.rs')
# vendor/gpui-pre is zed's snapshot, patched to name a family's weights; vendor/ely is
# Ely's sources, patched so a font picker offers monospaced families alone. Both keep
# upstream's shape.
ours() { git ls-files -co --exclude-standard "$@" | grep -v -e '^vendor/gpui-pre/' -e '^vendor/ely/' -e '^vendor/gpui-pre-windows/'; }
files=$(ours '*.rs')
multi=$(awk 'FNR==1{prev=0} /^[[:space:]]*\/\// {if(prev) print FILENAME":"FNR; prev=1; next} {prev=0}' $files)
[ -z "$multi" ] || { echo "multi-line comments:"; echo "$multi"; fail=1; }
for f in $(git ls-files -co --exclude-standard); do
for f in $(ours); do
case ${f##*/} in Cargo.lock | package-lock.json) continue ;; esac
grep -Iq '' "$f" || continue
n=$(wc -l < "$f")
Expand Down
39 changes: 39 additions & 0 deletions scripts/fonts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh
# Packs the Nerd Font faces the terminal registers into one xz archive.
#
# The release tarball carries 98 faces across six variants and unpacks to 243 MB;
# the terminal draws JetBrainsMonoNerdFontMono's eight weights and their italics,
# and xz packs those to about 2.5 MB since the faces share nearly every outline.
# Run this after bumping NF to a new release.
set -eu

NF=${NF:-v3.5.1}
URL="https://github.com/ryanoasis/nerd-fonts/releases/download/${NF}/JetBrainsMono.tar.xz"
WEIGHTS="Thin ExtraLight Light Regular Medium SemiBold Bold ExtraBold"

root=$(cd "$(dirname "$0")/.." && pwd)
out=$root/assets/fonts/JetBrainsMonoNerdFontMono.tar.xz
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

echo "fonts: fetching $URL"
curl -fsSL "$URL" -o "$work/release.tar.xz"
tar -xf "$work/release.tar.xz" -C "$work"

pick=$work/pick
mkdir "$pick"
for weight in $WEIGHTS; do
for face in "JetBrainsMonoNerdFontMono-$weight.ttf" \
"JetBrainsMonoNerdFontMono-${weight}Italic.ttf"; do
file=$work/$face
[ -f "$file" ] || { echo "fonts: no $face in $NF" >&2; exit 1; }
cp "$file" "$pick/"
done
done

tar -cf "$work/weights.tar" -C "$pick" .
xz -9e --check=crc64 -c "$work/weights.tar" > "$out"

raw=$(cat "$pick"/*.ttf | wc -c)
packed=$(wc -c < "$out")
echo "fonts: $raw bytes raw, $packed packed, written to $out"
23 changes: 12 additions & 11 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::term::mac;
use crate::{
blur, i18n, menus,
settings::{self, Place, SettingsStore},
term::{ICONS, Launch, MONO, config, input, shell::ShellIntegration, spawn},
term::{ICONS, Launch, MONO, config, input, registered, shell::ShellIntegration, spawn},
themes, updates,
workspace::{self, TITLE, Workspace},
};
Expand All @@ -29,16 +29,17 @@ pub const APP_ID: &str = "com.zacharyzhang.elyterminal";
/// Fonts, settings, language and scripts, checked before any window.
pub fn start(cx: &mut App) -> anyhow::Result<()> {
cx.text_system()
.add_fonts(vec![
Cow::Borrowed(include_bytes!("../assets/fonts/JetBrainsMono-Bold.ttf")),
Cow::Borrowed(include_bytes!("../assets/fonts/JetBrainsMono-Italic.ttf")),
Cow::Borrowed(include_bytes!(
"../assets/fonts/JetBrainsMono-BoldItalic.ttf"
)),
Cow::Borrowed(include_bytes!(
"../assets/fonts/JetBrainsMonoNerdFontMono-Regular.ttf"
)),
])
.add_fonts({
let mut fonts: Vec<Cow<'static, [u8]>> = vec![
Cow::Borrowed(include_bytes!("../assets/fonts/JetBrainsMono-Bold.ttf")),
Cow::Borrowed(include_bytes!("../assets/fonts/JetBrainsMono-Italic.ttf")),
Cow::Borrowed(include_bytes!(
"../assets/fonts/JetBrainsMono-BoldItalic.ttf"
)),
];
fonts.extend(registered()?);
fonts
})
.context("app: the bundled fonts failed to register")?;
let names = cx.text_system().all_font_names();
for family in [MONO, ICONS] {
Expand Down
16 changes: 16 additions & 0 deletions src/i18n/en.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ pub const MESSAGES: &[(&str, &str)] = &[
("et.appearance.separate_dark", "Separate dark theme"),
("et.appearance.font", "Font"),
("et.appearance.family", "Family"),
("et.appearance.monospace_only", "Monospaced only"),
(
"et.appearance.monospace_hint",
"Offers only families whose characters share one width",
),
("et.appearance.fallbacks", "Fallbacks"),
(
"et.appearance.fallbacks_hint",
Expand Down Expand Up @@ -71,6 +76,17 @@ pub const MESSAGES: &[(&str, &str)] = &[
("et.appearance.add_fallback", "Add fallback"),
("et.appearance.size", "Size"),
("et.appearance.weight", "Weight"),
("et.weight.thin", "Thin"),
("et.weight.extralight", "Extra-Light"),
("et.weight.light", "Light"),
("et.weight.semilight", "Semi-Light"),
("et.weight.normal", "Normal"),
("et.weight.medium", "Medium"),
("et.weight.semibold", "Semi-Bold"),
("et.weight.bold", "Bold"),
("et.weight.extrabold", "Extra-Bold"),
("et.weight.black", "Black"),
("et.weight.extrablack", "Extra-Black"),
("et.appearance.line_height", "Line height"),
("et.appearance.ligatures", "Ligatures"),
("et.appearance.cursor", "Cursor"),
Expand Down
13 changes: 13 additions & 0 deletions src/i18n/zh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub const MESSAGES: &[(&str, &str)] = &[
("et.appearance.separate_dark", "单独的深色主题"),
("et.appearance.font", "字体"),
("et.appearance.family", "字体系列"),
("et.appearance.monospace_only", "仅等宽字体"),
("et.appearance.monospace_hint", "只提供字符等宽的字体家族"),
("et.appearance.fallbacks", "后备字体"),
(
"et.appearance.fallbacks_hint",
Expand Down Expand Up @@ -68,6 +70,17 @@ pub const MESSAGES: &[(&str, &str)] = &[
("et.appearance.add_fallback", "添加后备字体"),
("et.appearance.size", "大小"),
("et.appearance.weight", "字重"),
("et.weight.thin", "极细体"),
("et.weight.extralight", "特细体"),
("et.weight.light", "细体"),
("et.weight.semilight", "次细体"),
("et.weight.normal", "常规"),
("et.weight.medium", "中等"),
("et.weight.semibold", "半粗体"),
("et.weight.bold", "粗体"),
("et.weight.extrabold", "特粗体"),
("et.weight.black", "黑体"),
("et.weight.extrablack", "超黑体"),
("et.appearance.line_height", "行高"),
("et.appearance.ligatures", "连字"),
("et.appearance.cursor", "光标"),
Expand Down
36 changes: 32 additions & 4 deletions src/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,14 @@ pub struct Settings {
pub font_family: String,
/// Families tried in order for a character Font family lacks.
pub font_fallbacks: Vec<String>,
/// Offers only monospaced families in the pickers.
pub monospace_only: bool,
/// Draws CJK characters; empty leaves them to the family.
pub cjk_font: String,
pub font_size: f32,
/// Normal text's weight; bold sits 300 above it.
pub font_weight: f32,
/// Normal text's weight, as Windows Terminal's `fontWeight`.
#[serde(deserialize_with = "whole")]
pub font_weight: u16,
pub line_height: f32,
/// Subpixel draws only on an opaque window, and never on macOS.
pub text_antialias: Antialias,
Expand Down Expand Up @@ -195,9 +198,10 @@ impl Default for Settings {
background_image: String::new(),
font_family: "JetBrains Mono".into(),
font_fallbacks: Vec::new(),
monospace_only: true,
cjk_font: String::new(),
font_size: 13.0,
font_weight: 400.0,
font_weight: 400,
line_height: 1.2,
text_antialias: Antialias::Grayscale,
ligatures: true,
Expand Down Expand Up @@ -233,7 +237,9 @@ impl Default for Settings {

pub const INTERFACE_SIZE: RangeInclusive<u8> = 12..=16;
pub const FONT_SIZE: RangeInclusive<f32> = 6.0..=72.0;
pub const FONT_WEIGHT: RangeInclusive<f32> = 100.0..=900.0;
/// The weights the list offers and the settings accept, as Windows Terminal
/// clamps `fontWeight`: every named weight falls inside, so a name picked saves.
pub const FONT_WEIGHT: RangeInclusive<u16> = 100..=990;
pub const LINE_HEIGHT: RangeInclusive<f32> = 1.0..=2.0;
pub const OPACITY: RangeInclusive<f32> = 0.3..=1.0;
pub const PADDING: RangeInclusive<f32> = 0.0..=64.0;
Expand All @@ -244,6 +250,20 @@ pub const ELY_LEFT: RangeInclusive<f32> = 0.0..=16384.0;
pub const PLACE: RangeInclusive<f32> = -32768.0..=32768.0;

impl Settings {
/// The weights this copy of the font draws: the family's own faces where the
/// platform names them, else the one nearly every family ships.
///
/// No invented 700: a family the platform cannot tell about is offered only
/// what it almost surely holds, so no pick falls back to a synthesised face.
pub fn weights(&self, cx: &gpui::App) -> Vec<u16> {
let mut weights = cx.text_system().weights_for_family(&self.font_family);
if weights.is_empty() {
weights = vec![400];
}
log::debug!("fonts: {} draws {weights:?}", self.font_family);
weights
}

/// Every value inside its range, every theme built in; the error names the field.
pub fn validate(&self) -> anyhow::Result<()> {
within("interface_size", self.interface_size, &INTERFACE_SIZE)?;
Expand Down Expand Up @@ -400,6 +420,14 @@ fn program(path: &Path) -> bool {
path.is_file()
}

/// Reads a whole number a file may hold as `400` or as `400.0`, which 0.2.2 wrote.
fn whole<'de, D: serde::Deserializer<'de>>(json: D) -> Result<u16, D::Error> {
use serde::de::Error as _;
let number = f64::deserialize(json)?;
u16::try_from(number.round() as i64)
.map_err(|_| D::Error::custom(format!("a weight is {number}, past 990")))
}

fn within<T: PartialOrd + std::fmt::Display>(
field: &str,
value: T,
Expand Down
35 changes: 32 additions & 3 deletions src/settings/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ fn a_file_without_files_menu_leaves_it_off() {
);
}

#[test]
fn a_file_without_the_monospace_switch_keeps_it_on() {
let settings: Settings = serde_json::from_str("{}").expect("every field has a default");
assert!(
settings.monospace_only,
"a terminal's font picker offers monospaced families by default"
);
let all: Settings =
serde_json::from_str(r#"{"monospace_only":false}"#).expect("the switch is a bool");
assert!(!all.monospace_only, "off offers every family");
}

#[test]
fn a_backdrop_is_acrylic_unless_named() {
let settings: Settings = serde_json::from_str("{}").expect("every field has a default");
Expand Down Expand Up @@ -52,11 +64,11 @@ fn a_value_out_of_range_names_its_field() {
let error = settings.validate().expect_err("2.5 is too tall");
assert_eq!(error.to_string(), "line_height is 2.5, outside 1 to 2");
let settings = Settings {
font_weight: 950.0,
font_weight: 1000,
..Settings::default()
};
let error = settings.validate().expect_err("950 is past black");
assert_eq!(error.to_string(), "font_weight is 950, outside 100 to 900");
let error = settings.validate().expect_err("1000 is past the clamp");
assert_eq!(error.to_string(), "font_weight is 1000, outside 100 to 990");
let settings = Settings {
scrollback: 100_001,
..Settings::default()
Expand Down Expand Up @@ -361,3 +373,20 @@ fn the_interface_wears_the_system_font_at_13_px_unless_named() {
);
}
}

/// A file 0.2.2 wrote names the weight as a float; it reads back as the whole number, and a round saves whole.
#[test]
fn a_weight_0_2_2_wrote_as_a_float_reads_back_whole() {
for (text, weight) in [
(r#"{"font_weight":400}"#, 400),
(r#"{"font_weight":400.0}"#, 400),
(r#"{"font_weight":450.0}"#, 450),
(r#"{"font_weight":950.0}"#, 950),
] {
let settings: Settings =
serde_json::from_str(text).unwrap_or_else(|error| panic!("{text}: {error}"));
assert_eq!(settings.font_weight, weight, "{text}");
}
let saved = serde_json::to_string(&Settings::default()).expect("the settings write");
assert!(saved.contains("\"font_weight\":400"), "{saved}");
}
Loading