Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ Prefer this over loosening the permissions on your home directory.

### First launch: unsigned builds

Releases are **not code-signed** (that requires a paid Apple developer
account and a Windows certificate). The binaries are fine; the OS just
can't verify who made them, so it warns on first launch.
And all installation packages are built exclusively by the GitHub free tier CI/CD
Releases are **not code-signed**, where signing requires a paid Apple developer
account and a Windows certificate. All installation packages are built exclusively by the GitHub CI/CD
pipeline.

- **macOS** — right-click the app and choose *Open*, or:
Expand Down
1 change: 1 addition & 0 deletions packaging/macos/make-dmg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ cat > "build/${BUNDLE}/Contents/Info.plist" <<PLIST
<key>CFBundleTypeExtensions</key>
<array>
<string>txt</string>
<string>json</string>
<string>log</string>
<string>t</string>
</array>
Expand Down
32 changes: 32 additions & 0 deletions src/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package editorapp

import (
"image"
"strconv"
"time"

gioapp "gioui.org/app"
Expand Down Expand Up @@ -139,6 +140,7 @@ func (a *App) Run(win *gioapp.Window) error {
// doesn't have to wait for a frame to be scheduled before it's processed.
func (a *App) layout(gtx layout.Context) {
size := gtx.Constraints.Max
a.syncGuiFont(a.state.GuiFont())
a.syncMetrics(gtx)

a.handleInput(gtx)
Expand All @@ -157,6 +159,36 @@ func (a *App) layout(gtx layout.Context) {
render.Frame(gtx, a.fonts, snap)
}

func (a *App) syncGuiFont(guiFont string) {
size, ok := guiFontSize(guiFont)
if !ok || a.fonts.Size == unit.Sp(size) {
return
}
a.fonts.Size = unit.Sp(size)
a.fonts.Metrics = render.Metrics{}
}

func guiFontSize(guiFont string) (float32, bool) {
for i := 0; i+2 < len(guiFont); i++ {
if guiFont[i] != ':' || (guiFont[i+1] != 'h' && guiFont[i+1] != 'H') {
continue
}
start := i + 2
end := start
for end < len(guiFont) && ((guiFont[end] >= '0' && guiFont[end] <= '9') || guiFont[end] == '.') {
end++
}
if end == start {
continue
}
parsed, err := strconv.ParseFloat(guiFont[start:end], 32)
if err == nil && parsed > 0 {
return float32(parsed), true
}
}
return 0, false
}

// syncMetrics (re-)measures the cell grid whenever the pixel density the
// window is being rendered at changes.
//
Expand Down
32 changes: 32 additions & 0 deletions src/internal/app/metricsync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,35 @@ func TestGridResizesAfterScaleChange(t *testing.T) {
windowPx, cols1x, cols2x)
}
}

func TestGuiFontSizeInvalidatesMetrics(t *testing.T) {
a := newMeasuredApp()
var ops op.Ops
a.syncMetrics(frameAt(&ops, 1))
before := a.fonts.Metrics

a.syncGuiFont("Hack Nerd Font Mono:h18:b")
if got, want := a.fonts.Size, unit.Sp(18); got != want {
t.Fatalf("font size = %v, want %v", got, want)
}
if a.fonts.Metrics.CellWidth != 0 || a.fonts.Metrics.CellHeight != 0 {
t.Fatalf("metrics were not invalidated: %+v", a.fonts.Metrics)
}

a.syncMetrics(frameAt(&ops, 1))
if a.fonts.Metrics.CellWidth <= before.CellWidth || a.fonts.Metrics.CellHeight <= before.CellHeight {
t.Errorf("cells did not grow after guifont update: before=%+v after=%+v", before, a.fonts.Metrics)
}
}

func TestGuiFontSizeIgnoresInvalidValues(t *testing.T) {
a := newMeasuredApp()
want := a.fonts.Size

for _, guiFont := range []string{"", "Hack Nerd Font Mono", "Hack Nerd Font Mono:h0:b", "Hack Nerd Font Mono:hnope:b"} {
a.syncGuiFont(guiFont)
if a.fonts.Size != want {
t.Errorf("syncGuiFont(%q) changed size to %v, want %v", guiFont, a.fonts.Size, want)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Apple Event handler for Finder's "open document" ('odoc') event.
//
// This lives in a real .m file rather than in the cgo preamble of
// openfile_darwin.go. A preamble is textually prepended to *every* C
// This file contains Objective-C and is compiled as such by the package's
// -x objective-c CFLAGS. Keeping the .c extension prevents cgo from adding
// another -lobjc when Gio already supplies the Objective-C runtime.
//
// This lives in a separate translation unit rather than in the cgo preamble
// of openfile_darwin.go. A preamble is textually prepended to *every* C
// translation unit cgo generates for that package, so any function or ObjC
// class *defined* (not merely declared) there is compiled more than once and
// the link fails with duplicate symbols:
Expand All @@ -11,7 +15,7 @@
//
// That is guaranteed to happen once the same file also uses //export, because
// cgo then emits an extra translation unit for the exported thunks. The rule
// is: preambles declare, .m/.c files define.
// is: preambles declare, separate translation units define.

#import <Cocoa/Cocoa.h>

Expand Down Expand Up @@ -80,4 +84,4 @@ void snv_install_open_file_handler(void) {
forEventClass:kCoreEventClass
andEventID:kAEOpenDocuments];
});
}
}
2 changes: 1 addition & 1 deletion src/internal/app/openfile_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package editorapp
#cgo CFLAGS: -x objective-c -fmodules -fobjc-arc
#cgo LDFLAGS: -framework Cocoa

