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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 52 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ The intent is to show the output of Nushell commands in a GUI.
- `src/color_utils.rs`: shared color/style helper utilities.
- `src/table_data.rs`: shared table model passed between conversion and GUI layers.
- `src/value_conv/`: Nushell value conversion pipeline.
- `mod.rs`: public conversion API and tests.
- `closure.rs`: closure source extraction/highlighting helpers.
- `stringify.rs`: value-to-display-string conversion.
- `table.rs`: table-shaping logic from incoming pipeline data.
- `mod.rs`: public conversion API and tests.
- `closure.rs`: closure source extraction/highlighting helpers.
- `stringify.rs`: value-to-display-string conversion.
- `table.rs`: table-shaping logic from incoming pipeline data.
- `src/gui.rs`: primary GUI view/delegate, interactions, and app runtime glue.
- `src/gui_ansi.rs`: ANSI segment parsing for colored text rendering in cells.
- `src/window_sizing.rs`: initial window-size calculation logic.
Expand All @@ -52,35 +52,51 @@ The intent is to show the output of Nushell commands in a GUI.

```mermaid
flowchart TD
A["Nushell pipeline input<br/>example command: ls | to gui"] --> B[src/main.rs<br/>serve plugin]
B --> C[src/plugin_command.rs<br/>ToGuiCommand::run]

C --> D[src/value_conv/table.rs<br/>values_to_table_*]
C --> E[src/value_conv/stringify.rs<br/>value_to_string_with_engine]
C --> F[src/value_conv/closure.rs<br/>collect_closure_sources_*]
C --> G[src/color_config.rs<br/>build_runtime_color_config]

D --> H[src/table_data.rs<br/>TableData]
E --> H
F --> I[closure source cache]
G --> J[ColorConfig]

H --> K[src/gui.rs<br/>run_table_gui]
I --> K
J --> K

K --> L[src/window_sizing.rs<br/>ideal_window_size]
K --> M[src/gui_ansi.rs<br/>parse_ansi_segments]
K --> N[Interactive table window]

N --> O[Sort / filter / copy cell]
N --> P[Double-click nested values]
P --> D
N --> Q[Save as JSON]
A["Nushell pipeline input<br/>example command: ls | to gui"] --> B[src/main.rs<br/>serve plugin]
B --> C[src/plugin_command.rs<br/>ToGuiCommand::run]

C --> D[src/value_conv/table.rs<br/>values_to_table_*]
C --> E[src/value_conv/stringify.rs<br/>value_to_string_with_engine]
C --> F[src/value_conv/closure.rs<br/>collect_closure_sources_*]
C --> G[src/color_config.rs<br/>build_runtime_color_config]

D --> H[src/table_data.rs<br/>TableData]
E --> H
F --> I[closure source cache]
G --> J[ColorConfig]

H --> K[src/gui.rs<br/>run_table_gui]
I --> K
J --> K

K --> L[src/window_sizing.rs<br/>ideal_window_size]
K --> M[src/gui_ansi.rs<br/>parse_ansi_segments]
K --> N[Interactive table window]

N --> O[Sort / filter / copy cell]
N --> P[Double-click nested values]
P --> D
N --> Q[Save as JSON]
```

## Getting Started

### Nerd Font support

If any Nerd Font is installed on your system, glyph icons from tools like `eza` or `lsd` will render automatically with no configuration needed. We prefer `font-symbols-only-nerd-font` as it is the lightest option and only contains the symbols.

Installation commands:

- **macOS:** `brew install --cask font-symbols-only-nerd-font`
- **Windows (Scoop, user install, no admin required):**

```powershell
scoop bucket add nerd-fonts
scoop install nerd-fonts/NerdFontsSymbolsOnly
```

