Refactored a bunch of stuff, working on tidying element code
This commit is contained in:
parent
dce27870ce
commit
119207a9e5
10 changed files with 549 additions and 584 deletions
|
@ -1,5 +1,3 @@
|
||||||
pub mod terminal_layout_context;
|
|
||||||
|
|
||||||
use alacritty_terminal::{
|
use alacritty_terminal::{
|
||||||
ansi::{Color::Named, NamedColor},
|
ansi::{Color::Named, NamedColor},
|
||||||
event::WindowSize,
|
event::WindowSize,
|
||||||
|
@ -20,8 +18,7 @@ use gpui::{
|
||||||
json::json,
|
json::json,
|
||||||
text_layout::{Line, RunStyle},
|
text_layout::{Line, RunStyle},
|
||||||
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
|
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
|
||||||
PaintContext, Quad, ScrollWheelEvent, SizeConstraint, TextLayoutCache, WeakModelHandle,
|
PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||||
WeakViewHandle,
|
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
@ -32,34 +29,58 @@ use util::ResultExt;
|
||||||
use std::{cmp::min, ops::Range};
|
use std::{cmp::min, ops::Range};
|
||||||
use std::{fmt::Debug, ops::Sub};
|
use std::{fmt::Debug, ops::Sub};
|
||||||
|
|
||||||
use crate::{color_translation::convert_color, connection::Terminal, ConnectedView};
|
use crate::{mappings::colors::convert_color, model::Terminal, ConnectedView};
|
||||||
|
|
||||||
use self::terminal_layout_context::TerminalLayoutData;
|
|
||||||
|
|
||||||
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
///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
|
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
||||||
///Implement scroll bars.
|
///Implement scroll bars.
|
||||||
const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
|
const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
|
||||||
|
|
||||||
///The GPUI element that paints the terminal.
|
///The information generated during layout that is nescessary for painting
|
||||||
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
pub struct LayoutState {
|
||||||
pub struct TerminalEl {
|
cells: Vec<LayoutCell>,
|
||||||
terminal: WeakModelHandle<Terminal>,
|
rects: Vec<LayoutRect>,
|
||||||
view: WeakViewHandle<ConnectedView>,
|
highlights: Vec<RelativeHighlightedRange>,
|
||||||
modal: bool,
|
cursor: Option<Cursor>,
|
||||||
|
background_color: Color,
|
||||||
|
selection_color: Color,
|
||||||
|
size: TermDimensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
|
||||||
|
struct DisplayCursor {
|
||||||
|
line: i32,
|
||||||
|
col: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayCursor {
|
||||||
|
fn from(cursor_point: Point, display_offset: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
line: cursor_point.line.0 + display_offset as i32,
|
||||||
|
col: cursor_point.column.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&self) -> i32 {
|
||||||
|
self.line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col(&self) -> usize {
|
||||||
|
self.col
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TerminalDimensions {
|
pub struct TermDimensions {
|
||||||
pub cell_width: f32,
|
cell_width: f32,
|
||||||
pub line_height: f32,
|
line_height: f32,
|
||||||
pub height: f32,
|
height: f32,
|
||||||
pub width: f32,
|
width: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalDimensions {
|
impl TermDimensions {
|
||||||
pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
|
pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
|
||||||
TerminalDimensions {
|
TermDimensions {
|
||||||
cell_width,
|
cell_width,
|
||||||
line_height,
|
line_height,
|
||||||
width: size.x(),
|
width: size.x(),
|
||||||
|
@ -92,8 +113,7 @@ impl TerminalDimensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO look at what TermSize is
|
impl Into<WindowSize> for TermDimensions {
|
||||||
impl Into<WindowSize> for TerminalDimensions {
|
|
||||||
fn into(self) -> WindowSize {
|
fn into(self) -> WindowSize {
|
||||||
WindowSize {
|
WindowSize {
|
||||||
num_lines: self.num_lines() as u16,
|
num_lines: self.num_lines() as u16,
|
||||||
|
@ -104,9 +124,9 @@ impl Into<WindowSize> for TerminalDimensions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dimensions for TerminalDimensions {
|
impl Dimensions for TermDimensions {
|
||||||
fn total_lines(&self) -> usize {
|
fn total_lines(&self) -> usize {
|
||||||
self.num_lines() //TODO: Check that this is fine. This is supposed to be for the back buffer...
|
self.screen_lines() //TODO: Check that this is fine. This is supposed to be for the back buffer...
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen_lines(&self) -> usize {
|
fn screen_lines(&self) -> usize {
|
||||||
|
@ -214,15 +234,12 @@ impl RelativeHighlightedRange {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///The information generated during layout that is nescessary for painting
|
///The GPUI element that paints the terminal.
|
||||||
pub struct LayoutState {
|
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
||||||
cells: Vec<LayoutCell>,
|
pub struct TerminalEl {
|
||||||
rects: Vec<LayoutRect>,
|
terminal: WeakModelHandle<Terminal>,
|
||||||
highlights: Vec<RelativeHighlightedRange>,
|
view: WeakViewHandle<ConnectedView>,
|
||||||
cursor: Option<Cursor>,
|
modal: bool,
|
||||||
background_color: Color,
|
|
||||||
selection_color: Color,
|
|
||||||
size: TerminalDimensions,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalEl {
|
impl TerminalEl {
|
||||||
|
@ -238,12 +255,173 @@ impl TerminalEl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_grid(
|
||||||
|
grid: GridIterator<Cell>,
|
||||||
|
text_style: &TextStyle,
|
||||||
|
terminal_theme: &TerminalStyle,
|
||||||
|
text_layout_cache: &TextLayoutCache,
|
||||||
|
modal: bool,
|
||||||
|
selection_range: Option<SelectionRange>,
|
||||||
|
) -> (
|
||||||
|
Vec<LayoutCell>,
|
||||||
|
Vec<LayoutRect>,
|
||||||
|
Vec<RelativeHighlightedRange>,
|
||||||
|
) {
|
||||||
|
let mut cells = vec![];
|
||||||
|
let mut rects = vec![];
|
||||||
|
let mut highlight_ranges = vec![];
|
||||||
|
|
||||||
|
let mut cur_rect: Option<LayoutRect> = None;
|
||||||
|
let mut cur_alac_color = None;
|
||||||
|
let mut highlighted_range = None;
|
||||||
|
|
||||||
|
let linegroups = grid.group_by(|i| i.point.line);
|
||||||
|
for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
|
||||||
|
for (x_index, cell) in line.enumerate() {
|
||||||
|
//Increase selection range
|
||||||
|
{
|
||||||
|
if selection_range
|
||||||
|
.map(|range| range.contains(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Expand background rect range
|
||||||
|
{
|
||||||
|
if matches!(cell.bg, Named(NamedColor::Background)) {
|
||||||
|
//Continue to next cell, resetting variables if nescessary
|
||||||
|
cur_alac_color = None;
|
||||||
|
if let Some(rect) = cur_rect {
|
||||||
|
rects.push(rect);
|
||||||
|
cur_rect = None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match cur_alac_color {
|
||||||
|
Some(cur_color) => {
|
||||||
|
if cell.bg == cur_color {
|
||||||
|
cur_rect = cur_rect.take().map(|rect| rect.extend());
|
||||||
|
} else {
|
||||||
|
cur_alac_color = Some(cell.bg);
|
||||||
|
if let Some(_) = cur_rect {
|
||||||
|
rects.push(cur_rect.take().unwrap());
|
||||||
|
}
|
||||||
|
cur_rect = Some(LayoutRect::new(
|
||||||
|
Point::new(line_index as i32, cell.point.column.0 as i32),
|
||||||
|
1,
|
||||||
|
convert_color(&cell.bg, &terminal_theme.colors, modal),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
cur_alac_color = Some(cell.bg);
|
||||||
|
cur_rect = Some(LayoutRect::new(
|
||||||
|
Point::new(line_index as i32, cell.point.column.0 as i32),
|
||||||
|
1,
|
||||||
|
convert_color(&cell.bg, &terminal_theme.colors, modal),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Layout current cell text
|
||||||
|
{
|
||||||
|
let cell_text = &cell.c.to_string();
|
||||||
|
if cell_text != " " {
|
||||||
|
let cell_style =
|
||||||
|
TerminalEl::cell_style(&cell, terminal_theme, text_style, modal);
|
||||||
|
|
||||||
|
let layout_cell = text_layout_cache.layout_str(
|
||||||
|
cell_text,
|
||||||
|
text_style.font_size,
|
||||||
|
&[(cell_text.len(), cell_style)],
|
||||||
|
);
|
||||||
|
|
||||||
|
cells.push(LayoutCell::new(
|
||||||
|
Point::new(line_index as i32, cell.point.column.0 as i32),
|
||||||
|
layout_cell,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if highlighted_range.is_some() {
|
||||||
|
highlight_ranges.push(RelativeHighlightedRange::new(
|
||||||
|
line_index,
|
||||||
|
highlighted_range.take().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cur_rect.is_some() {
|
||||||
|
rects.push(cur_rect.take().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(cells, rects, highlight_ranges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
|
||||||
|
// the same position for sequential indexes. Use em_width instead
|
||||||
|
fn shape_cursor(
|
||||||
|
cursor_point: DisplayCursor,
|
||||||
|
size: TermDimensions,
|
||||||
|
text_fragment: &Line,
|
||||||
|
) -> Option<(Vector2F, f32)> {
|
||||||
|
if cursor_point.line() < size.total_lines() as i32 {
|
||||||
|
let cursor_width = if text_fragment.width() == 0. {
|
||||||
|
size.cell_width()
|
||||||
|
} else {
|
||||||
|
text_fragment.width()
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((
|
||||||
|
vec2f(
|
||||||
|
cursor_point.col() as f32 * size.cell_width(),
|
||||||
|
cursor_point.line() as f32 * size.line_height(),
|
||||||
|
),
|
||||||
|
cursor_width,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Convert the Alacritty cell styles to GPUI text styles and background color
|
||||||
|
fn cell_style(
|
||||||
|
indexed: &Indexed<&Cell>,
|
||||||
|
style: &TerminalStyle,
|
||||||
|
text_style: &TextStyle,
|
||||||
|
modal: bool,
|
||||||
|
) -> RunStyle {
|
||||||
|
let flags = indexed.cell.flags;
|
||||||
|
let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
|
||||||
|
|
||||||
|
let underline = flags
|
||||||
|
.contains(Flags::UNDERLINE)
|
||||||
|
.then(|| Underline {
|
||||||
|
color: Some(fg),
|
||||||
|
squiggly: false,
|
||||||
|
thickness: OrderedFloat(1.),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
RunStyle {
|
||||||
|
color: fg,
|
||||||
|
font_id: text_style.font_id,
|
||||||
|
underline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn attach_mouse_handlers(
|
fn attach_mouse_handlers(
|
||||||
&self,
|
&self,
|
||||||
origin: Vector2F,
|
origin: Vector2F,
|
||||||
view_id: usize,
|
view_id: usize,
|
||||||
visible_bounds: RectF,
|
visible_bounds: RectF,
|
||||||
cur_size: TerminalDimensions,
|
cur_size: TermDimensions,
|
||||||
cx: &mut PaintContext,
|
cx: &mut PaintContext,
|
||||||
) {
|
) {
|
||||||
let mouse_down_connection = self.terminal.clone();
|
let mouse_down_connection = self.terminal.clone();
|
||||||
|
@ -256,7 +434,7 @@ impl TerminalEl {
|
||||||
move |MouseButtonEvent { position, .. }, cx| {
|
move |MouseButtonEvent { position, .. }, cx| {
|
||||||
if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) {
|
if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) {
|
||||||
conn_handle.update(cx.app, |terminal, cx| {
|
conn_handle.update(cx.app, |terminal, cx| {
|
||||||
let (point, side) = mouse_to_cell_data(
|
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||||
position,
|
position,
|
||||||
origin,
|
origin,
|
||||||
cur_size,
|
cur_size,
|
||||||
|
@ -281,7 +459,7 @@ impl TerminalEl {
|
||||||
cx.focus_parent_view();
|
cx.focus_parent_view();
|
||||||
if let Some(conn_handle) = click_connection.upgrade(cx.app) {
|
if let Some(conn_handle) = click_connection.upgrade(cx.app) {
|
||||||
conn_handle.update(cx.app, |terminal, cx| {
|
conn_handle.update(cx.app, |terminal, cx| {
|
||||||
let (point, side) = mouse_to_cell_data(
|
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||||
position,
|
position,
|
||||||
origin,
|
origin,
|
||||||
cur_size,
|
cur_size,
|
||||||
|
@ -300,7 +478,7 @@ impl TerminalEl {
|
||||||
move |_, MouseMovedEvent { position, .. }, cx| {
|
move |_, MouseMovedEvent { position, .. }, cx| {
|
||||||
if let Some(conn_handle) = drag_connection.upgrade(cx.app) {
|
if let Some(conn_handle) = drag_connection.upgrade(cx.app) {
|
||||||
conn_handle.update(cx.app, |terminal, cx| {
|
conn_handle.update(cx.app, |terminal, cx| {
|
||||||
let (point, side) = mouse_to_cell_data(
|
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||||
position,
|
position,
|
||||||
origin,
|
origin,
|
||||||
cur_size,
|
cur_size,
|
||||||
|
@ -316,6 +494,79 @@ impl TerminalEl {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///Configures a text style from the current settings.
|
||||||
|
pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
|
||||||
|
// Pull the font family from settings properly overriding
|
||||||
|
let family_id = settings
|
||||||
|
.terminal_overrides
|
||||||
|
.font_family
|
||||||
|
.as_ref()
|
||||||
|
.or_else(|| settings.terminal_defaults.font_family.as_ref())
|
||||||
|
.and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
|
||||||
|
.unwrap_or(settings.buffer_font_family);
|
||||||
|
|
||||||
|
let font_size = settings
|
||||||
|
.terminal_overrides
|
||||||
|
.font_size
|
||||||
|
.or(settings.terminal_defaults.font_size)
|
||||||
|
.unwrap_or(settings.buffer_font_size);
|
||||||
|
|
||||||
|
let font_id = font_cache
|
||||||
|
.select_font(family_id, &Default::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
TextStyle {
|
||||||
|
color: settings.theme.editor.text_color,
|
||||||
|
font_family_id: family_id,
|
||||||
|
font_family_name: font_cache.family_name(family_id).unwrap(),
|
||||||
|
font_id,
|
||||||
|
font_size,
|
||||||
|
font_properties: Default::default(),
|
||||||
|
underline: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_to_cell_data(
|
||||||
|
pos: Vector2F,
|
||||||
|
origin: Vector2F,
|
||||||
|
cur_size: TermDimensions,
|
||||||
|
display_offset: usize,
|
||||||
|
) -> (Point, alacritty_terminal::index::Direction) {
|
||||||
|
let pos = pos.sub(origin);
|
||||||
|
let point = {
|
||||||
|
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.line_height;
|
||||||
|
let line = min(line as i32, cur_size.bottommost_line().0);
|
||||||
|
|
||||||
|
Point::new(GridLine(line - display_offset as i32), col)
|
||||||
|
};
|
||||||
|
|
||||||
|
//Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
|
||||||
|
let 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;
|
||||||
|
//Width: Pixels or columns?
|
||||||
|
if cell_x > half_cell_width
|
||||||
|
// Edge case when mouse leaves the window.
|
||||||
|
|| x as f32 >= end_of_grid
|
||||||
|
{
|
||||||
|
Side::Right
|
||||||
|
} else {
|
||||||
|
Side::Left
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(point, side)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Element for TerminalEl {
|
impl Element for TerminalEl {
|
||||||
|
@ -327,40 +578,74 @@ impl Element for TerminalEl {
|
||||||
constraint: gpui::SizeConstraint,
|
constraint: gpui::SizeConstraint,
|
||||||
cx: &mut gpui::LayoutContext,
|
cx: &mut gpui::LayoutContext,
|
||||||
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
|
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
|
||||||
let layout =
|
let settings = cx.global::<Settings>();
|
||||||
TerminalLayoutData::new(cx.global::<Settings>(), &cx.font_cache(), constraint.max);
|
let font_cache = &cx.font_cache();
|
||||||
|
|
||||||
|
//Setup layout information
|
||||||
|
let terminal_theme = &settings.theme.terminal;
|
||||||
|
let text_style = TerminalEl::make_text_style(font_cache, &settings);
|
||||||
|
let selection_color = settings.theme.editor.selection.selection;
|
||||||
|
let dimensions = {
|
||||||
|
let line_height = font_cache.line_height(text_style.font_size);
|
||||||
|
let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
|
||||||
|
TermDimensions::new(line_height, cell_width, constraint.max)
|
||||||
|
};
|
||||||
|
|
||||||
let terminal = self.terminal.upgrade(cx).unwrap().read(cx);
|
let terminal = self.terminal.upgrade(cx).unwrap().read(cx);
|
||||||
|
|
||||||
let (cursor, cells, rects, highlights) =
|
let (cursor, cells, rects, highlights) =
|
||||||
terminal.render_lock(Some(layout.size.clone()), |content, cursor_text| {
|
terminal.render_lock(Some(dimensions.clone()), |content, cursor_text| {
|
||||||
let (cells, rects, highlights) = layout_grid(
|
let (cells, rects, highlights) = TerminalEl::layout_grid(
|
||||||
content.display_iter,
|
content.display_iter,
|
||||||
&layout.text_style,
|
&text_style,
|
||||||
layout.terminal_theme,
|
terminal_theme,
|
||||||
cx.text_layout_cache,
|
cx.text_layout_cache,
|
||||||
self.modal,
|
self.modal,
|
||||||
content.selection,
|
content.selection,
|
||||||
);
|
);
|
||||||
|
|
||||||
//Layout cursor
|
//Layout cursor
|
||||||
let cursor = layout_cursor(
|
let cursor = {
|
||||||
cursor_text,
|
let cursor_point =
|
||||||
cx.text_layout_cache,
|
DisplayCursor::from(content.cursor.point, content.display_offset);
|
||||||
&layout,
|
let cursor_text = {
|
||||||
content.cursor.point,
|
let str_trxt = cursor_text.to_string();
|
||||||
content.display_offset,
|
cx.text_layout_cache.layout_str(
|
||||||
constraint,
|
&str_trxt,
|
||||||
);
|
text_style.font_size,
|
||||||
|
&[(
|
||||||
|
str_trxt.len(),
|
||||||
|
RunStyle {
|
||||||
|
font_id: text_style.font_id,
|
||||||
|
color: terminal_theme.colors.background,
|
||||||
|
underline: Default::default(),
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map(
|
||||||
|
move |(cursor_position, block_width)| {
|
||||||
|
Cursor::new(
|
||||||
|
cursor_position,
|
||||||
|
block_width,
|
||||||
|
dimensions.line_height,
|
||||||
|
terminal_theme.colors.cursor,
|
||||||
|
CursorShape::Block,
|
||||||
|
Some(cursor_text.clone()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
(cursor, cells, rects, highlights)
|
(cursor, cells, rects, highlights)
|
||||||
});
|
});
|
||||||
|
|
||||||
//Select background color
|
//Select background color
|
||||||
let background_color = if self.modal {
|
let background_color = if self.modal {
|
||||||
layout.terminal_theme.colors.modal_background
|
terminal_theme.colors.modal_background
|
||||||
} else {
|
} else {
|
||||||
layout.terminal_theme.colors.background
|
terminal_theme.colors.background
|
||||||
};
|
};
|
||||||
|
|
||||||
//Done!
|
//Done!
|
||||||
|
@ -370,8 +655,8 @@ impl Element for TerminalEl {
|
||||||
cells,
|
cells,
|
||||||
cursor,
|
cursor,
|
||||||
background_color,
|
background_color,
|
||||||
selection_color: layout.selection_color,
|
selection_color,
|
||||||
size: layout.size,
|
size: dimensions,
|
||||||
rects,
|
rects,
|
||||||
highlights,
|
highlights,
|
||||||
},
|
},
|
||||||
|
@ -385,13 +670,6 @@ impl Element for TerminalEl {
|
||||||
layout: &mut Self::LayoutState,
|
layout: &mut Self::LayoutState,
|
||||||
cx: &mut gpui::PaintContext,
|
cx: &mut gpui::PaintContext,
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
/*
|
|
||||||
* For paint, I want to change how mouse events are handled:
|
|
||||||
* - Refactor the mouse handlers to push the grid cell actions into the connection
|
|
||||||
* - But keep the conversion from GPUI coordinates to grid cells in the Terminal element
|
|
||||||
* - Switch from directly painting things, to calling 'paint' on items produced by layout
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Setup element stuff
|
//Setup element stuff
|
||||||
let clip_bounds = Some(visible_bounds);
|
let clip_bounds = Some(visible_bounds);
|
||||||
|
|
||||||
|
@ -520,270 +798,6 @@ impl Element for TerminalEl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///TODO: Fix cursor rendering with alacritty fork
|
|
||||||
fn layout_cursor(
|
|
||||||
cursor_text: char,
|
|
||||||
text_layout_cache: &TextLayoutCache,
|
|
||||||
tcx: &TerminalLayoutData,
|
|
||||||
cursor_point: Point,
|
|
||||||
display_offset: usize,
|
|
||||||
constraint: SizeConstraint,
|
|
||||||
) -> Option<Cursor> {
|
|
||||||
let cursor_text = layout_cursor_text(cursor_text, cursor_point, text_layout_cache, tcx);
|
|
||||||
get_cursor_shape(
|
|
||||||
cursor_point.line.0 as usize,
|
|
||||||
cursor_point.column.0 as usize,
|
|
||||||
display_offset,
|
|
||||||
tcx.size.line_height,
|
|
||||||
tcx.size.cell_width,
|
|
||||||
(constraint.max.y() / tcx.size.line_height) as usize, //TODO
|
|
||||||
&cursor_text,
|
|
||||||
)
|
|
||||||
.map(move |(cursor_position, block_width)| {
|
|
||||||
let block_width = if block_width != 0.0 {
|
|
||||||
block_width
|
|
||||||
} else {
|
|
||||||
tcx.size.cell_width
|
|
||||||
};
|
|
||||||
|
|
||||||
Cursor::new(
|
|
||||||
cursor_position,
|
|
||||||
block_width,
|
|
||||||
tcx.size.line_height,
|
|
||||||
tcx.terminal_theme.colors.cursor,
|
|
||||||
CursorShape::Block,
|
|
||||||
Some(cursor_text.clone()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout_cursor_text(
|
|
||||||
cursor_text: char,
|
|
||||||
_cursor_point: Point,
|
|
||||||
text_layout_cache: &TextLayoutCache,
|
|
||||||
tcx: &TerminalLayoutData,
|
|
||||||
) -> Line {
|
|
||||||
let cursor_text = cursor_text.to_string();
|
|
||||||
text_layout_cache.layout_str(
|
|
||||||
&cursor_text,
|
|
||||||
tcx.text_style.font_size,
|
|
||||||
&[(
|
|
||||||
cursor_text.len(),
|
|
||||||
RunStyle {
|
|
||||||
font_id: tcx.text_style.font_id,
|
|
||||||
color: tcx.terminal_theme.colors.background,
|
|
||||||
underline: Default::default(),
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mouse_to_cell_data(
|
|
||||||
pos: Vector2F,
|
|
||||||
origin: Vector2F,
|
|
||||||
cur_size: TerminalDimensions,
|
|
||||||
display_offset: usize,
|
|
||||||
) -> (Point, alacritty_terminal::index::Direction) {
|
|
||||||
let pos = pos.sub(origin);
|
|
||||||
let point = {
|
|
||||||
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.line_height;
|
|
||||||
let line = min(line as i32, cur_size.bottommost_line().0);
|
|
||||||
|
|
||||||
Point::new(GridLine(line - display_offset as i32), col)
|
|
||||||
};
|
|
||||||
|
|
||||||
//Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
|
|
||||||
let 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;
|
|
||||||
//Width: Pixels or columns?
|
|
||||||
if cell_x > half_cell_width
|
|
||||||
// Edge case when mouse leaves the window.
|
|
||||||
|| x as f32 >= end_of_grid
|
|
||||||
{
|
|
||||||
Side::Right
|
|
||||||
} else {
|
|
||||||
Side::Left
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(point, side)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout_grid(
|
|
||||||
grid: GridIterator<Cell>,
|
|
||||||
text_style: &TextStyle,
|
|
||||||
terminal_theme: &TerminalStyle,
|
|
||||||
text_layout_cache: &TextLayoutCache,
|
|
||||||
modal: bool,
|
|
||||||
selection_range: Option<SelectionRange>,
|
|
||||||
) -> (
|
|
||||||
Vec<LayoutCell>,
|
|
||||||
Vec<LayoutRect>,
|
|
||||||
Vec<RelativeHighlightedRange>,
|
|
||||||
) {
|
|
||||||
let mut cells = vec![];
|
|
||||||
let mut rects = vec![];
|
|
||||||
let mut highlight_ranges = vec![];
|
|
||||||
|
|
||||||
let mut cur_rect: Option<LayoutRect> = None;
|
|
||||||
let mut cur_alac_color = None;
|
|
||||||
let mut highlighted_range = None;
|
|
||||||
|
|
||||||
let linegroups = grid.group_by(|i| i.point.line);
|
|
||||||
for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
|
|
||||||
for (x_index, cell) in line.enumerate() {
|
|
||||||
//Increase selection range
|
|
||||||
{
|
|
||||||
if selection_range
|
|
||||||
.map(|range| range.contains(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Expand background rect range
|
|
||||||
{
|
|
||||||
if matches!(cell.bg, Named(NamedColor::Background)) {
|
|
||||||
//Continue to next cell, resetting variables if nescessary
|
|
||||||
cur_alac_color = None;
|
|
||||||
if let Some(rect) = cur_rect {
|
|
||||||
rects.push(rect);
|
|
||||||
cur_rect = None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match cur_alac_color {
|
|
||||||
Some(cur_color) => {
|
|
||||||
if cell.bg == cur_color {
|
|
||||||
cur_rect = cur_rect.take().map(|rect| rect.extend());
|
|
||||||
} else {
|
|
||||||
cur_alac_color = Some(cell.bg);
|
|
||||||
if let Some(_) = cur_rect {
|
|
||||||
rects.push(cur_rect.take().unwrap());
|
|
||||||
}
|
|
||||||
cur_rect = Some(LayoutRect::new(
|
|
||||||
Point::new(line_index as i32, cell.point.column.0 as i32),
|
|
||||||
1,
|
|
||||||
convert_color(&cell.bg, &terminal_theme.colors, modal),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
cur_alac_color = Some(cell.bg);
|
|
||||||
cur_rect = Some(LayoutRect::new(
|
|
||||||
Point::new(line_index as i32, cell.point.column.0 as i32),
|
|
||||||
1,
|
|
||||||
convert_color(&cell.bg, &terminal_theme.colors, modal),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Layout current cell text
|
|
||||||
{
|
|
||||||
let cell_text = &cell.c.to_string();
|
|
||||||
if cell_text != " " {
|
|
||||||
let cell_style = cell_style(&cell, terminal_theme, text_style, modal);
|
|
||||||
|
|
||||||
let layout_cell = text_layout_cache.layout_str(
|
|
||||||
cell_text,
|
|
||||||
text_style.font_size,
|
|
||||||
&[(cell_text.len(), cell_style)],
|
|
||||||
);
|
|
||||||
|
|
||||||
cells.push(LayoutCell::new(
|
|
||||||
Point::new(line_index as i32, cell.point.column.0 as i32),
|
|
||||||
layout_cell,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if highlighted_range.is_some() {
|
|
||||||
highlight_ranges.push(RelativeHighlightedRange::new(
|
|
||||||
line_index,
|
|
||||||
highlighted_range.take().unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cur_rect.is_some() {
|
|
||||||
rects.push(cur_rect.take().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(cells, rects, highlight_ranges)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
|
|
||||||
// the same position for sequential indexes. Use em_width instead
|
|
||||||
//TODO: This function is messy, too many arguments and too many ifs. Simplify.
|
|
||||||
fn get_cursor_shape(
|
|
||||||
line: usize,
|
|
||||||
line_index: usize,
|
|
||||||
display_offset: usize,
|
|
||||||
line_height: f32,
|
|
||||||
cell_width: f32,
|
|
||||||
total_lines: usize,
|
|
||||||
text_fragment: &Line,
|
|
||||||
) -> Option<(Vector2F, f32)> {
|
|
||||||
let cursor_line = line + display_offset;
|
|
||||||
if cursor_line <= total_lines {
|
|
||||||
let cursor_width = if text_fragment.width() == 0. {
|
|
||||||
cell_width
|
|
||||||
} else {
|
|
||||||
text_fragment.width()
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((
|
|
||||||
vec2f(
|
|
||||||
line_index as f32 * cell_width,
|
|
||||||
cursor_line as f32 * line_height,
|
|
||||||
),
|
|
||||||
cursor_width,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///Convert the Alacritty cell styles to GPUI text styles and background color
|
|
||||||
fn cell_style(
|
|
||||||
indexed: &Indexed<&Cell>,
|
|
||||||
style: &TerminalStyle,
|
|
||||||
text_style: &TextStyle,
|
|
||||||
modal: bool,
|
|
||||||
) -> RunStyle {
|
|
||||||
let flags = indexed.cell.flags;
|
|
||||||
let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
|
|
||||||
|
|
||||||
let underline = flags
|
|
||||||
.contains(Flags::UNDERLINE)
|
|
||||||
.then(|| Underline {
|
|
||||||
color: Some(fg),
|
|
||||||
squiggly: false,
|
|
||||||
thickness: OrderedFloat(1.),
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
RunStyle {
|
|
||||||
color: fg,
|
|
||||||
font_id: text_style.font_id,
|
|
||||||
underline,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -797,7 +811,7 @@ mod test {
|
||||||
let origin_x = 10.;
|
let origin_x = 10.;
|
||||||
let origin_y = 20.;
|
let origin_y = 20.;
|
||||||
|
|
||||||
let cur_size = crate::terminal_element::TerminalDimensions::new(
|
let cur_size = crate::connected_el::TermDimensions::new(
|
||||||
line_height,
|
line_height,
|
||||||
cell_width,
|
cell_width,
|
||||||
gpui::geometry::vector::vec2f(term_width, term_height),
|
gpui::geometry::vector::vec2f(term_width, term_height),
|
||||||
|
@ -806,7 +820,7 @@ mod test {
|
||||||
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
|
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 origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
|
||||||
let (point, _) =
|
let (point, _) =
|
||||||
crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
|
crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
point,
|
point,
|
||||||
alacritty_terminal::index::Point::new(
|
alacritty_terminal::index::Point::new(
|
162
crates/terminal/src/connected_view.rs
Normal file
162
crates/terminal/src/connected_view.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use gpui::{
|
||||||
|
actions, keymap::Keystroke, ClipboardItem, Element, ElementBox, ModelHandle, MutableAppContext,
|
||||||
|
View, ViewContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
connected_el::TerminalEl,
|
||||||
|
model::{Event, Terminal},
|
||||||
|
};
|
||||||
|
|
||||||
|
///Event to transmit the scroll from the element to the view
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct ScrollTerminal(pub i32);
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
terminal,
|
||||||
|
[Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,]
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
//Global binding overrrides
|
||||||
|
cx.add_action(ConnectedView::ctrl_c);
|
||||||
|
cx.add_action(ConnectedView::up);
|
||||||
|
cx.add_action(ConnectedView::down);
|
||||||
|
cx.add_action(ConnectedView::escape);
|
||||||
|
cx.add_action(ConnectedView::enter);
|
||||||
|
//Useful terminal views
|
||||||
|
cx.add_action(ConnectedView::copy);
|
||||||
|
cx.add_action(ConnectedView::paste);
|
||||||
|
cx.add_action(ConnectedView::clear);
|
||||||
|
}
|
||||||
|
|
||||||
|
///A terminal view, maintains the PTY's file handles and communicates with the terminal
|
||||||
|
pub struct ConnectedView {
|
||||||
|
terminal: ModelHandle<Terminal>,
|
||||||
|
has_new_content: bool,
|
||||||
|
//Currently using iTerm bell, show bell emoji in tab until input is received
|
||||||
|
has_bell: bool,
|
||||||
|
// Only for styling purposes. Doesn't effect behavior
|
||||||
|
modal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectedView {
|
||||||
|
pub fn from_terminal(
|
||||||
|
terminal: ModelHandle<Terminal>,
|
||||||
|
modal: bool,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
|
||||||
|
cx.subscribe(&terminal, |this, _, event, cx| match event {
|
||||||
|
Event::Wakeup => {
|
||||||
|
if cx.is_self_focused() {
|
||||||
|
cx.notify()
|
||||||
|
} else {
|
||||||
|
this.has_new_content = true;
|
||||||
|
cx.emit(Event::TitleChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Bell => {
|
||||||
|
this.has_bell = true;
|
||||||
|
cx.emit(Event::TitleChanged);
|
||||||
|
}
|
||||||
|
_ => cx.emit(*event),
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
terminal,
|
||||||
|
has_new_content: true,
|
||||||
|
has_bell: false,
|
||||||
|
modal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> ModelHandle<Terminal> {
|
||||||
|
self.terminal.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_new_content(&self) -> bool {
|
||||||
|
self.has_new_content
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_bell(&self) -> bool {
|
||||||
|
self.has_bell
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_bel(&mut self, cx: &mut ViewContext<ConnectedView>) {
|
||||||
|
self.has_bell = false;
|
||||||
|
cx.emit(Event::TitleChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal.read(cx).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
///Attempt to paste the clipboard into the terminal
|
||||||
|
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.copy()
|
||||||
|
.map(|text| cx.write_to_clipboard(ClipboardItem::new(text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
///Attempt to paste the clipboard into the terminal
|
||||||
|
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.read_from_clipboard().map(|item| {
|
||||||
|
self.terminal.read(cx).paste(item.text());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///Synthesize the keyboard event corresponding to 'up'
|
||||||
|
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.try_keystroke(&Keystroke::parse("up").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
///Synthesize the keyboard event corresponding to 'down'
|
||||||
|
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.try_keystroke(&Keystroke::parse("down").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
///Synthesize the keyboard event corresponding to 'ctrl-c'
|
||||||
|
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
///Synthesize the keyboard event corresponding to 'escape'
|
||||||
|
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.try_keystroke(&Keystroke::parse("escape").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
///Synthesize the keyboard event corresponding to 'enter'
|
||||||
|
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||||
|
self.terminal
|
||||||
|
.read(cx)
|
||||||
|
.try_keystroke(&Keystroke::parse("enter").unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ConnectedView {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"Connected Terminal View"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
||||||
|
let terminal_handle = self.terminal.clone().downgrade();
|
||||||
|
TerminalEl::new(cx.handle(), terminal_handle, self.modal)
|
||||||
|
.contained()
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
|
||||||
|
self.has_new_content = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -133,7 +133,7 @@ mod tests {
|
||||||
fn test_rgb_for_index() {
|
fn test_rgb_for_index() {
|
||||||
//Test every possible value in the color cube
|
//Test every possible value in the color cube
|
||||||
for i in 16..=231 {
|
for i in 16..=231 {
|
||||||
let (r, g, b) = crate::color_translation::rgb_for_index(&(i as u8));
|
let (r, g, b) = crate::mappings::colors::rgb_for_index(&(i as u8));
|
||||||
assert_eq!(i, 16 + 36 * r + 6 * g + b);
|
assert_eq!(i, 16 + 36 * r + 6 * g + b);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,6 @@
|
||||||
use alacritty_terminal::term::TermMode;
|
use alacritty_terminal::term::TermMode;
|
||||||
use gpui::keymap::Keystroke;
|
use gpui::keymap::Keystroke;
|
||||||
|
|
||||||
/*
|
|
||||||
Connection events still to do:
|
|
||||||
- Reporting mouse events correctly.
|
|
||||||
- Reporting scrolls
|
|
||||||
- Correctly bracketing a paste
|
|
||||||
- Storing changed colors
|
|
||||||
- Focus change sequence
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Modifiers {
|
pub enum Modifiers {
|
||||||
None,
|
None,
|
2
crates/terminal/src/mappings/mod.rs
Normal file
2
crates/terminal/src/mappings/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod colors;
|
||||||
|
pub mod keys;
|
|
@ -2,7 +2,7 @@ use gpui::{ModelHandle, ViewContext};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connection::Terminal, get_working_directory, DeployModal, Event, TerminalContent, TerminalView,
|
get_working_directory, model::Terminal, DeployModal, Event, TerminalContent, TerminalView,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -32,7 +32,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
|
||||||
let this = cx.add_view(|cx| TerminalView::new(working_directory, true, cx));
|
let this = cx.add_view(|cx| TerminalView::new(working_directory, true, cx));
|
||||||
|
|
||||||
if let TerminalContent::Connected(connected) = &this.read(cx).content {
|
if let TerminalContent::Connected(connected) = &this.read(cx).content {
|
||||||
let terminal_handle = connected.read(cx).terminal.clone();
|
let terminal_handle = connected.read(cx).handle();
|
||||||
cx.subscribe(&terminal_handle, on_event).detach();
|
cx.subscribe(&terminal_handle, on_event).detach();
|
||||||
// Set the global immediately if terminal construction was successful,
|
// Set the global immediately if terminal construction was successful,
|
||||||
// in case the user opens the command palette
|
// in case the user opens the command palette
|
||||||
|
@ -46,7 +46,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon
|
||||||
// Terminal modal was dismissed. Store terminal if the terminal view is connected
|
// Terminal modal was dismissed. Store terminal if the terminal view is connected
|
||||||
if let TerminalContent::Connected(connected) = &closed_terminal_handle.read(cx).content
|
if let TerminalContent::Connected(connected) = &closed_terminal_handle.read(cx).content
|
||||||
{
|
{
|
||||||
let terminal_handle = connected.read(cx).terminal.clone();
|
let terminal_handle = connected.read(cx).handle();
|
||||||
// Set the global immediately if terminal construction was successful,
|
// Set the global immediately if terminal construction was successful,
|
||||||
// in case the user opens the command palette
|
// in case the user opens the command palette
|
||||||
cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(
|
cx.set_global::<Option<StoredTerminal>>(Some(StoredTerminal(
|
|
@ -1,5 +1,3 @@
|
||||||
mod keymappings;
|
|
||||||
|
|
||||||
use alacritty_terminal::{
|
use alacritty_terminal::{
|
||||||
ansi::{ClearMode, Handler},
|
ansi::{ClearMode, Handler},
|
||||||
config::{Config, Program, PtyConfig},
|
config::{Config, Program, PtyConfig},
|
||||||
|
@ -25,12 +23,13 @@ use thiserror::Error;
|
||||||
use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext};
|
use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color_translation::{get_color_at_index, to_alac_rgb},
|
connected_el::TermDimensions,
|
||||||
terminal_element::TerminalDimensions,
|
mappings::{
|
||||||
|
colors::{get_color_at_index, to_alac_rgb},
|
||||||
|
keys::to_esc_str,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::keymappings::to_esc_str;
|
|
||||||
|
|
||||||
const DEFAULT_TITLE: &str = "Terminal";
|
const DEFAULT_TITLE: &str = "Terminal";
|
||||||
|
|
||||||
///Upward flowing events, for changing the title and such
|
///Upward flowing events, for changing the title and such
|
||||||
|
@ -132,7 +131,7 @@ impl TerminalBuilder {
|
||||||
working_directory: Option<PathBuf>,
|
working_directory: Option<PathBuf>,
|
||||||
shell: Option<Shell>,
|
shell: Option<Shell>,
|
||||||
env: Option<HashMap<String, String>>,
|
env: Option<HashMap<String, String>>,
|
||||||
initial_size: TerminalDimensions,
|
initial_size: TermDimensions,
|
||||||
) -> Result<TerminalBuilder> {
|
) -> Result<TerminalBuilder> {
|
||||||
let pty_config = {
|
let pty_config = {
|
||||||
let alac_shell = shell.clone().and_then(|shell| match shell {
|
let alac_shell = shell.clone().and_then(|shell| match shell {
|
||||||
|
@ -364,7 +363,7 @@ impl Terminal {
|
||||||
self.term.lock().selection = sel;
|
self.term.lock().selection = sel;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_lock<F, T>(&self, new_size: Option<TerminalDimensions>, f: F) -> T
|
pub fn render_lock<F, T>(&self, new_size: Option<TermDimensions>, f: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(RenderableContent, char) -> T,
|
F: FnOnce(RenderableContent, char) -> T,
|
||||||
{
|
{
|
|
@ -1,64 +1,40 @@
|
||||||
mod color_translation;
|
pub mod connected_el;
|
||||||
pub mod connection;
|
pub mod connected_view;
|
||||||
mod modal;
|
pub mod mappings;
|
||||||
pub mod terminal_element;
|
pub mod modal_view;
|
||||||
|
pub mod model;
|
||||||
|
|
||||||
use connection::{Event, Terminal, TerminalBuilder, TerminalError};
|
use connected_view::ConnectedView;
|
||||||
use dirs::home_dir;
|
use dirs::home_dir;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, geometry::vector::vec2f, keymap::Keystroke, AnyViewHandle, AppContext,
|
actions, elements::*, geometry::vector::vec2f, AnyViewHandle, AppContext, Entity, ModelHandle,
|
||||||
ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
MutableAppContext, View, ViewContext, ViewHandle,
|
||||||
};
|
};
|
||||||
use modal::deploy_modal;
|
use modal_view::deploy_modal;
|
||||||
|
use model::{Event, Terminal, TerminalBuilder, TerminalError};
|
||||||
|
|
||||||
|
use connected_el::TermDimensions;
|
||||||
use project::{LocalWorktree, Project, ProjectPath};
|
use project::{LocalWorktree, Project, ProjectPath};
|
||||||
use settings::{Settings, WorkingDirectory};
|
use settings::{Settings, WorkingDirectory};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use terminal_element::{terminal_layout_context::TerminalLayoutData, TerminalDimensions};
|
|
||||||
use workspace::{Item, Workspace};
|
use workspace::{Item, Workspace};
|
||||||
|
|
||||||
use crate::terminal_element::TerminalEl;
|
use crate::connected_el::TerminalEl;
|
||||||
|
|
||||||
const DEBUG_TERMINAL_WIDTH: f32 = 1000.; //This needs to be wide enough that the prompt can fill the whole space.
|
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_TERMINAL_HEIGHT: f32 = 200.;
|
||||||
const DEBUG_CELL_WIDTH: f32 = 5.;
|
const DEBUG_CELL_WIDTH: f32 = 5.;
|
||||||
const DEBUG_LINE_HEIGHT: f32 = 5.;
|
const DEBUG_LINE_HEIGHT: f32 = 5.;
|
||||||
|
|
||||||
///Event to transmit the scroll from the element to the view
|
actions!(terminal, [Deploy, DeployModal]);
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct ScrollTerminal(pub i32);
|
|
||||||
|
|
||||||
actions!(
|
|
||||||
terminal,
|
|
||||||
[
|
|
||||||
Deploy,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
CtrlC,
|
|
||||||
Escape,
|
|
||||||
Enter,
|
|
||||||
Clear,
|
|
||||||
Copy,
|
|
||||||
Paste,
|
|
||||||
DeployModal
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
///Initialize and register all of our action handlers
|
///Initialize and register all of our action handlers
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
//Global binding overrrides
|
cx.add_action(TerminalView::deploy);
|
||||||
cx.add_action(ConnectedView::ctrl_c);
|
|
||||||
cx.add_action(ConnectedView::up);
|
|
||||||
cx.add_action(ConnectedView::down);
|
|
||||||
cx.add_action(ConnectedView::escape);
|
|
||||||
cx.add_action(ConnectedView::enter);
|
|
||||||
//Useful terminal actions
|
|
||||||
cx.add_action(ConnectedView::deploy);
|
|
||||||
cx.add_action(ConnectedView::copy);
|
|
||||||
cx.add_action(ConnectedView::paste);
|
|
||||||
cx.add_action(ConnectedView::clear);
|
|
||||||
cx.add_action(deploy_modal);
|
cx.add_action(deploy_modal);
|
||||||
|
|
||||||
|
connected_view::init(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Make terminal view an enum, that can give you views for the error and non-error states
|
//Make terminal view an enum, that can give you views for the error and non-error states
|
||||||
|
@ -88,16 +64,6 @@ pub struct ErrorView {
|
||||||
error: TerminalError,
|
error: TerminalError,
|
||||||
}
|
}
|
||||||
|
|
||||||
///A terminal view, maintains the PTY's file handles and communicates with the terminal
|
|
||||||
pub struct ConnectedView {
|
|
||||||
terminal: ModelHandle<Terminal>,
|
|
||||||
has_new_content: bool,
|
|
||||||
//Currently using iTerm bell, show bell emoji in tab until input is received
|
|
||||||
has_bell: bool,
|
|
||||||
// Only for styling purposes. Doesn't effect behavior
|
|
||||||
modal: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Entity for TerminalView {
|
impl Entity for TerminalView {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
}
|
}
|
||||||
|
@ -111,11 +77,18 @@ impl Entity for ErrorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalView {
|
impl TerminalView {
|
||||||
|
///Create a new Terminal in the current working directory or the user's home directory
|
||||||
|
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||||
|
let working_directory = get_working_directory(workspace, cx);
|
||||||
|
let view = cx.add_view(|cx| TerminalView::new(working_directory, false, cx));
|
||||||
|
workspace.add_item(Box::new(view), cx);
|
||||||
|
}
|
||||||
|
|
||||||
///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices
|
///Create a new Terminal view. This spawns a task, a thread, and opens the TTY devices
|
||||||
///To get the right working directory from a workspace, use: `get_wd_for_workspace()`
|
///To get the right working directory from a workspace, use: `get_wd_for_workspace()`
|
||||||
fn new(working_directory: Option<PathBuf>, modal: bool, cx: &mut ViewContext<Self>) -> Self {
|
fn new(working_directory: Option<PathBuf>, modal: bool, cx: &mut ViewContext<Self>) -> Self {
|
||||||
//The details here don't matter, the terminal will be resized on the first layout
|
//The details here don't matter, the terminal will be resized on the first layout
|
||||||
let size_info = TerminalDimensions::new(
|
let size_info = TermDimensions::new(
|
||||||
DEBUG_LINE_HEIGHT,
|
DEBUG_LINE_HEIGHT,
|
||||||
DEBUG_CELL_WIDTH,
|
DEBUG_CELL_WIDTH,
|
||||||
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
|
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
|
||||||
|
@ -194,122 +167,6 @@ impl View for TerminalView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectedView {
|
|
||||||
fn from_terminal(
|
|
||||||
terminal: ModelHandle<Terminal>,
|
|
||||||
modal: bool,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> Self {
|
|
||||||
cx.observe(&terminal, |_, _, cx| cx.notify()).detach();
|
|
||||||
cx.subscribe(&terminal, |this, _, event, cx| match event {
|
|
||||||
Event::Wakeup => {
|
|
||||||
if cx.is_self_focused() {
|
|
||||||
cx.notify()
|
|
||||||
} else {
|
|
||||||
this.has_new_content = true;
|
|
||||||
cx.emit(Event::TitleChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Bell => {
|
|
||||||
this.has_bell = true;
|
|
||||||
cx.emit(Event::TitleChanged);
|
|
||||||
}
|
|
||||||
_ => cx.emit(*event),
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
terminal,
|
|
||||||
has_new_content: true,
|
|
||||||
has_bell: false,
|
|
||||||
modal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_bel(&mut self, cx: &mut ViewContext<ConnectedView>) {
|
|
||||||
self.has_bell = false;
|
|
||||||
cx.emit(Event::TitleChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
///Create a new Terminal in the current working directory or the user's home directory
|
|
||||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
|
||||||
let working_directory = get_working_directory(workspace, cx);
|
|
||||||
let view = cx.add_view(|cx| TerminalView::new(working_directory, false, cx));
|
|
||||||
workspace.add_item(Box::new(view), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear(&mut self, _: &Clear, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal.read(cx).clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
///Attempt to paste the clipboard into the terminal
|
|
||||||
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.copy()
|
|
||||||
.map(|text| cx.write_to_clipboard(ClipboardItem::new(text)));
|
|
||||||
}
|
|
||||||
|
|
||||||
///Attempt to paste the clipboard into the terminal
|
|
||||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
|
||||||
cx.read_from_clipboard().map(|item| {
|
|
||||||
self.terminal.read(cx).paste(item.text());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'up'
|
|
||||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.try_keystroke(&Keystroke::parse("up").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'down'
|
|
||||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.try_keystroke(&Keystroke::parse("down").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'ctrl-c'
|
|
||||||
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'escape'
|
|
||||||
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.try_keystroke(&Keystroke::parse("escape").unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'enter'
|
|
||||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
|
||||||
self.terminal
|
|
||||||
.read(cx)
|
|
||||||
.try_keystroke(&Keystroke::parse("enter").unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ConnectedView {
|
|
||||||
fn ui_name() -> &'static str {
|
|
||||||
"Connected Terminal View"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
|
||||||
let terminal_handle = self.terminal.clone().downgrade();
|
|
||||||
TerminalEl::new(cx.handle(), terminal_handle, self.modal)
|
|
||||||
.contained()
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
|
|
||||||
self.has_new_content = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View for ErrorView {
|
impl View for ErrorView {
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
"Terminal Error"
|
"Terminal Error"
|
||||||
|
@ -317,7 +174,7 @@ impl View for ErrorView {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let style = TerminalLayoutData::make_text_style(cx.font_cache(), settings);
|
let style = TerminalEl::make_text_style(cx.font_cache(), settings);
|
||||||
|
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
|
@ -341,7 +198,7 @@ impl Item for TerminalView {
|
||||||
) -> ElementBox {
|
) -> ElementBox {
|
||||||
let title = match &self.content {
|
let title = match &self.content {
|
||||||
TerminalContent::Connected(connected) => {
|
TerminalContent::Connected(connected) => {
|
||||||
connected.read(cx).terminal.read(cx).title.clone()
|
connected.read(cx).handle().read(cx).title.clone()
|
||||||
}
|
}
|
||||||
TerminalContent::Error(_) => "Terminal".to_string(),
|
TerminalContent::Error(_) => "Terminal".to_string(),
|
||||||
};
|
};
|
||||||
|
@ -363,7 +220,7 @@ impl Item for TerminalView {
|
||||||
if let TerminalContent::Connected(connected) = &self.content {
|
if let TerminalContent::Connected(connected) = &self.content {
|
||||||
let associated_directory = connected
|
let associated_directory = connected
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.terminal
|
.handle()
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.associated_directory
|
.associated_directory
|
||||||
.clone();
|
.clone();
|
||||||
|
@ -418,7 +275,7 @@ impl Item for TerminalView {
|
||||||
|
|
||||||
fn is_dirty(&self, cx: &gpui::AppContext) -> bool {
|
fn is_dirty(&self, cx: &gpui::AppContext) -> bool {
|
||||||
if let TerminalContent::Connected(connected) = &self.content {
|
if let TerminalContent::Connected(connected) = &self.content {
|
||||||
connected.read(cx).has_new_content
|
connected.read(cx).has_new_content()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -426,7 +283,7 @@ impl Item for TerminalView {
|
||||||
|
|
||||||
fn has_conflict(&self, cx: &AppContext) -> bool {
|
fn has_conflict(&self, cx: &AppContext) -> bool {
|
||||||
if let TerminalContent::Connected(connected) = &self.content {
|
if let TerminalContent::Connected(connected) = &self.content {
|
||||||
connected.read(cx).has_bell
|
connected.read(cx).has_bell()
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub struct TerminalLayoutData<'a> {
|
|
||||||
pub text_style: TextStyle,
|
|
||||||
pub selection_color: Color,
|
|
||||||
pub terminal_theme: &'a TerminalStyle,
|
|
||||||
pub size: TerminalDimensions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TerminalLayoutData<'a> {
|
|
||||||
pub fn new(settings: &'a Settings, font_cache: &FontCache, constraint: Vector2F) -> Self {
|
|
||||||
let text_style = Self::make_text_style(font_cache, &settings);
|
|
||||||
let selection_color = settings.theme.editor.selection.selection;
|
|
||||||
let terminal_theme = &settings.theme.terminal;
|
|
||||||
|
|
||||||
let line_height = font_cache.line_height(text_style.font_size);
|
|
||||||
|
|
||||||
let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
|
|
||||||
let dimensions = TerminalDimensions::new(line_height, cell_width, constraint);
|
|
||||||
|
|
||||||
TerminalLayoutData {
|
|
||||||
size: dimensions,
|
|
||||||
text_style,
|
|
||||||
selection_color,
|
|
||||||
terminal_theme,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///Configures a text style from the current settings.
|
|
||||||
pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
|
|
||||||
// Pull the font family from settings properly overriding
|
|
||||||
let family_id = settings
|
|
||||||
.terminal_overrides
|
|
||||||
.font_family
|
|
||||||
.as_ref()
|
|
||||||
.or_else(|| settings.terminal_defaults.font_family.as_ref())
|
|
||||||
.and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
|
|
||||||
.unwrap_or(settings.buffer_font_family);
|
|
||||||
|
|
||||||
let font_size = settings
|
|
||||||
.terminal_overrides
|
|
||||||
.font_size
|
|
||||||
.or(settings.terminal_defaults.font_size)
|
|
||||||
.unwrap_or(settings.buffer_font_size);
|
|
||||||
|
|
||||||
let font_id = font_cache
|
|
||||||
.select_font(family_id, &Default::default())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
TextStyle {
|
|
||||||
color: settings.theme.editor.text_color,
|
|
||||||
font_family_id: family_id,
|
|
||||||
font_family_name: font_cache.family_name(family_id).unwrap(),
|
|
||||||
font_id,
|
|
||||||
font_size,
|
|
||||||
font_properties: Default::default(),
|
|
||||||
underline: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,8 +8,8 @@ use project::{Entry, Project, ProjectPath, Worktree};
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connection::{Terminal, TerminalBuilder},
|
connected_el::TermDimensions,
|
||||||
terminal_element::TerminalDimensions,
|
model::{Terminal, TerminalBuilder},
|
||||||
DEBUG_CELL_WIDTH, DEBUG_LINE_HEIGHT, DEBUG_TERMINAL_HEIGHT, DEBUG_TERMINAL_WIDTH,
|
DEBUG_CELL_WIDTH, DEBUG_LINE_HEIGHT, DEBUG_TERMINAL_HEIGHT, DEBUG_TERMINAL_WIDTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ impl<'a> TerminalTestContext<'a> {
|
||||||
pub fn new(cx: &'a mut TestAppContext, term: bool) -> Self {
|
pub fn new(cx: &'a mut TestAppContext, term: bool) -> Self {
|
||||||
cx.set_condition_duration(Some(Duration::from_secs(5)));
|
cx.set_condition_duration(Some(Duration::from_secs(5)));
|
||||||
|
|
||||||
let size_info = TerminalDimensions::new(
|
let size_info = TermDimensions::new(
|
||||||
DEBUG_CELL_WIDTH,
|
DEBUG_CELL_WIDTH,
|
||||||
DEBUG_LINE_HEIGHT,
|
DEBUG_LINE_HEIGHT,
|
||||||
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
|
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue