T10: following the current match takes one pass: a forward walk maps matches to cells, line ends are found once; the same occurrence ranks by an identical line, then one grown or cut at its end, then by line and column; a cleared or trimmed history moves no line; the title replays on CursorBlinkingChange, which a reset sends in the parser's order, synchronized updates too, and the tap's reset count is gone
This commit is contained in:
8 files changed
+185
-73
No files matched your search
@@ -57,6 +57,6 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
|
||||
- 2026-10-14: a wheel notch scrolls 3 lines, as Ghostty's discrete wheel; a trackpad scrolls its pixels. The wheel reports to a program that tracks the mouse, sends arrows on the alternate screen under mode 1007, and scrolls history otherwise.
|
||||
- 2026-10-14: Cmd-C and Cmd-V copy and paste on macOS, Ctrl-Shift-C and Ctrl-Shift-V elsewhere; Cmd-press, Ctrl-press off macOS, opens an OSC 8 target or a URL Ely's `links` finds in the wrapped line. Copies drop the newline alacritty ends a whole line with, as Ghostty's do. Bracketed pastes lose ESC and Ctrl-C, as alacritty's do; plain pastes end lines with CR. Typing and pasting clear the selection. Copy on select and OSC 52 writes to `p` or `s` go to the primary selection on Linux, as Ghostty's do; macOS and Windows have none, so they go to the clipboard.
|
||||
- 2026-10-14: a key's release goes out only when the terminal saw its press, so a key a binding takes sends no release, as in kitty.
|
||||
- 2026-10-14: find opens on Cmd-F (Ctrl-Shift-F off macOS) beside the grid, outside the `Terminal` key context, so its keys never reach the program. Matches wash in Ely's `warning` color, the current one stronger; the newest is current first, Enter steps older and Shift-Enter newer. Shift-Enter binds under `TerminalFind > ElyInput`, the context of Ely's text input, whose own Shift-Enter binding takes the key. Find reads the grid as it shows: combining marks ride their cells, hidden cells are blank, wrapped rows join, words are Unicode; alacritty's own search reads no marks and knows only ASCII word edges. New output, a resize and a settings change search again; the current match stays the one at the same place in the same line's text, nearest where scrolling moved it, else the nearest.
|
||||
- 2026-10-14: the program hears focus in and out under mode 1004 when the terminal's focus or the window's activation changes. The window wears the program's title, the app's name while it names none. A bell is logged and makes no sound. A full reset, `ESC c`, empties alacritty's title without a title event; the tap counts resets, and the view replays the title through `set_options`, which sends it.
|
||||
- 2026-10-14: find opens on Cmd-F (Ctrl-Shift-F off macOS) beside the grid, outside the `Terminal` key context, so its keys never reach the program. Matches wash in Ely's `warning` color, the current one stronger; the newest is current first, Enter steps older and Shift-Enter newer. Shift-Enter binds under `TerminalFind > ElyInput`, the context of Ely's text input, whose own Shift-Enter binding takes the key. Find reads the grid as it shows: combining marks ride their cells, hidden cells are blank, wrapped rows join, words are Unicode; alacritty's own search reads no marks and knows only ASCII word edges. New output, a resize and a settings change search again; the current match stays the same occurrence: at its place in the same line, or in one grown or cut at its end, nearest where scrolling moved it; else the nearest by line, then column. Searching and following take one pass over the text and the matches.
|
||||
- 2026-10-14: the program hears focus in and out under mode 1004 when the terminal's focus or the window's activation changes. The window wears the program's title, the app's name while it names none. A bell is logged and makes no sound. A full reset, `ESC c`, empties alacritty's title and sends only `CursorBlinkingChange`, in the parser's order, synchronized updates too; on that event the view replays the title through `set_options`, which sends it.
|
||||
- 2026-10-14: settings refuse unknown fields; a bad settings file stops the start and is never overwritten.
|
||||
+4
-2
@@ -40,8 +40,10 @@ impl Workspace {
|
||||
) {
|
||||
if let TerminalEvent::Title(title) = event {
|
||||
let title = if title.is_empty() { TITLE } else { title };
|
||||
log::debug!("app: the window title is {title:?}");
|
||||
window.set_window_title(title);
|
||||
if window.window_title() != title {
|
||||
log::debug!("app: the window title is {title:?}");
|
||||
window.set_window_title(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+96
-40
@@ -38,12 +38,13 @@ impl Finder {
|
||||
pub fn lit<T>(&self, term: &Term<T>, color: Hsla) -> Vec<(Match, Hsla)> {
|
||||
let offset = term.grid().display_offset() as i32;
|
||||
let (top, bottom) = (-offset, term.screen_lines() as i32 - 1 - offset);
|
||||
self.found
|
||||
.hits
|
||||
let hits = &self.found.hits;
|
||||
let first = hits.partition_point(|hit| hit.end().line.0 < top);
|
||||
hits[first..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, hit)| hit.end().line.0 >= top && hit.start().line.0 <= bottom)
|
||||
.map(|(at, hit)| {
|
||||
.take_while(|hit| hit.start().line.0 <= bottom)
|
||||
.zip(first..)
|
||||
.map(|(hit, at)| {
|
||||
let wash = if Some(at) == self.current { 0.55 } else { 0.25 };
|
||||
(hit.clone(), color.opacity(wash))
|
||||
})
|
||||
@@ -51,14 +52,17 @@ impl Finder {
|
||||
}
|
||||
}
|
||||
|
||||
/// One search of the grid: its shown text, and each match as cells and as bytes of that text.
|
||||
/// One search of the grid: its shown text and line ends, and each match as cells and as bytes.
|
||||
#[derive(Default)]
|
||||
struct Found {
|
||||
text: String,
|
||||
/// Where each newline sits in the text.
|
||||
breaks: Vec<usize>,
|
||||
hits: Vec<Match>,
|
||||
bytes: Vec<Range<usize>>,
|
||||
/// The grid's history then, to follow lines that scrolled since.
|
||||
/// The grid's history and size then, to follow lines that scrolled since.
|
||||
history: usize,
|
||||
size: (usize, usize),
|
||||
}
|
||||
|
||||
const SPACERS: Flags = Flags::WIDE_CHAR_SPACER.union(Flags::LEADING_WIDE_CHAR_SPACER);
|
||||
@@ -100,72 +104,124 @@ fn grid_text<T>(term: &Term<T>) -> (String, Vec<(usize, Line)>) {
|
||||
(text, rows)
|
||||
}
|
||||
|
||||
/// The cell showing byte `offset` of the grid's text; a line's end maps to the cell after its text.
|
||||
fn cell_at<T>(term: &Term<T>, rows: &[(usize, Line)], offset: usize) -> Point {
|
||||
let (mut end, line) = rows[rows.partition_point(|(start, _)| *start <= offset) - 1];
|
||||
let row = &term.grid()[line];
|
||||
for column in 0..term.columns() {
|
||||
let cell = &row[Column(column)];
|
||||
if !cell.flags.intersects(SPACERS) {
|
||||
end += shown(cell).map(char::len_utf8).sum::<usize>();
|
||||
/// Walks the grid's cells in text order, so rising byte offsets map to cells in one pass.
|
||||
struct Walk<'a, T> {
|
||||
term: &'a Term<T>,
|
||||
rows: &'a [(usize, Line)],
|
||||
row: usize,
|
||||
column: usize,
|
||||
/// The text's byte where `column` starts.
|
||||
at: usize,
|
||||
}
|
||||
|
||||
impl<T> Walk<'_, T> {
|
||||
/// The cell showing byte `offset`, at or past the last asked; a line's end maps past its text.
|
||||
fn cell(&mut self, offset: usize) -> Point {
|
||||
while self
|
||||
.rows
|
||||
.get(self.row + 1)
|
||||
.is_some_and(|(start, _)| *start <= offset)
|
||||
{
|
||||
self.row += 1;
|
||||
self.column = 0;
|
||||
self.at = self.rows[self.row].0;
|
||||
}
|
||||
if offset < end {
|
||||
return Point::new(line, Column(column));
|
||||
let line = self.rows[self.row].1;
|
||||
let row = &self.term.grid()[line];
|
||||
while self.column < self.term.columns() {
|
||||
let cell = &row[Column(self.column)];
|
||||
let size = match cell.flags.intersects(SPACERS) {
|
||||
true => 0,
|
||||
false => shown(cell).map(char::len_utf8).sum(),
|
||||
};
|
||||
if offset < self.at + size {
|
||||
return Point::new(line, Column(self.column));
|
||||
}
|
||||
self.at += size;
|
||||
self.column += 1;
|
||||
}
|
||||
Point::new(line, self.term.last_column())
|
||||
}
|
||||
Point::new(line, term.last_column())
|
||||
}
|
||||
|
||||
impl Found {
|
||||
/// Every match of `query`, oldest first; a pattern that does not compile is an error.
|
||||
fn search<T>(term: &Term<T>, query: &str, options: FindOptions) -> Result<Self, String> {
|
||||
let history = term.grid().history_size();
|
||||
let size = (term.columns(), term.screen_lines());
|
||||
if query.is_empty() {
|
||||
return Ok(Self {
|
||||
history,
|
||||
size,
|
||||
..Self::default()
|
||||
});
|
||||
}
|
||||
let (text, rows) = grid_text(term);
|
||||
let bytes = find_all(&text, query, options)?;
|
||||
let mut walk = Walk {
|
||||
term,
|
||||
rows: &rows,
|
||||
row: 0,
|
||||
column: 0,
|
||||
at: 0,
|
||||
};
|
||||
let hits = bytes
|
||||
.iter()
|
||||
.map(|range| cell_at(term, &rows, range.start)..=cell_at(term, &rows, range.end - 1))
|
||||
.map(|range| {
|
||||
let start = walk.cell(range.start);
|
||||
start..=walk.cell(range.end - 1)
|
||||
})
|
||||
.collect();
|
||||
let breaks = text.match_indices('\n').map(|(at, _)| at).collect();
|
||||
Ok(Self {
|
||||
text,
|
||||
breaks,
|
||||
hits,
|
||||
bytes,
|
||||
history,
|
||||
size,
|
||||
})
|
||||
}
|
||||
|
||||
/// The bytes of the line holding byte `offset`.
|
||||
fn line(&self, offset: usize) -> Range<usize> {
|
||||
let start = self.text[..offset].rfind('\n').map_or(0, |at| at + 1);
|
||||
let end = self.text[offset..]
|
||||
.find('\n')
|
||||
.map_or(self.text.len(), |at| offset + at);
|
||||
start..end
|
||||
let ends = self.breaks.partition_point(|&at| at < offset);
|
||||
let start = ends
|
||||
.checked_sub(1)
|
||||
.map_or(0, |before| self.breaks[before] + 1);
|
||||
start..self.breaks.get(ends).copied().unwrap_or(self.text.len())
|
||||
}
|
||||
|
||||
/// The match that was `then`'s `at`: same line and place, nearest its scrolled spot; else the nearest.
|
||||
/// The match that was `then`'s `at`: at its place in its line, kept, grown or cut; else the nearest.
|
||||
fn follow(&self, then: &Found, at: usize) -> Option<usize> {
|
||||
let was = then.bytes[at].start;
|
||||
let line = then.line(was);
|
||||
let (text, offset) = (&then.text[line.clone()], was - line.start);
|
||||
let scrolled = self.history as i32 - then.history as i32;
|
||||
let expected = then.hits[at].start().line.0 - scrolled;
|
||||
let distance = |index: &usize| (self.hits[*index].start().line.0 - expected).abs();
|
||||
let all = 0..self.hits.len();
|
||||
all.clone()
|
||||
.filter(|&index| {
|
||||
let start = self.bytes[index].start;
|
||||
let line = self.line(start);
|
||||
start - line.start == offset && self.text[line] == *text
|
||||
})
|
||||
.min_by_key(distance)
|
||||
.or_else(|| all.min_by_key(distance))
|
||||
let (whole, offset) = (&then.text[line.clone()], was - line.start);
|
||||
let place = *then.hits[at].start();
|
||||
let grew = self.history as i32 - then.history as i32;
|
||||
// A cleared or trimmed history moves no line; a resize pulls lines back down.
|
||||
let scrolled = if self.size == then.size {
|
||||
grew.max(0)
|
||||
} else {
|
||||
grew
|
||||
};
|
||||
let mut best: Option<((u8, i32, usize), usize)> = None;
|
||||
for (index, bytes) in self.bytes.iter().enumerate() {
|
||||
let line = self.line(bytes.start);
|
||||
let now = &self.text[line.clone()];
|
||||
let rank = match bytes.start - line.start == offset {
|
||||
true if now == whole => 0,
|
||||
true if now.starts_with(whole) || whole.starts_with(now) => 1,
|
||||
_ => 2,
|
||||
};
|
||||
let start = self.hits[index].start();
|
||||
let away = (start.line.0 + scrolled - place.line.0).abs();
|
||||
let key = (rank, away, start.column.0.abs_diff(place.column.0));
|
||||
if best.is_none_or(|(least, _)| key < least) {
|
||||
best = Some((key, index));
|
||||
}
|
||||
}
|
||||
best.map(|(_, index)| index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,9 +306,9 @@ impl TerminalView {
|
||||
);
|
||||
self.reveal(cx);
|
||||
} else {
|
||||
let count = finder.found.hits.len();
|
||||
let (count, current) = (finder.found.hits.len(), finder.current);
|
||||
log::trace!(
|
||||
"terminal {}: {count} matches as the grid changed",
|
||||
"terminal {}: {count} matches as the grid changed, current {current:?}",
|
||||
self.pty.pid
|
||||
);
|
||||
cx.notify();
|
||||
|
||||
@@ -131,6 +131,16 @@ fn the_current_match_follows_its_line() {
|
||||
"of like lines, the one that scrolled"
|
||||
);
|
||||
|
||||
let mut cleared = term("n\r\nn\r\nn\r\nn\r\nn", 10, 3, 10);
|
||||
let then = found(&cleared, "n", PLAIN);
|
||||
feed(&mut cleared, "\x1b[3J");
|
||||
let now = found(&cleared, "n", PLAIN);
|
||||
assert_eq!(
|
||||
now.follow(&then, 3),
|
||||
Some(1),
|
||||
"a cleared history moves no line"
|
||||
);
|
||||
|
||||
let mut gone = term("needle A\r\nneedle B\r\n\r\nneedle C", 20, 5, 10);
|
||||
let then = found(&gone, "needle", PLAIN);
|
||||
feed(&mut gone, "\x1b[2;1H\x1b[2K");
|
||||
@@ -146,6 +156,64 @@ fn the_current_match_follows_its_line() {
|
||||
assert_ne!(now.hits[0].start(), then.hits[0].start());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_line_that_grows_or_changes_keeps_the_match_at_its_place() {
|
||||
let mut grown = term("needle A needle B", 30, 3, 10);
|
||||
let then = found(&grown, "needle", PLAIN);
|
||||
feed(&mut grown, "!");
|
||||
let now = found(&grown, "needle", PLAIN);
|
||||
assert_eq!(
|
||||
now.follow(&then, 1),
|
||||
Some(1),
|
||||
"the same text runs through it"
|
||||
);
|
||||
|
||||
let mut ring = term(
|
||||
"needle 1\r\nneedle 2\r\nneedle 3\r\nneedle 4\r\nneedle 5",
|
||||
10,
|
||||
3,
|
||||
2,
|
||||
);
|
||||
let then = found(&ring, "needle", PLAIN);
|
||||
feed(&mut ring, "!\r\nneedle 6");
|
||||
let now = found(&ring, "needle", PLAIN);
|
||||
assert_eq!(now.follow(&then, 4), Some(3), "needle 5!, a line up");
|
||||
|
||||
let mut both = term(
|
||||
"needle 1\r\nneedle 2\r\nneedle 5\r\nneedle 6\r\nneedle 60",
|
||||
10,
|
||||
3,
|
||||
2,
|
||||
);
|
||||
let then = found(&both, "needle", PLAIN);
|
||||
feed(&mut both, "\r\nx");
|
||||
let now = found(&both, "needle", PLAIN);
|
||||
assert_eq!(
|
||||
now.follow(&then, 3),
|
||||
Some(2),
|
||||
"needle 6 itself before needle 60 in its place"
|
||||
);
|
||||
|
||||
let mut changed = term("needle A needle B", 30, 3, 10);
|
||||
let then = found(&changed, "needle", PLAIN);
|
||||
feed(&mut changed, "\rneedle X needle Y");
|
||||
let now = found(&changed, "needle", PLAIN);
|
||||
assert_eq!(now.follow(&then, 1), Some(1), "else the nearest column");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn following_a_long_line_takes_one_pass() {
|
||||
let mut long = term(&"a".repeat(1_000_000), 147, 48, 10_000);
|
||||
let then = found(&long, "a", PLAIN);
|
||||
feed(&mut long, "!");
|
||||
let now = found(&long, "a", PLAIN);
|
||||
let last = then.hits.len() - 1;
|
||||
let clock = std::time::Instant::now();
|
||||
assert_eq!(now.follow(&then, last), Some(last));
|
||||
let took = clock.elapsed();
|
||||
assert!(took.as_secs() < 5, "a quadratic follow took {took:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn steps_go_round_the_ends() {
|
||||
assert_eq!(stepped(Some(0), 3, false), Some(2));
|
||||
|
||||
+1
-3
@@ -20,8 +20,6 @@ pub enum PtyEvent {
|
||||
Term(Event),
|
||||
Folder(PathBuf),
|
||||
Mouse(MouseMode),
|
||||
/// A full reset, `ESC c`, which empties the title without a title event.
|
||||
Reset,
|
||||
}
|
||||
|
||||
/// Hands the grid's events to the session; `pid` names the program in logs.
|
||||
@@ -251,7 +249,7 @@ mod tests {
|
||||
PtyEvent::Folder(folder) => folders.push(folder),
|
||||
PtyEvent::Term(Event::ChildExit(status)) => success = status.success(),
|
||||
PtyEvent::Term(Event::Exit) => break,
|
||||
PtyEvent::Term(_) | PtyEvent::Mouse(_) | PtyEvent::Reset => {}
|
||||
PtyEvent::Term(_) | PtyEvent::Mouse(_) => {}
|
||||
}
|
||||
}
|
||||
(folders, success)
|
||||
|
||||
+2
-10
@@ -53,12 +53,7 @@ impl<P: EventedReadWrite> io::Read for Tapped<P> {
|
||||
log::debug!("pty {pid}: a folder report after its session closed");
|
||||
}
|
||||
});
|
||||
let resets = self.mouse.resets;
|
||||
let changed = self.mouse.feed(&buf[..count]);
|
||||
if self.mouse.resets != resets && events.unbounded_send(PtyEvent::Reset).is_err() {
|
||||
log::debug!("pty {pid}: a reset after its session closed");
|
||||
}
|
||||
if let Some(mode) = changed {
|
||||
if let Some(mode) = self.mouse.feed(&buf[..count]) {
|
||||
log::info!("pty {pid}: mouse {mode:?}");
|
||||
if events.unbounded_send(PtyEvent::Mouse(mode)).is_err() {
|
||||
log::debug!("pty {pid}: a mouse mode after its session closed");
|
||||
@@ -199,7 +194,7 @@ enum Csi {
|
||||
Private,
|
||||
}
|
||||
|
||||
/// The mouse modes as xterm holds them, and resets, read as vte reads; alacritty drops 9.
|
||||
/// The mouse modes as xterm holds them, read as vte reads; alacritty drops 9.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct MouseModes {
|
||||
state: Csi,
|
||||
@@ -209,8 +204,6 @@ pub(crate) struct MouseModes {
|
||||
/// Past a `:`, whose subparameters the parser's mode sets skip.
|
||||
sub: bool,
|
||||
mode: MouseMode,
|
||||
/// Full resets, `ESC c`, seen so far.
|
||||
pub resets: usize,
|
||||
}
|
||||
|
||||
impl MouseModes {
|
||||
@@ -237,7 +230,6 @@ impl MouseModes {
|
||||
(Csi::Escape, b'[') => Csi::Bracket,
|
||||
(Csi::Escape, b'c') => {
|
||||
self.mode = MouseMode::default();
|
||||
self.resets += 1;
|
||||
Csi::Ground
|
||||
}
|
||||
(Csi::Bracket, b'?') => {
|
||||
|
||||
@@ -50,7 +50,6 @@ fn mouse_modes_follow_private_modes_in_order_across_reads() {
|
||||
modes.feed(b"\x1b[?1002h");
|
||||
let reset = modes.feed(b"\x1bc");
|
||||
assert_eq!(reset, Some(MouseMode::default()), "a reset ends it");
|
||||
assert_eq!(modes.resets, 1);
|
||||
modes.feed(b"\x1b[?09;\x1b[?1003h");
|
||||
assert_eq!(modes.mode.tracking, Tracking::Any);
|
||||
}
|
||||
@@ -120,10 +119,10 @@ fn other_hosts_and_bad_uris_give_none() {
|
||||
assert_eq!(folder(b"file:///%FF"), None);
|
||||
}
|
||||
|
||||
/// vte's own reading of `bytes`, under the same mode rules: the mode, and the resets.
|
||||
fn parsed(bytes: &[u8]) -> (MouseMode, usize) {
|
||||
/// vte's own reading of `bytes`, under the same mode rules.
|
||||
fn parsed(bytes: &[u8]) -> MouseMode {
|
||||
use alacritty_terminal::vte::{Params, Parser, Perform};
|
||||
struct Modes(MouseMode, usize);
|
||||
struct Modes(MouseMode);
|
||||
impl Perform for Modes {
|
||||
fn csi_dispatch(&mut self, params: &Params, between: &[u8], ignore: bool, action: char) {
|
||||
if !ignore && between == b"?" && matches!(action, 'h' | 'l') {
|
||||
@@ -136,13 +135,12 @@ fn parsed(bytes: &[u8]) -> (MouseMode, usize) {
|
||||
fn esc_dispatch(&mut self, between: &[u8], _: bool, byte: u8) {
|
||||
if between.is_empty() && byte == b'c' {
|
||||
self.0 = MouseMode::default();
|
||||
self.1 += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut modes = Modes(MouseMode::default(), 0);
|
||||
let mut modes = Modes(MouseMode::default());
|
||||
Parser::new().advance(&mut modes, bytes);
|
||||
(modes.0, modes.1)
|
||||
modes.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -176,8 +174,7 @@ fn the_tap_reads_mouse_modes_as_vte_does_byte_by_byte() {
|
||||
let mut modes = MouseModes::default();
|
||||
for end in 1..=case.len() {
|
||||
modes.feed(&case[end - 1..end]);
|
||||
let seen = (modes.mode, modes.resets);
|
||||
assert_eq!(seen, parsed(&case[..end]), "{case:?} to byte {end}");
|
||||
assert_eq!(modes.mode, parsed(&case[..end]), "{case:?} to byte {end}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -204,12 +204,6 @@ impl TerminalView {
|
||||
PtyEvent::Folder(folder) => cx.emit(TerminalEvent::Folder(folder)),
|
||||
PtyEvent::Term(event) => self.term_event(event, cx),
|
||||
PtyEvent::Mouse(mode) => self.mouse = mode,
|
||||
PtyEvent::Reset => {
|
||||
log::debug!("terminal {}: a reset; the title follows", self.pty.pid);
|
||||
// Setting the options sends the grid's title, empty after a reset.
|
||||
let settings = config(SettingsStore::get(cx));
|
||||
self.pty.term.lock().set_options(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
// ponytail: a full search per output batch while find is open; search only new lines if it lags.
|
||||
@@ -222,7 +216,12 @@ impl TerminalView {
|
||||
fn term_event(&mut self, event: Event, cx: &mut Context<Self>) {
|
||||
match event {
|
||||
Event::Wakeup => self.shown = true,
|
||||
Event::MouseCursorDirty | Event::CursorBlinkingChange => {}
|
||||
Event::MouseCursorDirty => {}
|
||||
// A reset empties the title and sends only this; setting the options sends the title.
|
||||
Event::CursorBlinkingChange => {
|
||||
let settings = config(SettingsStore::get(cx));
|
||||
self.pty.term.lock().set_options(settings);
|
||||
}
|
||||
Event::Title(title) => cx.emit(TerminalEvent::Title(title)),
|
||||
Event::ResetTitle => cx.emit(TerminalEvent::Title(String::new())),
|
||||
Event::Bell => {
|
||||
|
||||
Reference in new issue
Block a user