Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ccf8be8
terminal: fix damage tracking when insert/deleting characters
coletrammer Apr 12, 2025
6d560d1
meta: add fzf runtime as a dependency
coletrammer Apr 12, 2025
43c9d94
client: support full screen panes
coletrammer Apr 12, 2025
880cc59
terminal+client: implement DECSTR soft reset
coletrammer Apr 12, 2025
3899eff
client: implement support for a single popup tab
coletrammer Apr 13, 2025
2818918
client: support spawning commands with pipe as stdin and stdout
coletrammer Apr 13, 2025
b4615b9
client: implement prefix+f to switch tabs with an fzf popup
coletrammer Apr 13, 2025
80f4958
client: implement renaming a tab via fzf popup
coletrammer Apr 13, 2025
610aa44
client: support fixed size popup windows
coletrammer Apr 13, 2025
e93d393
client: implement key binds for next tab + prev tab
coletrammer Apr 14, 2025
6b43f38
client: implement support for a single session
coletrammer Apr 14, 2025
65dc8b2
meta: update flake
coletrammer Apr 14, 2025
81aa5be
client: implement pane switching, finding, and creating key binds
coletrammer Apr 14, 2025
41b3b10
terminal: fix scroll back when we exceed the maximum scroll back size
coletrammer Apr 15, 2025
7b062b5
terminal: fix incorrect row width when filling cells from scroll back
coletrammer Apr 15, 2025
ac897fe
client: prevent having more than 1 popup
coletrammer Apr 15, 2025
20a2101
client: perform terminal setup on every SIGWINCH
coletrammer Apr 15, 2025
f1b7d85
terminal: make kitty keyboard flags per screen
coletrammer Apr 15, 2025
784f3bc
meta: fix clang tidy violations
coletrammer Apr 16, 2025
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ details.

## Dependencies