// Declarations only. The implementation lives in openfile_darwin.m --
// Declarations only. The implementation lives in openfile_darwin.c --
// this preamble is prepended to every translation unit cgo generates for
// the package, so defining the class or function here would compile them
// more than once and fail the link with duplicate symbols.
Expand Down
26 changes: 26 additions & 0 deletions src/internal/uistate/options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package uistate

import "testing"

func TestOptionSetStoresGuiFont(t *testing.T) {
state := New()
state.Apply([][]interface{}{
{"option_set", []interface{}{"guifont", "Hack Nerd Font Mono:h17:b"}},
})

if got, want := state.GuiFont(), "Hack Nerd Font Mono:h17:b"; got != want {
t.Fatalf("GuiFont = %q, want %q", got, want)
}
}

func TestOptionSetIgnoresOtherOptionsAndMalformedRows(t *testing.T) {
state := New()
state.Apply([][]interface{}{
{"option_set", []interface{}{"guifont", "monospace:h14"}},
{"option_set", []interface{}{"linespace", 2}, "invalid"},
})

if got, want := state.GuiFont(), "monospace:h14"; got != want {
t.Fatalf("GuiFont = %q, want %q", got, want)
}
}
26 changes: 25 additions & 1 deletion src/internal/uistate/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type State struct {
hl *HighlightTable
cursor Cursor

title string
title string
guiFont string

mode string
modeIdx int
Expand Down Expand Up @@ -88,6 +89,8 @@ func (s *State) Apply(batch [][]interface{}) bool {
s.applyModeChange(args)
case "set_title":
s.applySetTitle(args)
case "option_set":
s.applyOptionSet(args)
case "busy_start":
s.busy = true
case "busy_stop":
Expand Down Expand Up @@ -155,6 +158,27 @@ func (s *State) Snapshot() Snapshot {
}
}

// GuiFont returns the latest guifont value reported by Nvim.
func (s *State) GuiFont() string {
s.mu.RLock()
defer s.mu.RUnlock()
return s.guiFont
}

func (s *State) applyOptionSet(args []interface{}) {
for _, arg := range args {
row, ok := arg.([]interface{})
if !ok || len(row) < 2 {
continue
}
name, nameOK := row[0].(string)
value, valueOK := row[1].(string)
if name == "guifont" && nameOK && valueOK {
s.guiFont = value
}
}
}

func (s *State) applySetTitle(args []interface{}) {
if len(args) == 0 {
return
Expand Down
31 changes: 31 additions & 0 deletions src/test/integration/nvimproc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,37 @@ func TestSpawnAttachesAndRendersFileContent(t *testing.T) {
waitForLine(t, proc, s, "hello integration test")
}

func TestGuiFontOptionChangeReachesUIState(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "guifont.txt")
if err := os.WriteFile(file, []byte("font test\n"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}

proc := spawnIsolated(t, file, 40, 10)
state := uistate.New()
const want = "monospace:h19"
if err := proc.Nvim.Command("set guifont=" + want); err != nil {
t.Fatalf("set guifont: %v", err)
}

deadline := time.After(drainTimeout)
for state.GuiFont() != want {
select {
case batch, ok := <-proc.Redraw:
if !ok {
t.Fatal("Nvim redraw stream closed before guifont update arrived")
}
needsFrame := state.Apply(batch)
if state.GuiFont() == want && !needsFrame {
t.Fatal("guifont update did not request a frame")
}
case <-deadline:
t.Fatalf("timed out after %s waiting for guifont %q; got %q", drainTimeout, want, state.GuiFont())
}
}
}

func TestInputIsReflectedInBothOurGridAndRealNvimBuffer(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "edit.txt")
Expand Down
3 changes: 3 additions & 0 deletions src/test/unit/keymap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func TestEncodeKeyModifierCombinations(t *testing.T) {
}{
{"ctrl+letter", key.Event{Name: "A", Modifiers: key.ModCtrl, State: key.Press}, "<C-a>"},
{"ctrl+digit", key.Event{Name: "1", Modifiers: key.ModCtrl, State: key.Press}, "<C-1>"},
{"ctrl+equal", key.Event{Name: "=", Modifiers: key.ModCtrl, State: key.Press}, "<C-=>"},
{"ctrl+minus", key.Event{Name: "-", Modifiers: key.ModCtrl, State: key.Press}, "<C-->"},
{"ctrl+zero", key.Event{Name: "0", Modifiers: key.ModCtrl, State: key.Press}, "<C-0>"},
{"command (mac cmd) + letter", key.Event{Name: "A", Modifiers: key.ModCommand, State: key.Press}, "<D-a>"},
{"super (win/linux logo) + letter", key.Event{Name: "A", Modifiers: key.ModSuper, State: key.Press}, "<D-a>"},
{"alt + letter", key.Event{Name: "A", Modifiers: key.ModAlt, State: key.Press}, "<A-a>"},
Expand Down
Loading