@@ -0,0 +1,134 @@
+use alacritty_terminal::{ansi::Color as AnsiColor, term::color::Rgb as AlacRgb};
+use gpui::color::Color;
+use theme::TerminalStyle;
+
+///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
+pub fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
+ match alac_color {
+ //Named and theme defined colors
+ alacritty_terminal::ansi::Color::Named(n) => match n {
+ alacritty_terminal::ansi::NamedColor::Black => style.black,
+ alacritty_terminal::ansi::NamedColor::Red => style.red,
+ alacritty_terminal::ansi::NamedColor::Green => style.green,
+ alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
+ alacritty_terminal::ansi::NamedColor::Blue => style.blue,
+ alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
+ alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
+ alacritty_terminal::ansi::NamedColor::White => style.white,
+ alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
+ alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
+ alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
+ alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
+ alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
+ alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
+ alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
+ alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
+ alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
+ alacritty_terminal::ansi::NamedColor::Background => style.background,
+ alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
+ alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
+ alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
+ alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
+ alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
+ alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
+ alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
+ alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
+ alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
+ alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
+ alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
+ },
+ //'True' colors
+ alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
+ //8 bit, indexed colors
+ alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(&(*i as usize), style),
+ }
+}
+
+///Converts an 8 bit ANSI color to it's GPUI equivalent.
+///Accepts usize for compatability with the alacritty::Colors interface,
+///Other than that use case, should only be called with values in the [0,255] range
+pub fn get_color_at_index(index: &usize, style: &TerminalStyle) -> Color {
+ match index {
+ //0-15 are the same as the named colors above
+ 0 => style.black,
+ 1 => style.red,
+ 2 => style.green,
+ 3 => style.yellow,
+ 4 => style.blue,
+ 5 => style.magenta,
+ 6 => style.cyan,
+ 7 => style.white,
+ 8 => style.bright_black,
+ 9 => style.bright_red,
+ 10 => style.bright_green,
+ 11 => style.bright_yellow,
+ 12 => style.bright_blue,
+ 13 => style.bright_magenta,
+ 14 => style.bright_cyan,
+ 15 => style.bright_white,
+ //16-231 are mapped to their RGB colors on a 0-5 range per channel
+ 16..=231 => {
+ let (r, g, b) = rgb_for_index(&(*index as u8)); //Split the index into it's ANSI-RGB components
+ let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
+ Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
+ }
+ //232-255 are a 24 step grayscale from black to white
+ 232..=255 => {
+ let i = *index as u8 - 232; //Align index to 0..24
+ let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
+ Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
+ }
+ //For compatability with the alacritty::Colors interface
+ 256 => style.foreground,
+ 257 => style.background,
+ 258 => style.cursor,
+ 259 => style.dim_black,
+ 260 => style.dim_red,
+ 261 => style.dim_green,
+ 262 => style.dim_yellow,
+ 263 => style.dim_blue,
+ 264 => style.dim_magenta,
+ 265 => style.dim_cyan,
+ 266 => style.dim_white,
+ 267 => style.bright_foreground,
+ 268 => style.black, //'Dim Background', non-standard color
+ _ => Color::new(0, 0, 0, 255),
+ }
+}
+///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
+///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
+///
+///Wikipedia gives a formula for calculating the index for a given color:
+///
+///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
+///
+///This function does the reverse, calculating the r, g, and b components from a given index.
+fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
+ debug_assert!(i >= &16 && i <= &231);
+ let i = i - 16;
+ let r = (i - (i % 36)) / 36;
+ let g = ((i % 36) - (i % 6)) / 6;
+ let b = (i % 36) % 6;
+ (r, g, b)
+}
+
+//Convenience method to convert from a GPUI color to an alacritty Rgb
+pub fn to_alac_rgb(color: Color) -> AlacRgb {
+ AlacRgb {
+ r: color.r,
+ g: color.g,
+ b: color.g,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_rgb_for_index() {
+ //Test every possible value in the color cube
+ for i in 16..=231 {
+ let (r, g, b) = crate::color_translation::rgb_for_index(&(i as u8));
+ assert_eq!(i, 16 + 36 * r + 6 * g + b);
+ }
+ }
+}
@@ -4,19 +4,19 @@ use alacritty_terminal::{
event_loop::{EventLoop, Msg, Notifier},
grid::Scroll,
sync::FairMutex,
- term::{color::Rgb as AlacRgb, SizeInfo},
+ term::SizeInfo,
tty::{self, setup_env},
Term,
};
-
+use color_translation::{get_color_at_index, to_alac_rgb};
use dirs::home_dir;
use futures::{
channel::mpsc::{unbounded, UnboundedSender},
StreamExt,
};
use gpui::{
- actions, color::Color, elements::*, impl_internal_actions, platform::CursorStyle,
- ClipboardItem, Entity, MutableAppContext, View, ViewContext,
+ actions, elements::*, impl_internal_actions, platform::CursorStyle, ClipboardItem, Entity,
+ MutableAppContext, View, ViewContext,
};
use project::{LocalWorktree, Project, ProjectPath};
use settings::Settings;
@@ -24,7 +24,7 @@ use smallvec::SmallVec;
use std::{collections::HashMap, path::PathBuf, sync::Arc};
use workspace::{Item, Workspace};
-use crate::terminal_element::{get_color_at_index, TerminalEl};
+use crate::terminal_element::TerminalEl;
//ASCII Control characters on a keyboard
const ETX_CHAR: char = 3_u8 as char; //'End of text', the control code for 'ctrl-c'
@@ -37,7 +37,12 @@ const RIGHT_SEQ: &str = "\x1b[C";
const UP_SEQ: &str = "\x1b[A";
const DOWN_SEQ: &str = "\x1b[B";
const DEFAULT_TITLE: &str = "Terminal";
+const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space.
+const DEBUG_TERMINAL_HEIGHT: f32 = 200.;
+const DEBUG_CELL_WIDTH: f32 = 5.;
+const DEBUG_LINE_HEIGHT: f32 = 5.;
+pub mod color_translation;
pub mod gpui_func_tools;
pub mod terminal_element;
@@ -51,7 +56,7 @@ pub struct ScrollTerminal(pub i32);
actions!(
terminal,
- [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Paste, Deploy, Quit]
+ [Sigint, Escape, Del, Return, Left, Right, Up, Down, Tab, Clear, Copy, Paste, Deploy, Quit]
);
impl_internal_actions!(terminal, [Input, ScrollTerminal]);
@@ -63,12 +68,13 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Terminal::escape);
cx.add_action(Terminal::quit);
cx.add_action(Terminal::del);
- cx.add_action(Terminal::carriage_return); //TODO figure out how to do this properly. Should we be checking the terminal mode?
+ cx.add_action(Terminal::carriage_return);
cx.add_action(Terminal::left);
cx.add_action(Terminal::right);
cx.add_action(Terminal::up);
cx.add_action(Terminal::down);
cx.add_action(Terminal::tab);
+ cx.add_action(Terminal::copy);
cx.add_action(Terminal::paste);
cx.add_action(Terminal::scroll_terminal);
}
@@ -126,12 +132,11 @@ impl Terminal {
.detach();
let pty_config = PtyConfig {
- shell: None,
+ shell: None, //Use the users default shell
working_directory: working_directory.clone(),
hold: false,
};
- //Does this mangle the zed Env? I'm guessing it does... do child processes have a seperate ENV?
let mut env: HashMap<String, String> = HashMap::new();
//TODO: Properly set the current locale,
env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
@@ -145,8 +150,15 @@ impl Terminal {
setup_env(&config);
//The details here don't matter, the terminal will be resized on the first layout
- //Set to something small for easier debugging
- let size_info = SizeInfo::new(200., 100.0, 5., 5., 0., 0., false);
+ let size_info = SizeInfo::new(
+ DEBUG_TERMINAL_WIDTH,
+ DEBUG_TERMINAL_HEIGHT,
+ DEBUG_CELL_WIDTH,
+ DEBUG_LINE_HEIGHT,
+ 0.,
+ 0.,
+ false,
+ );
//Set up the terminal...
let term = Term::new(&config, size_info, ZedListener(events_tx.clone()));
@@ -222,24 +234,7 @@ impl Terminal {
AlacTermEvent::ColorRequest(index, format) => {
let color = self.term.lock().colors()[index].unwrap_or_else(|| {
let term_style = &cx.global::<Settings>().theme.terminal;
- match index {
- 0..=255 => to_alac_rgb(get_color_at_index(&(index as u8), term_style)),
- //These additional values are required to match the Alacritty Colors object's behavior
- 256 => to_alac_rgb(term_style.foreground),
- 257 => to_alac_rgb(term_style.background),
- 258 => to_alac_rgb(term_style.cursor),
- 259 => to_alac_rgb(term_style.dim_black),
- 260 => to_alac_rgb(term_style.dim_red),
- 261 => to_alac_rgb(term_style.dim_green),
- 262 => to_alac_rgb(term_style.dim_yellow),
- 263 => to_alac_rgb(term_style.dim_blue),
- 264 => to_alac_rgb(term_style.dim_magenta),
- 265 => to_alac_rgb(term_style.dim_cyan),
- 266 => to_alac_rgb(term_style.dim_white),
- 267 => to_alac_rgb(term_style.bright_foreground),
- 268 => to_alac_rgb(term_style.black), //Dim Background, non-standard
- _ => AlacRgb { r: 0, g: 0, b: 0 },
- }
+ to_alac_rgb(get_color_at_index(&index, term_style))
});
self.write_to_pty(&Input(format(color)), cx)
}
@@ -291,6 +286,16 @@ impl Terminal {
cx.emit(Event::CloseTerminal);
}
+ ///Attempt to paste the clipboard into the terminal
+ fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
+ let term = self.term.lock();
+ let copy_text = term.selection_to_string();
+ match copy_text {
+ Some(s) => cx.write_to_clipboard(ClipboardItem::new(s)),
+ None => (),
+ }
+ }
+
///Attempt to paste the clipboard into the terminal
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(item) = cx.read_from_clipboard() {
@@ -479,15 +484,6 @@ impl Item for Terminal {
}
}
-//Convenience method for less lines
-fn to_alac_rgb(color: Color) -> AlacRgb {
- AlacRgb {
- r: color.r,
- g: color.g,
- b: color.g,
- }
-}
-
fn get_working_directory(wt: &LocalWorktree) -> Option<PathBuf> {
Some(wt.abs_path().to_path_buf())
.filter(|path| path.is_dir())
@@ -497,13 +493,17 @@ fn get_working_directory(wt: &LocalWorktree) -> Option<PathBuf> {
#[cfg(test)]
mod tests {
- use std::{path::Path, sync::atomic::AtomicUsize, time::Duration};
-
use super::*;
- use alacritty_terminal::{grid::GridIterator, term::cell::Cell};
+ use alacritty_terminal::{
+ grid::GridIterator,
+ index::{Column, Line, Point, Side},
+ selection::{Selection, SelectionType},
+ term::cell::Cell,
+ };
use gpui::TestAppContext;
use itertools::Itertools;
use project::{FakeFs, Fs, RealFs, RemoveOptions, Worktree};
+ use std::{path::Path, sync::atomic::AtomicUsize, time::Duration};
///Basic integration test, can we get the terminal to show up, execute a command,
//and produce noticable output?
@@ -511,7 +511,6 @@ mod tests {
async fn test_terminal(cx: &mut TestAppContext) {
let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None));
cx.set_condition_duration(Duration::from_secs(2));
-
terminal.update(cx, |terminal, cx| {
terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx);
terminal.carriage_return(&Return, cx);
@@ -521,20 +520,12 @@ mod tests {
.condition(cx, |terminal, _cx| {
let term = terminal.term.clone();
let content = grid_as_str(term.lock().renderable_content().display_iter);
+ dbg!(&content);
content.contains("7")
})
.await;
}
- pub(crate) fn grid_as_str(grid_iterator: GridIterator<Cell>) -> String {
- let lines = grid_iterator.group_by(|i| i.point.line.0);
- lines
- .into_iter()
- .map(|(_, line)| line.map(|i| i.c).collect::<String>())
- .collect::<Vec<String>>()
- .join("\n")
- }
-
#[gpui::test]
async fn single_file_worktree(cx: &mut TestAppContext) {
let mut async_cx = cx.to_async();
@@ -615,4 +606,46 @@ mod tests {
.ok()
.expect("Could not remove test directory");
}
+
+ ///If this test is failing for you, check that DEBUG_TERMINAL_WIDTH is wide enough to fit your entire command prompt!
+ #[gpui::test]
+ async fn test_copy(cx: &mut TestAppContext) {
+ let terminal = cx.add_view(Default::default(), |cx| Terminal::new(cx, None));
+ cx.set_condition_duration(Duration::from_secs(2));
+
+ terminal.update(cx, |terminal, cx| {
+ terminal.write_to_pty(&Input(("expr 3 + 4".to_string()).to_string()), cx);
+ terminal.carriage_return(&Return, cx);
+ });
+
+ terminal
+ .condition(cx, |terminal, _cx| {
+ let term = terminal.term.clone();
+ let content = grid_as_str(term.lock().renderable_content().display_iter);
+ content.contains("7")
+ })
+ .await;
+
+ terminal.update(cx, |terminal, cx| {
+ let mut term = terminal.term.lock();
+ term.selection = Some(Selection::new(
+ SelectionType::Semantic,
+ Point::new(Line(2), Column(0)),
+ Side::Right,
+ ));
+ drop(term);
+ terminal.copy(&Copy, cx)
+ });
+
+ cx.assert_clipboard_content(Some(&"7"));
+ }
+
+ pub(crate) fn grid_as_str(grid_iterator: GridIterator<Cell>) -> String {
+ let lines = grid_iterator.group_by(|i| i.point.line.0);
+ lines
+ .into_iter()
+ .map(|(_, line)| line.map(|i| i.c).collect::<String>())
+ .collect::<Vec<String>>()
+ .join("\n")
+ }
}
@@ -1,13 +1,15 @@
use alacritty_terminal::{
- ansi::Color as AnsiColor,
grid::{Dimensions, GridIterator, Indexed},
- index::Point,
+ index::{Column as GridCol, Line as GridLine, Point, Side},
+ selection::{Selection, SelectionRange, SelectionType},
+ sync::FairMutex,
term::{
cell::{Cell, Flags},
SizeInfo,
},
+ Term,
};
-use editor::{Cursor, CursorShape};
+use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
use gpui::{
color::Color,
elements::*,
@@ -24,10 +26,14 @@ use gpui::{
use itertools::Itertools;
use ordered_float::OrderedFloat;
use settings::Settings;
-use std::rc::Rc;
+use std::{cmp::min, ops::Range, rc::Rc, sync::Arc};
+use std::{fmt::Debug, ops::Sub};
use theme::TerminalStyle;
-use crate::{gpui_func_tools::paint_layer, Input, ScrollTerminal, Terminal};
+use crate::{
+ color_translation::convert_color, gpui_func_tools::paint_layer, Input, ScrollTerminal,
+ Terminal, ZedListener,
+};
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
///Scroll multiplier that is set to 3 by default. This will be removed when I
@@ -44,14 +50,27 @@ pub struct TerminalEl {
view: WeakViewHandle<Terminal>,
}
-///Helper types so I don't mix these two up
+///New type pattern so I don't mix these two up
struct CellWidth(f32);
struct LineHeight(f32);
+struct LayoutLine {
+ cells: Vec<LayoutCell>,
+ highlighted_range: Option<Range<usize>>,
+}
+
+///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than
+struct PaneRelativePos(Vector2F);
+
+///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position
+fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos {
+ PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating
+}
+
#[derive(Clone, Debug, Default)]
struct LayoutCell {
point: Point<i32, i32>,
- text: Line,
+ text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN!
background_color: Color,
}
@@ -67,13 +86,14 @@ impl LayoutCell {
///The information generated during layout that is nescessary for painting
pub struct LayoutState {
- cells: Vec<(Point<i32, i32>, Line)>,
- background_rects: Vec<(RectF, Color)>, //Vec index == Line index for the LineSpan
+ layout_lines: Vec<LayoutLine>,
line_height: LineHeight,
em_width: CellWidth,
cursor: Option<Cursor>,
background_color: Color,
cur_size: SizeInfo,
+ terminal: Arc<FairMutex<Term<ZedListener>>>,
+ selection_color: Color,
}
impl TerminalEl {
@@ -105,46 +125,30 @@ impl Element for TerminalEl {
//Tell the view our new size. Requires a mutable borrow of cx and the view
let cur_size = make_new_size(constraint, &cell_width, &line_height);
//Note that set_size locks and mutates the terminal.
- //TODO: Would be nice to lock once for the whole of layout
view_handle.update(cx.app, |view, _cx| view.set_size(cur_size));
//Now that we're done with the mutable portion, grab the immutable settings and view again
- let terminal_theme = &(cx.global::<Settings>()).theme.terminal;
- let term = view_handle.read(cx).term.lock();
+ let (selection_color, terminal_theme) = {
+ let theme = &(cx.global::<Settings>()).theme;
+ (theme.editor.selection.selection, &theme.terminal)
+ };
+ let terminal_mutex = view_handle.read(cx).term.clone();
+ let term = terminal_mutex.lock();
let grid = term.grid();
let cursor_point = grid.cursor.point;
let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string();
let content = term.renderable_content();
- let layout_cells = layout_cells(
+ let layout_lines = layout_lines(
content.display_iter,
&text_style,
terminal_theme,
cx.text_layout_cache,
+ content.selection,
);
- let cells = layout_cells
- .iter()
- .map(|c| (c.point, c.text.clone()))
- .collect::<Vec<(Point<i32, i32>, Line)>>();
- let background_rects = layout_cells
- .iter()
- .map(|cell| {
- (
- RectF::new(
- vec2f(
- cell.point.column as f32 * cell_width.0,
- cell.point.line as f32 * line_height.0,
- ),
- vec2f(cell_width.0, line_height.0),
- ),
- cell.background_color,
- )
- })
- .collect::<Vec<(RectF, Color)>>();
-
let block_text = cx.text_layout_cache.layout_str(
&cursor_text,
text_style.font_size,
@@ -183,17 +187,19 @@ impl Element for TerminalEl {
Some(block_text.clone()),
)
});
+ drop(term);
(
constraint.max,
LayoutState {
- cells,
+ layout_lines,
line_height,
em_width: cell_width,
cursor,
cur_size,
- background_rects,
background_color: terminal_theme.background,
+ terminal: terminal_mutex,
+ selection_color,
},
)
}
@@ -207,17 +213,21 @@ impl Element for TerminalEl {
) -> Self::PaintState {
//Setup element stuff
let clip_bounds = Some(visible_bounds);
- paint_layer(cx, clip_bounds, |cx| {
- //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
- cx.scene.push_mouse_region(MouseRegion {
- view_id: self.view.id(),
- mouse_down: Some(Rc::new(|_, cx| cx.focus_parent_view())),
- bounds: visible_bounds,
- ..Default::default()
- });
+ paint_layer(cx, clip_bounds, |cx| {
+ let cur_size = layout.cur_size.clone();
let origin = bounds.origin() + vec2f(layout.em_width.0, 0.);
+ //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
+ attach_mouse_handlers(
+ origin,
+ cur_size,
+ self.view.id(),
+ &layout.terminal,
+ visible_bounds,
+ cx,
+ );
+
paint_layer(cx, clip_bounds, |cx| {
//Start with a background color
cx.scene.push_quad(Quad {
@@ -228,25 +238,83 @@ impl Element for TerminalEl {
});
//Draw cell backgrounds
- for background_rect in &layout.background_rects {
- let new_origin = origin + background_rect.0.origin();
- cx.scene.push_quad(Quad {
- bounds: RectF::new(new_origin, background_rect.0.size()),
- background: Some(background_rect.1),
- border: Default::default(),
- corner_radius: 0.,
+ for layout_line in &layout.layout_lines {
+ for layout_cell in &layout_line.cells {
+ let position = vec2f(
+ origin.x() + layout_cell.point.column as f32 * layout.em_width.0,
+ origin.y() + layout_cell.point.line as f32 * layout.line_height.0,
+ );
+ let size = vec2f(layout.em_width.0, layout.line_height.0);
+
+ cx.scene.push_quad(Quad {
+ bounds: RectF::new(position, size),
+ background: Some(layout_cell.background_color),
+ border: Default::default(),
+ corner_radius: 0.,
+ })
+ }
+ }
+ });
+
+ //Draw Selection
+ paint_layer(cx, clip_bounds, |cx| {
+ let mut highlight_y = None;
+ let highlight_lines = layout
+ .layout_lines
+ .iter()
+ .filter_map(|line| {
+ if let Some(range) = &line.highlighted_range {
+ if let None = highlight_y {
+ highlight_y = Some(
+ origin.y()
+ + line.cells[0].point.line as f32 * layout.line_height.0,
+ );
+ }
+ let start_x = origin.x()
+ + line.cells[range.start].point.column as f32 * layout.em_width.0;
+ let end_x = origin.x()
+ + line.cells[range.end].point.column as f32 * layout.em_width.0
+ + layout.em_width.0;
+
+ return Some(HighlightedRangeLine { start_x, end_x });
+ } else {
+ return None;
+ }
})
+ .collect::<Vec<HighlightedRangeLine>>();
+
+ if let Some(y) = highlight_y {
+ let hr = HighlightedRange {
+ start_y: y, //Need to change this
+ line_height: layout.line_height.0,
+ lines: highlight_lines,
+ color: layout.selection_color,
+ //Copied from editor. TODO: move to theme or something
+ corner_radius: 0.15 * layout.line_height.0,
+ };
+ hr.paint(bounds, cx.scene);
}
});
//Draw text
paint_layer(cx, clip_bounds, |cx| {
- for (point, cell) in &layout.cells {
- let cell_origin = vec2f(
- origin.x() + point.column as f32 * layout.em_width.0,
- origin.y() + point.line as f32 * layout.line_height.0,
- );
- cell.paint(cell_origin, visible_bounds, layout.line_height.0, cx);
+ for layout_line in &layout.layout_lines {
+ for layout_cell in &layout_line.cells {
+ let point = layout_cell.point;
+
+ //Don't actually know the start_x for a line, until here:
+ let cell_origin = vec2f(
+ origin.x() + point.column as f32 * layout.em_width.0,
+ origin.y() + point.line as f32 * layout.line_height.0,
+ );
+
+ layout_cell.text.paint(
+ cell_origin,
+ visible_bounds,
+ layout.line_height.0,
+ cx,
+ );
+ }
}
});
@@ -311,6 +379,18 @@ impl Element for TerminalEl {
}
}
+fn mouse_to_cell_data(
+ pos: Vector2F,
+ origin: Vector2F,
+ cur_size: SizeInfo,
+ display_offset: usize,
+) -> (Point, alacritty_terminal::index::Direction) {
+ let relative_pos = relative_pos(pos, origin);
+ let point = grid_cell(&relative_pos, cur_size, display_offset);
+ let side = cell_side(&relative_pos, cur_size);
+ (point, side)
+}
+
///Configures a text style from the current settings.
fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
TextStyle {
@@ -343,38 +423,56 @@ fn make_new_size(
)
}
-fn layout_cells(
+fn layout_lines(
grid: GridIterator<Cell>,
text_style: &TextStyle,
terminal_theme: &TerminalStyle,
text_layout_cache: &TextLayoutCache,
-) -> Vec<LayoutCell> {
- let mut line_count: i32 = 0;
+ selection_range: Option<SelectionRange>,
+) -> Vec<LayoutLine> {
let lines = grid.group_by(|i| i.point.line);
lines
.into_iter()
- .map(|(_, line)| {
- line_count += 1;
- line.map(|indexed_cell| {
- let cell_text = &indexed_cell.c.to_string();
-
- let cell_style = cell_style(&indexed_cell, terminal_theme, text_style);
-
- let layout_cell = text_layout_cache.layout_str(
- cell_text,
- text_style.font_size,
- &[(cell_text.len(), cell_style)],
- );
- LayoutCell::new(
- Point::new(line_count - 1, indexed_cell.point.column.0 as i32),
- layout_cell,
- convert_color(&indexed_cell.bg, terminal_theme),
- )
- })
- .collect::<Vec<LayoutCell>>()
+ .enumerate()
+ .map(|(line_index, (_, line))| {
+ let mut highlighted_range = None;
+ let cells = line
+ .enumerate()
+ .map(|(x_index, indexed_cell)| {
+ if selection_range
+ .map(|range| range.contains(indexed_cell.point))
+ .unwrap_or(false)
+ {
+ let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
+ range.end = range.end.max(x_index);
+ highlighted_range = Some(range);
+ }
+
+ let cell_text = &indexed_cell.c.to_string();
+
+ let cell_style = cell_style(&indexed_cell, terminal_theme, text_style);
+
+ //This is where we might be able to get better performance
+ let layout_cell = text_layout_cache.layout_str(
+ cell_text,
+ text_style.font_size,
+ &[(cell_text.len(), cell_style)],
+ );
+
+ LayoutCell::new(
+ Point::new(line_index as i32, indexed_cell.point.column.0 as i32),
+ layout_cell,
+ convert_color(&indexed_cell.bg, terminal_theme),
+ )
+ })
+ .collect::<Vec<LayoutCell>>();
+
+ LayoutLine {
+ cells,
+ highlighted_range,
+ }
})
- .flatten()
- .collect::<Vec<LayoutCell>>()
+ .collect::<Vec<LayoutLine>>()
}
// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
@@ -430,98 +528,113 @@ fn cell_style(indexed: &Indexed<&Cell>, style: &TerminalStyle, text_style: &Text
}
}
-///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
-fn convert_color(alac_color: &AnsiColor, style: &TerminalStyle) -> Color {
- match alac_color {
- //Named and theme defined colors
- alacritty_terminal::ansi::Color::Named(n) => match n {
- alacritty_terminal::ansi::NamedColor::Black => style.black,
- alacritty_terminal::ansi::NamedColor::Red => style.red,
- alacritty_terminal::ansi::NamedColor::Green => style.green,
- alacritty_terminal::ansi::NamedColor::Yellow => style.yellow,
- alacritty_terminal::ansi::NamedColor::Blue => style.blue,
- alacritty_terminal::ansi::NamedColor::Magenta => style.magenta,
- alacritty_terminal::ansi::NamedColor::Cyan => style.cyan,
- alacritty_terminal::ansi::NamedColor::White => style.white,
- alacritty_terminal::ansi::NamedColor::BrightBlack => style.bright_black,
- alacritty_terminal::ansi::NamedColor::BrightRed => style.bright_red,
- alacritty_terminal::ansi::NamedColor::BrightGreen => style.bright_green,
- alacritty_terminal::ansi::NamedColor::BrightYellow => style.bright_yellow,
- alacritty_terminal::ansi::NamedColor::BrightBlue => style.bright_blue,
- alacritty_terminal::ansi::NamedColor::BrightMagenta => style.bright_magenta,
- alacritty_terminal::ansi::NamedColor::BrightCyan => style.bright_cyan,
- alacritty_terminal::ansi::NamedColor::BrightWhite => style.bright_white,
- alacritty_terminal::ansi::NamedColor::Foreground => style.foreground,
- alacritty_terminal::ansi::NamedColor::Background => style.background,
- alacritty_terminal::ansi::NamedColor::Cursor => style.cursor,
- alacritty_terminal::ansi::NamedColor::DimBlack => style.dim_black,
- alacritty_terminal::ansi::NamedColor::DimRed => style.dim_red,
- alacritty_terminal::ansi::NamedColor::DimGreen => style.dim_green,
- alacritty_terminal::ansi::NamedColor::DimYellow => style.dim_yellow,
- alacritty_terminal::ansi::NamedColor::DimBlue => style.dim_blue,
- alacritty_terminal::ansi::NamedColor::DimMagenta => style.dim_magenta,
- alacritty_terminal::ansi::NamedColor::DimCyan => style.dim_cyan,
- alacritty_terminal::ansi::NamedColor::DimWhite => style.dim_white,
- alacritty_terminal::ansi::NamedColor::BrightForeground => style.bright_foreground,
- alacritty_terminal::ansi::NamedColor::DimForeground => style.dim_foreground,
- },
- //'True' colors
- alacritty_terminal::ansi::Color::Spec(rgb) => Color::new(rgb.r, rgb.g, rgb.b, u8::MAX),
- //8 bit, indexed colors
- alacritty_terminal::ansi::Color::Indexed(i) => get_color_at_index(i, style),
- }
+fn attach_mouse_handlers(
+ origin: Vector2F,
+ cur_size: SizeInfo,
+ view_id: usize,
+ terminal_mutex: &Arc<FairMutex<Term<ZedListener>>>,
+ visible_bounds: RectF,
+ cx: &mut PaintContext,
+) {
+ let click_mutex = terminal_mutex.clone();
+ let drag_mutex = terminal_mutex.clone();
+ let mouse_down_mutex = terminal_mutex.clone();
+
+ cx.scene.push_mouse_region(MouseRegion {
+ view_id,
+ mouse_down: Some(Rc::new(move |pos, _| {
+ let mut term = mouse_down_mutex.lock();
+ let (point, side) = mouse_to_cell_data(
+ pos,
+ origin,
+ cur_size,
+ term.renderable_content().display_offset,
+ );
+ term.selection = Some(Selection::new(SelectionType::Simple, point, side))
+ })),
+ click: Some(Rc::new(move |pos, click_count, cx| {
+ let mut term = click_mutex.lock();
+
+ let (point, side) = mouse_to_cell_data(
+ pos,
+ origin,
+ cur_size,
+ term.renderable_content().display_offset,
+ );
+
+ let selection_type = match click_count {
+ 0 => return, //This is a release
+ 1 => Some(SelectionType::Simple),
+ 2 => Some(SelectionType::Semantic),
+ 3 => Some(SelectionType::Lines),
+ _ => None,
+ };
+
+ let selection =
+ selection_type.map(|selection_type| Selection::new(selection_type, point, side));
+
+ term.selection = selection;
+ cx.focus_parent_view();
+ cx.notify();
+ })),
+ bounds: visible_bounds,
+ drag: Some(Rc::new(move |_delta, pos, cx| {
+ let mut term = drag_mutex.lock();
+
+ let (point, side) = mouse_to_cell_data(
+ pos,
+ origin,
+ cur_size,
+ term.renderable_content().display_offset,
+ );
+
+ if let Some(mut selection) = term.selection.take() {
+ selection.update(point, side);
+ term.selection = Some(selection);
+ }
+
+ cx.notify();
+ })),
+ ..Default::default()
+ });
}
-///Converts an 8 bit ANSI color to it's GPUI equivalent.
-pub fn get_color_at_index(index: &u8, style: &TerminalStyle) -> Color {
- match index {
- //0-15 are the same as the named colors above
- 0 => style.black,
- 1 => style.red,
- 2 => style.green,
- 3 => style.yellow,
- 4 => style.blue,
- 5 => style.magenta,
- 6 => style.cyan,
- 7 => style.white,
- 8 => style.bright_black,
- 9 => style.bright_red,
- 10 => style.bright_green,
- 11 => style.bright_yellow,
- 12 => style.bright_blue,
- 13 => style.bright_magenta,
- 14 => style.bright_cyan,
- 15 => style.bright_white,
- //16-231 are mapped to their RGB colors on a 0-5 range per channel
- 16..=231 => {
- let (r, g, b) = rgb_for_index(index); //Split the index into it's ANSI-RGB components
- let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
- Color::new(r * step, g * step, b * step, u8::MAX) //Map the ANSI-RGB components to an RGB color
- }
- //232-255 are a 24 step grayscale from black to white
- 232..=255 => {
- let i = index - 232; //Align index to 0..24
- let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
- Color::new(i * step, i * step, i * step, u8::MAX) //Map the ANSI-grayscale components to the RGB-grayscale
- }
+///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
+fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side {
+ let x = pos.0.x() as usize;
+ let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize;
+ let half_cell_width = (cur_size.cell_width() / 2.0) as usize;
+
+ let additional_padding =
+ (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width();
+ let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding;
+
+ if cell_x > half_cell_width
+ // Edge case when mouse leaves the window.
+ || x as f32 >= end_of_grid
+ {
+ Side::Right
+ } else {
+ Side::Left
}
}
-///Generates the rgb channels in [0, 5] for a given index into the 6x6x6 ANSI color cube
-///See: [8 bit ansi color](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit).
-///
-///Wikipedia gives a formula for calculating the index for a given color:
-///
-///index = 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
-///
-///This function does the reverse, calculating the r, g, and b components from a given index.
-fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
- debug_assert!(i >= &16 && i <= &231);
- let i = i - 16;
- let r = (i - (i % 36)) / 36;
- let g = ((i % 36) - (i % 6)) / 6;
- let b = (i % 36) % 6;
- (r, g, b)
+///Copied (with modifications) from alacritty/src/event.rs > Mouse::point()
+///Position is a pane-relative position. That means the top left corner of the mouse
+///Region should be (0,0)
+fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point {
+ let pos = pos.0;
+ let col = pos.x() / cur_size.cell_width(); //TODO: underflow...
+ let col = min(GridCol(col as usize), cur_size.last_column());
+
+ let line = pos.y() / cur_size.cell_height();
+ let line = min(line as i32, cur_size.bottommost_line().0);
+
+ //when clicking, need to ADD to get to the top left cell
+ //e.g. total_lines - viewport_height, THEN subtract display offset
+ //0 -> total_lines - viewport_height - display_offset + mouse_line
+
+ Point::new(GridLine(line - display_offset as i32), col)
}
///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between
@@ -555,14 +668,73 @@ fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContex
}
}
-#[cfg(test)]
-mod tests {
+mod test {
+
#[test]
- fn test_rgb_for_index() {
- //Test every possible value in the color cube
- for i in 16..=231 {
- let (r, g, b) = crate::terminal_element::rgb_for_index(&(i as u8));
- assert_eq!(i, 16 + 36 * r + 6 * g + b);
- }
+ fn test_mouse_to_selection() {
+ let term_width = 100.;
+ let term_height = 200.;
+ let cell_width = 10.;
+ let line_height = 20.;
+ let mouse_pos_x = 100.; //Window relative
+ let mouse_pos_y = 100.; //Window relative
+ let origin_x = 10.;
+ let origin_y = 20.;
+
+ let cur_size = alacritty_terminal::term::SizeInfo::new(
+ term_width,
+ term_height,
+ cell_width,
+ line_height,
+ 0.,
+ 0.,
+ false,
+ );
+
+ let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
+ let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
+ let (point, _) =
+ crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
+ assert_eq!(
+ point,
+ alacritty_terminal::index::Point::new(
+ alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
+ alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
+ )
+ );
+ }
+
+ #[test]
+ fn test_mouse_to_selection_off_edge() {
+ let term_width = 100.;
+ let term_height = 200.;
+ let cell_width = 10.;
+ let line_height = 20.;
+ let mouse_pos_x = 100.; //Window relative
+ let mouse_pos_y = 100.; //Window relative
+ let origin_x = 10.;
+ let origin_y = 20.;
+
+ let cur_size = alacritty_terminal::term::SizeInfo::new(
+ term_width,
+ term_height,
+ cell_width,
+ line_height,
+ 0.,
+ 0.,
+ false,
+ );
+
+ let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
+ let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
+ let (point, _) =
+ crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
+ assert_eq!(
+ point,
+ alacritty_terminal::index::Point::new(
+ alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
+ alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
+ )
+ );
}
}