This project depends on the [dius](https://github.com/coletrammer/dius) library, to use its cross-platform
abstractions.
- [fzf](https://github.com/junegunn/fzf) program, for various popup memus
- [dius](https://github.com/coletrammer/dius) library, to use its cross-platform
abstractions.

## Installing

Expand Down
9 changes: 8 additions & 1 deletion docs/pages/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ This package can be built either directly through [CMake](https://cmake.org/) or

### Dependencies

- The [fzf](https://github.com/junegunn/fzf) program. This is used at runtime for various menus. To be clear, this
dependency is only needed for the `ttx` application and not the terminal emulation library itself.
- The [dius](https://github.com/coletrammer/dius) library.
- The [di](https://github.com/coletrammer/di) library (dependency of dius).

Expand Down Expand Up @@ -66,6 +68,9 @@ Afterwards, use the library via `target_link_libraries(target PRIVATE ttx::ttx)`

### Note to packagers

Any `ttx` package should depend on `fzf` as `ttx` requires the `fzf` program to be
in the user's PATH on application startup.

The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if
the project is configured as a top level project to avoid indirectly including
other libraries when installed to a common prefix. Please review the
Expand Down Expand Up @@ -98,7 +103,9 @@ ttx = {
Then include `inputs.ttx.packages.${system}.ttx-lib` in the `buildInputs` of your derivation. Assuming your
project is using CMake, `find_package(ttx)` will succeed automatically.

This flake provides takes the di and dius libraries as a flake input, so it can be overridden easily.
This flake provides takes the di and dius libraries as a flake input, so it can be overridden easily. The
`fzf` dependency is taken from nixpkgs (which is also easy to override), and the `ttx` binary is wrapped
by nix to ensure `fzf` will be in the `PATH` variable when running `ttx`.

### Manual Build Commands

Expand Down
5 changes: 3 additions & 2 deletions docs/pages/install.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Install

The easiest way to install ttx is using [nix](https://nixos). As of now, the only alternative is compiling from source.
In the future, a statically linked binary will available via a GitHub release. To build ttx from source,
see the steps [here](./build.md).
In the future, a statically linked binary will available via a GitHub release. However the user will
still need to install `fzf` to make use to the application. To build ttx from source, see the steps
[here](./build.md).

## Installing with Nix

Expand Down
12 changes: 6 additions & 6 deletions flake.lock

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

6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,23 @@ tidy *args="": ensure_configured
#!/usr/bin/env bash
set -euo pipefail

export ttx_TIDY_ARGS="{{ args }}"
export TTX_TIDY_ARGS="{{ args }}"
cmake --build --preset {{ preset }} -t tidy

# Run static analysis
analyze *args="": ensure_configured
#!/usr/bin/env bash
set -euo pipefail

export ttx_TIDY_ARGS="{{ args }}"
export TTX_TIDY_ARGS="{{ args }}"
cmake --build --preset {{ preset }} -t analyze

# Run clang-tidy and output failures
check_tidy *args="": ensure_configured
#!/usr/bin/env bash
set -euo pipefail

export ttx_TIDY_ARGS="{{ args }}"
export TTX_TIDY_ARGS="{{ args }}"
cmake --build --preset {{ preset }} -t check_tidy

# Clean
Expand Down
57 changes: 31 additions & 26 deletions lib/include/ttx/pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,51 @@
#include "ttx/terminal.h"

namespace ttx {
class Pane;

struct PaneHooks {
/// @brief Application controlled callback when the internal process exits.
di::Function<void(Pane&)> did_exit;

/// @brief controlled callback when the terminal buffer has updated.
di::Function<void(Pane&)> did_update;

/// @brief Application controlled callback when text is selected.
di::Function<void(di::Span<byte const>)> did_selection;

/// @brief Application controlled callback when APC command is set.
di::Function<void(di::StringView)> apc_passthrough;

/// @brief Callback with the results on reading from the output pipe.
di::Function<void(di::StringView)> did_finish_output;
};

struct CreatePaneArgs {
di::Vector<di::TransparentStringView> command {};
di::Vector<di::TransparentString> command {};
di::Optional<di::Path> capture_command_output_path {};
di::Optional<di::Path> replay_path {};
di::Optional<di::Path> save_state_path {};
di::Optional<di::String> pipe_input {};
bool pipe_output { false };
PaneHooks hooks {};
};

class Pane {
public:
static auto create_from_replay(u64 id, di::PathView replay_path, di::Optional<di::Path> save_state_path,
Size const& size, di::Function<void(Pane&)> did_exit,
di::Function<void(Pane&)> did_update,
di::Function<void(di::Span<byte const>)> did_selection,
di::Function<void(di::StringView)> apc_passthrough) -> di::Result<di::Box<Pane>>;
static auto create(u64 id, CreatePaneArgs args, Size const& size, di::Function<void(Pane&)> did_exit,
di::Function<void(Pane&)> did_update, di::Function<void(di::Span<byte const>)> did_selection,
di::Function<void(di::StringView)> apc_passthrough) -> di::Result<di::Box<Pane>>;
Size const& size, PaneHooks hooks) -> di::Result<di::Box<Pane>>;
static auto create(u64 id, CreatePaneArgs args, Size const& size) -> di::Result<di::Box<Pane>>;

// For testing, create a mock pane. This doesn't actually create a psuedo terminal or a subprocess.
static auto create_mock() -> di::Box<Pane>;

explicit Pane(u64 id, dius::SyncFile pty_controller, Size const& size, dius::system::ProcessHandle process,
di::Function<void(Pane&)> did_exit, di::Function<void(Pane&)> did_update,
di::Function<void(di::Span<byte const>)> did_selection,
di::Function<void(di::StringView)> apc_passthrough)
PaneHooks hooks)
: m_id(id)
, m_pty_controller(di::move(pty_controller))
, m_terminal(di::in_place, id, m_pty_controller, size)
, m_process(process)
, m_did_exit(di::move(did_exit))
, m_did_update(di::move(did_update))
, m_did_selection(di::move(did_selection))
, m_apc_passthrough(di::move(apc_passthrough)) {}
, m_hooks(di::move(hooks)) {}
~Pane();

auto id() const { return m_id; }
Expand All @@ -67,6 +79,7 @@ class Pane {
void scroll(Direction direction, i32 amount_in_cells);
auto save_state(di::PathView path) -> di::Result<>;
void stop_capture();
void soft_reset();
void exit();

private:
Expand All @@ -83,20 +96,12 @@ class Pane {
u32 m_vertical_scroll_offset { 0 };
u32 m_horizontal_scroll_offset { 0 };

// Application controlled callback when the internal process exits.
di::Function<void(Pane&)> m_did_exit;

// Application controlled callback when the terminal buffer has updated.
di::Function<void(Pane&)> m_did_update;

// Application controlled callback when text is selected.
di::Function<void(di::Span<byte const>)> m_did_selection;

// Application controlled callback when APC command is set.
di::Function<void(di::StringView)> m_apc_passthrough;
PaneHooks m_hooks;

// These are declared last, for when dius::Thread calls join() in the destructor.
dius::Thread m_process_thread;
dius::Thread m_reader_thread;
dius::Thread m_pipe_writer_thread;
dius::Thread m_pipe_reader_thread;
};
}
41 changes: 41 additions & 0 deletions lib/include/ttx/popup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "ttx/layout.h"
#include "ttx/size.h"

namespace ttx {
enum class PopupAlignment {
Left,
Right,
Top,
Bottom,
Center,
};

namespace detail {
struct RelatizeSizeTag {
using Type = i64;
};
struct AbsoluteSizeTag {
using Type = u32;
};
}

using RelatizeSize = di::StrongInt<detail::RelatizeSizeTag>;
using AbsoluteSize = di::StrongInt<detail::AbsoluteSizeTag>;

using PopupSize = di::Variant<RelatizeSize, AbsoluteSize>;

struct PopupLayout {
PopupAlignment alignment { PopupAlignment::Center };
PopupSize width { RelatizeSize(max_layout_precision / 2) }; // 50% width default
PopupSize height { RelatizeSize(max_layout_precision / 2) }; // 50% height default
};

struct Popup {
di::Box<Pane> pane {};
PopupLayout layout_config;

auto layout(Size const& size) -> LayoutEntry;
};
}
5 changes: 5 additions & 0 deletions lib/include/ttx/renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ struct RenderedCursor {

class Renderer {
public:
auto setup(dius::SyncFile& output) -> di::Result<>;
auto cleanup(dius::SyncFile& output) -> di::Result<>;

void start(Size const& size);
auto finish(dius::SyncFile& output, RenderedCursor const& cursor) -> di::Result<>;

Expand All @@ -42,6 +45,8 @@ class Renderer {
di::VectorWriter<> m_buffer;
Size m_size;

di::Vector<di::String> m_cleanup;

GraphicsRendition m_last_graphics_rendition;
di::Optional<terminal::Hyperlink> m_last_hyperlink;
u32 m_last_cursor_row { 0 };
Expand Down
12 changes: 9 additions & 3 deletions lib/include/ttx/terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
terminal::Screen screen;
di::Optional<terminal::SavedCursor> saved_cursor;
CursorStyle cursor_style { CursorStyle::SteadyBar };

// Per https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol,
// the keyboard mode stack and flags are per-screen.
KeyReportingFlags m_key_reporting_flags { KeyReportingFlags::None };
di::Ring<KeyReportingFlags> m_key_reporting_flags_stack;
};

public:
Expand Down Expand Up @@ -64,7 +69,7 @@
auto visible_size() const -> Size { return m_available_size; }

auto application_cursor_keys_mode() const -> ApplicationCursorKeysMode { return m_application_cursor_keys_mode; }
auto key_reporting_flags() const -> KeyReportingFlags { return m_key_reporting_flags; }
auto key_reporting_flags() const -> KeyReportingFlags { return active_screen().m_key_reporting_flags; }

Check warning on line 72 in lib/include/ttx/terminal.h

View check run for this annotation

Codecov / codecov/patch

lib/include/ttx/terminal.h#L72

Added line #L72 was not covered by tests

auto alternate_scroll_mode() const -> AlternateScrollMode { return m_alternate_scroll_mode; }
auto mouse_protocol() const -> MouseProtocol { return m_mouse_protocol; }
Expand All @@ -76,6 +81,8 @@

auto bracked_paste_mode() const -> BracketedPasteMode { return m_bracketed_paste_mode; }

void soft_reset();

void invalidate_all();

auto outgoing_events() -> di::Vector<TerminalEvent> { return di::move(m_outgoing_events); }
Expand Down Expand Up @@ -160,6 +167,7 @@
void csi_decstbm(Params const& params);
void csi_scosc(Params const& params);
void csi_scorc(Params const& params);
void csi_decstr(Params const& params);
void csi_xtwinops(Params const& params);

void csi_set_key_reporting_flags(Params const& params);
Expand Down Expand Up @@ -189,8 +197,6 @@
di::Optional<c32> m_last_graphics_charcter { 0 };

ApplicationCursorKeysMode m_application_cursor_keys_mode { ApplicationCursorKeysMode::Disabled };
KeyReportingFlags m_key_reporting_flags { KeyReportingFlags::None };
di::Ring<KeyReportingFlags> m_key_reporting_flags_stack;

AlternateScrollMode m_alternate_scroll_mode { AlternateScrollMode::Disabled };
MouseProtocol m_mouse_protocol { MouseProtocol::None };
Expand Down
2 changes: 1 addition & 1 deletion lib/include/ttx/terminal/screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class Screen {

// Screen state.
RowGroup m_active_rows;
bool m_whole_screen_dirty { false };
bool m_whole_screen_dirty { true };

// Scroll back
ScrollBack m_scroll_back;
Expand Down
Loading
Loading