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
8 changes: 1 addition & 7 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"additionalProperties": false
},
"differenceColors": {
"description": "Changed normalization spans, separate from the selection cursor. Underlining also identifies these spans.",
"description": "Changed normalization spans, separate from the selection colors. Underlining also identifies these spans.",
"type": "object",
"properties": {
"fg": {
Expand Down Expand Up @@ -272,12 +272,6 @@
}
],
"default": "native"
},
"selection_cursor": {
"description": "The cursor shown in the reserved leading cell of selected Inspector properties, list entries, and code point entries. It must be empty or occupy exactly one terminal cell.",
"type": "string",
"pattern": "^(?:|[^\\u0000-\\u001F\\u007F]+)$",
"default": ""
}
},
"additionalProperties": false
Expand Down
14 changes: 0 additions & 14 deletions docs/src/configurations/config-file-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ fg = "#f5f7fa"
bg = "#00000000"

[ui]
selection_cursor = ""
input_cursor = "native"
```

Expand Down Expand Up @@ -100,19 +99,6 @@ See [Glyph Preview](./glyph-preview.md) for examples and the complete font selec

## `ui`

### `selection_cursor`

The marker shown before a selected Inspector property, list entry, or code point.

- Type: string.
- Default: `""` (no visible marker).
- Must be empty or occupy exactly one terminal cell, without control characters.

```toml
[ui]
selection_cursor = "▸"
```

### `input_cursor`

The cursor shown in the search input.
Expand Down
26 changes: 1 addition & 25 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,23 +475,20 @@ mod tests {
}

#[test]
fn ui_config_is_loaded_and_unspecified_values_use_defaults() {
fn empty_ui_config_uses_defaults() {
let directory = tempdir().unwrap();
let path = directory.path().join("config.toml");
fs::write(
&path,
r#"
[ui]
selection_cursor = ">"
"#,
)
.unwrap();

let configured = load_config(Some(&ConfigFile::Explicit(path))).unwrap();
let defaults = default_runtime_config();

assert_eq!(configured.ui().selection_cursor, ">");
assert_eq!(defaults.ui().selection_cursor, "");
assert_eq!(
configured.ui().input_cursor,
crate::ui::settings::InputCursor::Native
Expand Down Expand Up @@ -556,27 +553,6 @@ mod tests {
assert!(message.contains("ui.input_cursor"));
}

#[test]
fn invalid_ui_config_includes_the_config_path_and_setting() {
let directory = tempdir().unwrap();
let path = directory.path().join("config.toml");
fs::write(
&path,
r#"
[ui]
selection_cursor = "界"
"#,
)
.unwrap();

let error = load_config(Some(&ConfigFile::Explicit(path.clone()))).unwrap_err();
let message = error.to_string();

assert!(matches!(error, ConfigError::Validate { .. }));
assert!(message.contains(&path.display().to_string()));
assert!(message.contains("ui.selection_cursor"));
}

#[test]
fn unknown_top_level_field_is_rejected_with_the_path() {
let directory = tempdir().unwrap();
Expand Down
71 changes: 17 additions & 54 deletions src/ui/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,13 @@ use crate::{
layout::browser,
scrollbar::{self, ViewportScrollbar},
selectable_list_line, selection_preview,
settings::UiSettings,
theme::ColorTheme,
workspace,
},
unicode::{CodePoint, Plane, UnicodeDatabase, plane::PlaneRange},
};

pub fn render(
frame: &mut Frame,
area: Rect,
state: &AppState,
color_theme: &ColorTheme,
ui: &UiSettings,
) {
pub fn render(frame: &mut Frame, area: Rect, state: &AppState, color_theme: &ColorTheme) {
let browse = state
.browse()
.expect("the browser view always has browse state");
Expand All @@ -46,7 +39,6 @@ pub fn render(
.visible_list_items()
.expect("the plane level has a list viewport"),
color_theme,
ui,
);
}
BrowseLevel::Range => {
Expand All @@ -61,7 +53,6 @@ pub fn render(
.visible_list_items()
.expect("the range level has a list viewport"),
color_theme,
ui,
);
}
BrowseLevel::Block => {
Expand All @@ -76,7 +67,6 @@ pub fn render(
.visible_list_items()
.expect("the block level has a list viewport"),
color_theme,
ui,
);
}
BrowseLevel::CodePointTable => {
Expand All @@ -98,7 +88,6 @@ pub fn render(
.visible_table_rows()
.expect("the code point table level has a row viewport"),
color_theme,
ui,
);
}
}
Expand All @@ -110,7 +99,6 @@ fn render_block_navigator(
browse: &BrowseState,
visible_items: std::ops::Range<usize>,
color_theme: &ColorTheme,
ui: &UiSettings,
) {
let selected = browse
.selected_block()
Expand All @@ -121,10 +109,9 @@ fn render_block_navigator(
.map(|index| {
let item = UnicodeDatabase::block(index).expect("the viewport contains valid blocks");
let is_selected = item == selected;
let marker = ui.selection_marker(is_selected);
selectable_list_line(
Line::from(format!(
"{marker} {:06X}–{:06X} {}",
"{:06X}–{:06X} {}",
item.start().value(),
item.end().value(),
item.name()
Expand Down Expand Up @@ -174,7 +161,6 @@ fn render_plane_navigator(
cursor: CodePoint,
visible_items: std::ops::Range<usize>,
color_theme: &ColorTheme,
ui: &UiSettings,
) {
let selected = Plane::for_code_point(cursor);
let content = workspace::render_primary_heading(frame, area, "Planes", None, color_theme);
Expand All @@ -183,10 +169,9 @@ fn render_plane_navigator(
.map(|number| {
let plane = Plane::new(number as u8).expect("the viewport contains valid planes");
let is_selected = plane == selected;
let marker = ui.selection_marker(is_selected);
selectable_list_line(
Line::from(format!(
"{marker} Plane {:>2} {}",
"Plane {:>2} {}",
plane.number(),
plane.name().unwrap_or("Reserved")
)),
Expand Down Expand Up @@ -230,7 +215,6 @@ fn render_range_navigator(
cursor: CodePoint,
visible_items: std::ops::Range<usize>,
color_theme: &ColorTheme,
ui: &UiSettings,
) {
let selected = PlaneRange::for_code_point(cursor);
let plane = format!("Plane {}", selected.plane().number());
Expand All @@ -241,9 +225,8 @@ fn render_range_navigator(
.map(|index| {
let range = selected.plane().range(index as u8);
let is_selected = range == selected;
let marker = ui.selection_marker(is_selected);
selectable_list_line(
Line::from(format!("{marker} {}–{}", range.start(), range.end())),
Line::from(format!("{}–{}", range.start(), range.end())),
is_selected,
content.width,
color_theme.selection,
Expand Down Expand Up @@ -298,7 +281,6 @@ fn render_code_point_table(
browse: &BrowseState,
visible_rows: std::ops::Range<usize>,
color_theme: &ColorTheme,
ui: &UiSettings,
) {
let cursor = browse.cursor();
let (page_start, page_end) = browse
Expand All @@ -322,7 +304,7 @@ fn render_code_point_table(
let code_point = CodePoint::new(row_start + column as u32)
.expect("table pages contain valid code points");
let selected = code_point == cursor;
let cell = table_cell(code_point, selected, ui);
let cell = table_cell(code_point);
spans.push(if selected {
Span::styled(cell, color_theme.selection.style())
} else {
Expand Down Expand Up @@ -355,7 +337,7 @@ fn table_column_header(selected_column: usize, color_theme: &ColorTheme) -> Line
Line::from(spans)
}

fn table_cell(code_point: CodePoint, selected: bool, ui: &UiSettings) -> String {
fn table_cell(code_point: CodePoint) -> String {
let representation = UnicodeDatabase::display_representation(code_point);
let width = Line::from(representation.as_str()).width();
let display = if matches!(width, 1 | 2) {
Expand All @@ -364,9 +346,7 @@ fn table_cell(code_point: CodePoint, selected: bool, ui: &UiSettings) -> String
"·"
};
let display_width = Line::from(display).width();
let marker = ui.selection_marker(selected);

format!("{marker}{display}{}", " ".repeat(2 - display_width))
format!(" {display}{}", " ".repeat(2 - display_width))
}

#[cfg(test)]
Expand All @@ -376,25 +356,20 @@ mod tests {
use super::*;

#[rstest]
#[case(0x0041, false, " A ")]
#[case(0x4e00, false, " 一")]
#[case(0x0301, true, ">◌́ ")]
#[case(0x000a, false, " · ")]
#[case(0x0378, false, " · ")]
#[case(0xd800, true, ">· ")]
#[case(0xe000, false, " · ")]
#[case(0xfdd0, false, " · ")]
#[case(0x0041, " A ")]
#[case(0x4e00, " 一")]
#[case(0x0301, " ◌́ ")]
#[case(0x000a, " · ")]
#[case(0x0378, " · ")]
#[case(0xd800, " · ")]
#[case(0xe000, " · ")]
#[case(0xfdd0, " · ")]
fn table_cells_use_only_safe_one_or_two_column_representations(
#[case] value: u32,
#[case] selected: bool,
#[case] expected: &str,
) {
let code_point = CodePoint::new(value).unwrap();
let ui = UiSettings {
selection_cursor: ">".to_owned(),
..Default::default()
};
let cell = table_cell(code_point, selected, &ui);
let cell = table_cell(code_point);

assert_eq!(cell, expected, "{code_point}");
assert_eq!(Line::from(cell).width(), 3, "{code_point}");
Expand All @@ -406,24 +381,12 @@ mod tests {

let cursor = CodePoint::new(0x0041).unwrap();
let range = PlaneRange::for_code_point(cursor);
let ui = UiSettings::default();
let mut row = format!("{:06X} ", range.start().value());
for offset in 0..16 {
let code_point = range.code_point(offset);
row.push_str(&table_cell(code_point, code_point == cursor, &ui));
row.push_str(&table_cell(code_point));
}

assert_eq!(Line::from(row).width(), 55);
}

#[test]
fn empty_selection_cursor_preserves_the_code_point_cell_width() {
let cursor = CodePoint::new(0x0041).unwrap();

assert_eq!(table_cell(cursor, true, &UiSettings::default()), " A ");
assert_eq!(
Line::from(table_cell(cursor, true, &UiSettings::default())).width(),
3
);
}
}
Loading
Loading