- **Linux:** Install via your distro package manager or download from [https://www.nerdfonts.com/font-downloads](https://www.nerdfonts.com/font-downloads).

### Platform prerequisites

#### Linux
Expand All @@ -90,21 +106,23 @@ On Linux and FreeBSD, the build performs an early dependency check and reports m
If both `WAYLAND_DISPLAY` and `DISPLAY` are present and Wayland initialization fails, `to gui` automatically retries with X11. Set `TO_GUI_FORCE_WAYLAND=1` to disable this fallback.

- install development packages for XCB and XKB before building.
- Debian/Ubuntu: `sudo apt install libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev`
- Fedora: `sudo dnf install libxcb-devel libxkbcommon-devel libxkbcommon-x11-devel`
- Arch: `sudo pacman -S libxcb libxkbcommon xorg-xkbcommon`
- Debian/Ubuntu: `sudo apt install libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev`
- Fedora: `sudo dnf install libxcb-devel libxkbcommon-devel libxkbcommon-x11-devel`
- Arch: `sudo pacman -S libxcb libxkbcommon xorg-xkbcommon`

#### MacOS

On macOS, this project enables `gpui` runtime shader compilation (`runtime_shaders`) to avoid requiring the separate Metal Toolchain component during `cargo build`.

- Install Xcode Command Line Tools: `xcode-select --install`
- If prompted, open Xcode once and accept the license.
- Install Xcode Command Line Tools: `xcode-select --install`
- If prompted, open Xcode once and accept the license.

#### Windows

- use the MSVC Rust target (`x86_64-pc-windows-msvc`) with Visual Studio Build Tools.

### Building

1. Clone the repository and ensure you have Rust installed (edition 2024).
2. Run `cargo build` to compile the project or `cargo install --path .`. Dependencies are fetched from the Nushell GitHub repo.
3. Register the plugin with `plugin add /path/to/nu_plugin_to_gui`.
Expand All @@ -115,5 +133,4 @@ On macOS, this project enables `gpui` runtime shader compilation (`runtime_shade

_Note:_ the project is in early development and primarily intended for internal tooling or experimentation.


Feel free to open issues or contribute enhancements.
55 changes: 41 additions & 14 deletions src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ pub struct NushellTableDelegate {
right_clicked_col: Option<usize>,
/// Last clicked column index (used by double-click drilldown without forcing table scroll).
last_clicked_col: Option<usize>,
/// Nerd Font family to use for fallback rendering.
nerd_font_family: Option<String>,
}

impl NushellTableDelegate {
Expand All @@ -166,6 +168,7 @@ impl NushellTableDelegate {
autosize: bool,
color_config: ColorConfig,
column_filter_inputs: Vec<Entity<InputState>>,
nerd_font_family: Option<String>,
) -> Self {
let num_cols = data.columns.len();
let count = data.rows.len();
Expand Down Expand Up @@ -217,6 +220,7 @@ impl NushellTableDelegate {
column_filter_inputs,
right_clicked_col: None,
last_clicked_col: None,
nerd_font_family,
}
}

Expand Down Expand Up @@ -490,6 +494,15 @@ impl TableDelegate for NushellTableDelegate {
let mut has_ansi_segments = false;

let mut div = gpui::div().size_full();

// Apply Nerd Font fallbacks if available
if let Some(ref font_family) = self.nerd_font_family {
div = div.font(gpui::Font {
fallbacks: Some(gpui::FontFallbacks::from_fonts(vec![font_family.clone()])),
..gpui::font(".SystemUIFont")
});
}

if let Some(segments) = parse_ansi_segments(&text) {
has_ansi_segments = true;
let mut text_row = gpui::div().h_flex().gap_0().w_full();
Expand Down Expand Up @@ -609,6 +622,7 @@ struct ViewSettings {
closure_sources: Arc<HashMap<usize, String>>,
table_config: Arc<Config>,
rfc3339: bool,
nerd_font_family: Option<String>,
}

impl ToGuiView {
Expand All @@ -622,7 +636,11 @@ impl ToGuiView {
closure_sources,
table_config,
rfc3339,
nerd_font_family,
} = launch;
let nerd_font_family = nerd_font_family.or_else(|| {
crate::nerd_glyphs::find_nerd_font_family(&cx.text_system().all_font_names())
});

let root_data = table_data.clone();
let closure_sources = Arc::new(closure_sources);
Expand All @@ -633,6 +651,7 @@ impl ToGuiView {
closure_sources: closure_sources.clone(),
table_config: table_config.clone(),
rfc3339,
nerd_font_family,
});

let (fi, ts) = Self::build_page(window, cx, &table_data, initial_filter, &settings);
Expand Down Expand Up @@ -754,6 +773,7 @@ impl ToGuiView {
settings.autosize,
settings.color_config.clone(),
col_inputs,
settings.nerd_font_family.clone(),
);

let ts = cx.new(|cx| {
Expand Down Expand Up @@ -1242,6 +1262,7 @@ pub fn run_table_gui(launch: GuiLaunch) -> Result<()> {
closure_sources,
table_config,
rfc3339,
nerd_font_family,
} = launch;

let app = build_app()?;
Expand All @@ -1251,6 +1272,11 @@ pub fn run_table_gui(launch: GuiLaunch) -> Result<()> {

app.run(move |cx| {
gpui_component::init(cx);

let nerd_font_family2 = nerd_font_family.clone();
if let Some(ref name) = nerd_font_family2 {
eprintln!("to-gui: using Nerd Font family: {name}");
}
Theme::change(ThemeMode::Dark, None, cx);
cx.activate(true);

Expand Down Expand Up @@ -1382,20 +1408,21 @@ pub fn run_table_gui(launch: GuiLaunch) -> Result<()> {
let cc = color_config.clone();
let save_dir = save_dir.clone();
let view = cx.new(|cx| {
ToGuiView::new(
window,
cx,
GuiLaunch {
table: table.clone(),
initial_filter: initial_filter.clone(),
autosize,
color_config: cc,
save_dir,
closure_sources: closure_sources2,
table_config: table_config2,
rfc3339: rfc3339_2,
},
)
ToGuiView::new(
window,
cx,
GuiLaunch {
table: table.clone(),
initial_filter: initial_filter.clone(),
autosize,
color_config: cc,
save_dir,
closure_sources: closure_sources2,
table_config: table_config2,
rfc3339: rfc3339_2,
nerd_font_family: nerd_font_family2,
},
)
});
cx.new(|cx| Root::new(view, window, cx))
})?;
Expand Down
1 change: 1 addition & 0 deletions src/gui_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct GuiLaunch {
pub closure_sources: HashMap<usize, String>,
pub table_config: Config,
pub rfc3339: bool,
pub nerd_font_family: Option<String>,
}

static GUI_REQUEST_TX: OnceLock<mpsc::Sender<GuiLaunch>> = OnceLock::new();
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod gui;
pub mod gui_ansi;
#[cfg(not(test))]
pub mod gui_dispatch;
pub mod nerd_glyphs;
pub mod plugin_command;
pub mod table_data;
pub mod value_conv;
Expand Down
14 changes: 14 additions & 0 deletions src/nerd_glyphs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub fn find_nerd_font_family(font_names: &[String]) -> Option<String> {
let priorities: &[&str] = &[
"Symbols Nerd Font Mono",
"Symbols Nerd Font",
"Nerd Font Mono",
"Nerd Font",
];
for pattern in priorities {
if let Some(name) = font_names.iter().find(|n| n.contains(pattern)) {
return Some(name.clone());
}
}
None
}
1 change: 1 addition & 0 deletions src/plugin_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl PluginCommand for ToGuiCommand {
closure_sources,
table_config: (*nu_config).clone(),
rfc3339,
nerd_font_family: None,
};

#[cfg(test)]
Expand Down