diff --git a/CMakeLists.txt b/CMakeLists.txt index 14e75e0..1091a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(go-libghostty LANGUAGES C) include(FetchContent) FetchContent_Declare(ghostty GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git - GIT_TAG 15264856f61b112c8beb14fbe3f403f6266c8bdf + GIT_TAG 54ac5fd21e5eeef5e910f7f646934dc58fd373f8 ) FetchContent_MakeAvailable(ghostty) diff --git a/TODO.md b/TODO.md index e41e3e7..e473e15 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,6 @@ ## Not Bound -- [x] Key encoding (`key.h`, `key/encoder.h`, `key/event.h`) -- [x] Mouse encoding (`mouse.h`, `mouse/encoder.h`, `mouse/event.h`) - [ ] OSC parser (`osc.h`) - `ghostty_osc_new()` - `ghostty_osc_free()` @@ -22,14 +20,6 @@ - `ghostty_sgr_unknown_partial()` - `ghostty_sgr_attribute_tag()` - `ghostty_sgr_attribute_value()` -- [x] Paste utilities (`paste.h`) -- [x] Focus encoding (`focus.h`) -- [x] Kitty graphics (`kitty_graphics.h`) -- [x] Allocator (`allocator.h` — `ghostty_alloc`, `ghostty_free`) -- [x] Terminal selection helpers (`selection.h`) -- [x] Selection gesture APIs (`selection.h`) -- [x] Render-state row selection (`GHOSTTY_RENDER_STATE_ROW_DATA_SELECTION`) -- [x] Build info (`build_info.h`) ## WASM-only Not Bound diff --git a/render_state_cell.go b/render_state_cell.go index 155a95b..4312319 100644 --- a/render_state_cell.go +++ b/render_state_cell.go @@ -44,6 +44,14 @@ const ( // RenderStateRowCellsDataFgColor is the resolved foreground color of // the cell (GhosttyColorRgb). RenderStateRowCellsDataFgColor RenderStateRowCellsData = C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_FG_COLOR + + // RenderStateRowCellsDataSelected indicates whether the cell is contained + // within the current selection (bool). + RenderStateRowCellsDataSelected RenderStateRowCellsData = C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED + + // RenderStateRowCellsDataHasStyling indicates whether the cell has any + // explicit styling (bool). + RenderStateRowCellsDataHasStyling RenderStateRowCellsData = C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING ) // RenderStateRowCells iterates over cells in a render-state row. @@ -207,3 +215,27 @@ func (rc *RenderStateRowCells) FgColor() (*ColorRGB, error) { c := ColorRGB{R: uint8(v.r), G: uint8(v.g), B: uint8(v.b)} return &c, nil } + +// Selected reports whether the current cell is contained within the +// terminal selection snapshot that was current when the render state +// was updated. Rendering policy for selected cells is left to the +// caller. +func (rc *RenderStateRowCells) Selected() (bool, error) { + var v C.bool + if err := resultError(C.ghostty_render_state_row_cells_get(rc.ptr, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_SELECTED, unsafe.Pointer(&v))); err != nil { + return false, err + } + return bool(v), nil +} + +// HasStyling reports whether the current cell has any explicit styling. +// This is equivalent to querying the raw Cell's HasStyling value, but +// avoids materializing the raw cell when renderers only need to know +// whether fetching the full style is necessary. +func (rc *RenderStateRowCells) HasStyling() (bool, error) { + var v C.bool + if err := resultError(C.ghostty_render_state_row_cells_get(rc.ptr, C.GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_HAS_STYLING, unsafe.Pointer(&v))); err != nil { + return false, err + } + return bool(v), nil +} diff --git a/render_state_cell_test.go b/render_state_cell_test.go index 24b343c..4fde1b2 100644 --- a/render_state_cell_test.go +++ b/render_state_cell_test.go @@ -200,8 +200,8 @@ func TestRenderStateRowCellsStyle(t *testing.T) { } defer term.Close() - // Write bold text. - term.VTWrite([]byte("\x1b[1mX")) + // Write one bold cell and one default-styled cell. + term.VTWrite([]byte("\x1b[1mX\x1b[0mY")) rs, err := NewRenderState() if err != nil { @@ -248,6 +248,94 @@ func TestRenderStateRowCellsStyle(t *testing.T) { if !style.Bold() { t.Fatal("expected bold style") } + + hasStyling, err := rc.HasStyling() + if err != nil { + t.Fatal(err) + } + if !hasStyling { + t.Fatal("expected first cell to have styling") + } + + if !rc.Next() { + t.Fatal("expected second cell") + } + hasStyling, err = rc.HasStyling() + if err != nil { + t.Fatal(err) + } + if hasStyling { + t.Fatal("expected second cell to have default styling") + } +} + +func TestRenderStateRowCellsSelected(t *testing.T) { + term, err := NewTerminal(WithSize(80, 24)) + if err != nil { + t.Fatal(err) + } + defer term.Close() + + term.VTWrite([]byte("hello")) + start, err := term.GridRef(Point{Tag: PointTagActive, X: 1, Y: 0}) + if err != nil { + t.Fatal(err) + } + end, err := term.GridRef(Point{Tag: PointTagActive, X: 3, Y: 0}) + if err != nil { + t.Fatal(err) + } + if err := term.SetSelection(&Selection{Start: *start, End: *end}); err != nil { + t.Fatal(err) + } + + rs, err := NewRenderState() + if err != nil { + t.Fatal(err) + } + defer rs.Close() + + if err := rs.Update(term); err != nil { + t.Fatal(err) + } + + ri, err := NewRenderStateRowIterator() + if err != nil { + t.Fatal(err) + } + defer ri.Close() + + if err := rs.RowIterator(ri); err != nil { + t.Fatal(err) + } + + if !ri.Next() { + t.Fatal("expected at least one row") + } + + rc, err := NewRenderStateRowCells() + if err != nil { + t.Fatal(err) + } + defer rc.Close() + + if err := ri.Cells(rc); err != nil { + t.Fatal(err) + } + + for x := uint16(0); x < 5; x++ { + if err := rc.Select(x); err != nil { + t.Fatal(err) + } + selected, err := rc.Selected() + if err != nil { + t.Fatal(err) + } + expected := x >= 1 && x <= 3 + if selected != expected { + t.Fatalf("cell %d selected = %t, expected %t", x, selected, expected) + } + } } func TestRenderStateRowCellsColors(t *testing.T) {