Merge pull request #1299 from zed-industries/terminal-selections
Terminal selections first pass
This commit is contained in:
@@ -417,7 +417,8 @@
|
||||
"up": "terminal::Up",
|
||||
"down": "terminal::Down",
|
||||
"tab": "terminal::Tab",
|
||||
"cmd-v": "terminal::Paste"
|
||||
"cmd-v": "terminal::Paste",
|
||||
"cmd-c": "terminal::Copy"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -8232,7 +8232,7 @@ mod tests {
|
||||
fox ju|mps over
|
||||
the lazy dog"});
|
||||
cx.update_editor(|e, cx| e.copy(&Copy, cx));
|
||||
cx.assert_clipboard_content(Some("fox jumps over\n"));
|
||||
cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
|
||||
|
||||
// Paste with three selections, noticing how the copied full-line selection is inserted
|
||||
// before the empty selections but replaces the selection that is non-empty.
|
||||
|
||||
@@ -1695,22 +1695,22 @@ impl Cursor {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HighlightedRange {
|
||||
start_y: f32,
|
||||
line_height: f32,
|
||||
lines: Vec<HighlightedRangeLine>,
|
||||
color: Color,
|
||||
corner_radius: f32,
|
||||
pub struct HighlightedRange {
|
||||
pub start_y: f32,
|
||||
pub line_height: f32,
|
||||
pub lines: Vec<HighlightedRangeLine>,
|
||||
pub color: Color,
|
||||
pub corner_radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HighlightedRangeLine {
|
||||
start_x: f32,
|
||||
end_x: f32,
|
||||
pub struct HighlightedRangeLine {
|
||||
pub start_x: f32,
|
||||
pub end_x: f32,
|
||||
}
|
||||
|
||||
impl HighlightedRange {
|
||||
fn paint(&self, bounds: RectF, scene: &mut Scene) {
|
||||
pub fn paint(&self, bounds: RectF, scene: &mut Scene) {
|
||||
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
|
||||
self.paint_lines(
|
||||
|
||||
@@ -404,14 +404,6 @@ impl<'a> EditorTestContext<'a> {
|
||||
|
||||
editor_text_with_selections
|
||||
}
|
||||
|
||||
pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
|
||||
self.cx.update(|cx| {
|
||||
let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
|
||||
let expected_content = expected_content.map(|content| content.to_owned());
|
||||
assert_eq!(actual_content, expected_content);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EditorTestContext<'a> {
|
||||
|
||||
@@ -627,6 +627,14 @@ impl TestAppContext {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
|
||||
self.update(|cx| {
|
||||
let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
|
||||
let expected_content = expected_content.map(|content| content.to_owned());
|
||||
assert_eq!(actual_content, expected_content);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAppContext {
|
||||
|
||||
@@ -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();
|
||||
.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_style = cell_style(&indexed_cell, terminal_theme, text_style);
|
||||
let cell_text = &indexed_cell.c.to_string();
|
||||
|
||||
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>>()
|
||||
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()
|
||||
});
|
||||
}
|
||||
|
||||
///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
|
||||
}
|
||||
}
|
||||
|
||||
///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/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());
|
||||
|
||||
///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)
|
||||
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),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,14 +147,6 @@ impl<'a> VimTestContext<'a> {
|
||||
let mode = self.mode();
|
||||
VimBindingTestContext::new(keystrokes, mode, mode, self)
|
||||
}
|
||||
|
||||
pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
|
||||
self.cx.update(|cx| {
|
||||
let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
|
||||
let expected_content = expected_content.map(|content| content.to_owned());
|
||||
assert_eq!(actual_content, expected_content);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for VimTestContext<'a> {
|
||||
|
||||
Reference in New Issue
Block a user