Merge branch 'main' into select-on-rename
This commit is contained in:
commit
951fd1ab36
28 changed files with 1063 additions and 257 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5852,6 +5852,7 @@ dependencies = [
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"smallvec",
|
||||||
"theme",
|
"theme",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
|
@ -631,6 +631,9 @@ impl Client {
|
||||||
} else {
|
} else {
|
||||||
log::info!("unhandled message {}", type_name);
|
log::info!("unhandled message {}", type_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't starve the main thread when receiving lots of messages at once.
|
||||||
|
smol::future::yield_now().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod wrap_map;
|
||||||
use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
use crate::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||||
use block_map::{BlockMap, BlockPoint};
|
use block_map::{BlockMap, BlockPoint};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fold_map::{FoldMap, ToFoldPoint as _};
|
use fold_map::FoldMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
fonts::{FontId, HighlightStyle},
|
fonts::{FontId, HighlightStyle},
|
||||||
Entity, ModelContext, ModelHandle,
|
Entity, ModelContext, ModelHandle,
|
||||||
|
@ -223,7 +223,7 @@ impl DisplaySnapshot {
|
||||||
|
|
||||||
pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||||
loop {
|
loop {
|
||||||
let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Left);
|
let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Left);
|
||||||
*fold_point.column_mut() = 0;
|
*fold_point.column_mut() = 0;
|
||||||
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ impl DisplaySnapshot {
|
||||||
|
|
||||||
pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) {
|
||||||
loop {
|
loop {
|
||||||
let mut fold_point = point.to_fold_point(&self.folds_snapshot, Bias::Right);
|
let mut fold_point = self.folds_snapshot.to_fold_point(point, Bias::Right);
|
||||||
*fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
|
*fold_point.column_mut() = self.folds_snapshot.line_len(fold_point.row());
|
||||||
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
point = fold_point.to_buffer_point(&self.folds_snapshot);
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
||||||
let fold_point = point.to_fold_point(&self.folds_snapshot, bias);
|
let fold_point = self.folds_snapshot.to_fold_point(point, bias);
|
||||||
let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
|
let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
|
||||||
let wrap_point = self.wraps_snapshot.from_tab_point(tab_point);
|
let wrap_point = self.wraps_snapshot.from_tab_point(tab_point);
|
||||||
let block_point = self.blocks_snapshot.to_block_point(wrap_point);
|
let block_point = self.blocks_snapshot.to_block_point(wrap_point);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::TextHighlights;
|
||||||
use crate::{
|
use crate::{
|
||||||
multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot,
|
multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot,
|
||||||
ToOffset,
|
ToOffset,
|
||||||
|
@ -16,12 +17,6 @@ use std::{
|
||||||
};
|
};
|
||||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
use sum_tree::{Bias, Cursor, FilterCursor, SumTree};
|
||||||
|
|
||||||
use super::TextHighlights;
|
|
||||||
|
|
||||||
pub trait ToFoldPoint {
|
|
||||||
fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
|
||||||
pub struct FoldPoint(pub super::Point);
|
pub struct FoldPoint(pub super::Point);
|
||||||
|
|
||||||
|
@ -81,26 +76,6 @@ impl FoldPoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToFoldPoint for Point {
|
|
||||||
fn to_fold_point(&self, snapshot: &FoldSnapshot, bias: Bias) -> FoldPoint {
|
|
||||||
let mut cursor = snapshot.transforms.cursor::<(Point, FoldPoint)>();
|
|
||||||
cursor.seek(self, Bias::Right, &());
|
|
||||||
if cursor.item().map_or(false, |t| t.is_fold()) {
|
|
||||||
if bias == Bias::Left || *self == cursor.start().0 {
|
|
||||||
cursor.start().1
|
|
||||||
} else {
|
|
||||||
cursor.end(&()).1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let overshoot = *self - cursor.start().0;
|
|
||||||
FoldPoint(cmp::min(
|
|
||||||
cursor.start().1 .0 + overshoot,
|
|
||||||
cursor.end(&()).1 .0,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.lines;
|
self.0 += &summary.output.lines;
|
||||||
|
@ -566,6 +541,24 @@ impl FoldSnapshot {
|
||||||
summary
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_fold_point(&self, point: Point, bias: Bias) -> FoldPoint {
|
||||||
|
let mut cursor = self.transforms.cursor::<(Point, FoldPoint)>();
|
||||||
|
cursor.seek(&point, Bias::Right, &());
|
||||||
|
if cursor.item().map_or(false, |t| t.is_fold()) {
|
||||||
|
if bias == Bias::Left || point == cursor.start().0 {
|
||||||
|
cursor.start().1
|
||||||
|
} else {
|
||||||
|
cursor.end(&()).1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let overshoot = point - cursor.start().0;
|
||||||
|
FoldPoint(cmp::min(
|
||||||
|
cursor.start().1 .0 + overshoot,
|
||||||
|
cursor.end(&()).1 .0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> FoldOffset {
|
pub fn len(&self) -> FoldOffset {
|
||||||
FoldOffset(self.transforms.summary().output.bytes)
|
FoldOffset(self.transforms.summary().output.bytes)
|
||||||
}
|
}
|
||||||
|
@ -1511,7 +1504,7 @@ mod tests {
|
||||||
let buffer_point = fold_point.to_buffer_point(&snapshot);
|
let buffer_point = fold_point.to_buffer_point(&snapshot);
|
||||||
let buffer_offset = buffer_point.to_offset(&buffer_snapshot);
|
let buffer_offset = buffer_point.to_offset(&buffer_snapshot);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer_point.to_fold_point(&snapshot, Right),
|
snapshot.to_fold_point(buffer_point, Right),
|
||||||
fold_point,
|
fold_point,
|
||||||
"{:?} -> fold point",
|
"{:?} -> fold point",
|
||||||
buffer_point,
|
buffer_point,
|
||||||
|
@ -1583,10 +1576,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
for fold_range in map.merged_fold_ranges() {
|
for fold_range in map.merged_fold_ranges() {
|
||||||
let fold_point = fold_range
|
let fold_point =
|
||||||
.start
|
snapshot.to_fold_point(fold_range.start.to_point(&buffer_snapshot), Right);
|
||||||
.to_point(&buffer_snapshot)
|
|
||||||
.to_fold_point(&snapshot, Right);
|
|
||||||
assert!(snapshot.is_line_folded(fold_point.row()));
|
assert!(snapshot.is_line_folded(fold_point.row()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{
|
use super::{
|
||||||
fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot, ToFoldPoint},
|
fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot},
|
||||||
TextHighlights,
|
TextHighlights,
|
||||||
};
|
};
|
||||||
use crate::MultiBufferSnapshot;
|
use crate::MultiBufferSnapshot;
|
||||||
|
@ -212,10 +212,6 @@ impl TabSnapshot {
|
||||||
TabPoint::new(input.row(), expanded as u32)
|
TabPoint::new(input.row(), expanded as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
|
|
||||||
self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
|
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
|
||||||
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
|
||||||
let expanded = output.column() as usize;
|
let expanded = output.column() as usize;
|
||||||
|
@ -228,6 +224,10 @@ impl TabSnapshot {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint {
|
||||||
|
self.to_tab_point(self.fold_snapshot.to_fold_point(point, bias))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
|
||||||
self.to_fold_point(point, bias)
|
self.to_fold_point(point, bias)
|
||||||
.0
|
.0
|
||||||
|
|
|
@ -453,6 +453,7 @@ pub struct Editor {
|
||||||
document_highlights_task: Option<Task<()>>,
|
document_highlights_task: Option<Task<()>>,
|
||||||
pending_rename: Option<RenameState>,
|
pending_rename: Option<RenameState>,
|
||||||
searchable: bool,
|
searchable: bool,
|
||||||
|
cursor_shape: CursorShape,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorSnapshot {
|
pub struct EditorSnapshot {
|
||||||
|
@ -934,6 +935,7 @@ impl Editor {
|
||||||
pending_rename: Default::default(),
|
pending_rename: Default::default(),
|
||||||
searchable: true,
|
searchable: true,
|
||||||
override_text_style: None,
|
override_text_style: None,
|
||||||
|
cursor_shape: Default::default(),
|
||||||
};
|
};
|
||||||
this.end_selection(cx);
|
this.end_selection(cx);
|
||||||
this
|
this
|
||||||
|
@ -1030,6 +1032,11 @@ impl Editor {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
||||||
|
self.cursor_shape = cursor_shape;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor)
|
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor)
|
||||||
|
@ -2647,11 +2654,26 @@ impl Editor {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
if selection.is_empty() {
|
if selection.is_empty() {
|
||||||
let head = selection.head().to_display_point(&display_map);
|
let old_head = selection.head();
|
||||||
let cursor = movement::left(&display_map, head)
|
let mut new_head =
|
||||||
|
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_point(&display_map);
|
.to_point(&display_map);
|
||||||
selection.set_head(cursor);
|
if let Some((buffer, line_buffer_range)) = display_map
|
||||||
|
.buffer_snapshot
|
||||||
|
.buffer_line_for_row(old_head.row)
|
||||||
|
{
|
||||||
|
let indent_column = buffer.indent_column_for_line(line_buffer_range.start.row);
|
||||||
|
if old_head.column <= indent_column && old_head.column > 0 {
|
||||||
|
let indent = buffer.indent_size();
|
||||||
|
new_head = cmp::min(
|
||||||
|
new_head,
|
||||||
|
Point::new(old_head.row, ((old_head.column - 1) / indent) * indent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.set_head(new_head);
|
||||||
selection.goal = SelectionGoal::None;
|
selection.goal = SelectionGoal::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5599,7 +5621,7 @@ impl View for Editor {
|
||||||
self.display_map.update(cx, |map, cx| {
|
self.display_map.update(cx, |map, cx| {
|
||||||
map.set_font(style.text.font_id, style.text.font_size, cx)
|
map.set_font(style.text.font_id, style.text.font_size, cx)
|
||||||
});
|
});
|
||||||
EditorElement::new(self.handle.clone(), style.clone()).boxed()
|
EditorElement::new(self.handle.clone(), style.clone(), self.cursor_shape).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui_name() -> &'static str {
|
fn ui_name() -> &'static str {
|
||||||
|
@ -7202,14 +7224,13 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_backspace(cx: &mut gpui::MutableAppContext) {
|
fn test_backspace(cx: &mut gpui::MutableAppContext) {
|
||||||
let buffer =
|
|
||||||
MultiBuffer::build_simple("one two three\nfour five six\nseven eight nine\nten\n", cx);
|
|
||||||
let settings = Settings::test(&cx);
|
let settings = Settings::test(&cx);
|
||||||
let (_, view) = cx.add_window(Default::default(), |cx| {
|
let (_, view) = cx.add_window(Default::default(), |cx| {
|
||||||
build_editor(buffer.clone(), settings, cx)
|
build_editor(MultiBuffer::build_simple("", cx), settings, cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
view.update(cx, |view, cx| {
|
||||||
|
view.set_text("one two three\nfour five six\nseven eight nine\nten\n", cx);
|
||||||
view.select_display_ranges(
|
view.select_display_ranges(
|
||||||
&[
|
&[
|
||||||
// an empty selection - the preceding character is deleted
|
// an empty selection - the preceding character is deleted
|
||||||
|
@ -7222,12 +7243,28 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
view.backspace(&Backspace, cx);
|
view.backspace(&Backspace, cx);
|
||||||
});
|
assert_eq!(view.text(cx), "oe two three\nfou five six\nseven ten\n");
|
||||||
|
|
||||||
assert_eq!(
|
view.set_text(" one\n two\n three\n four", cx);
|
||||||
buffer.read(cx).read(cx).text(),
|
view.select_display_ranges(
|
||||||
"oe two three\nfou five six\nseven ten\n"
|
&[
|
||||||
|
// cursors at the the end of leading indent - last indent is deleted
|
||||||
|
DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
|
||||||
|
DisplayPoint::new(1, 8)..DisplayPoint::new(1, 8),
|
||||||
|
// cursors inside leading indent - overlapping indent deletions are coalesced
|
||||||
|
DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
|
||||||
|
DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
|
||||||
|
DisplayPoint::new(2, 6)..DisplayPoint::new(2, 6),
|
||||||
|
// cursor at the beginning of a line - preceding newline is deleted
|
||||||
|
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
||||||
|
// selection inside leading indent - only the selected character is deleted
|
||||||
|
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3),
|
||||||
|
],
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
|
view.backspace(&Backspace, cx);
|
||||||
|
assert_eq!(view.text(cx), "one\n two\n three four");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -16,7 +16,7 @@ use gpui::{
|
||||||
PathBuilder,
|
PathBuilder,
|
||||||
},
|
},
|
||||||
json::{self, ToJson},
|
json::{self, ToJson},
|
||||||
text_layout::{self, RunStyle, TextLayoutCache},
|
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
||||||
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
|
AppContext, Axis, Border, Element, ElementBox, Event, EventContext, LayoutContext,
|
||||||
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
||||||
};
|
};
|
||||||
|
@ -32,11 +32,20 @@ use std::{
|
||||||
pub struct EditorElement {
|
pub struct EditorElement {
|
||||||
view: WeakViewHandle<Editor>,
|
view: WeakViewHandle<Editor>,
|
||||||
style: EditorStyle,
|
style: EditorStyle,
|
||||||
|
cursor_shape: CursorShape,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorElement {
|
impl EditorElement {
|
||||||
pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
|
pub fn new(
|
||||||
Self { view, style }
|
view: WeakViewHandle<Editor>,
|
||||||
|
style: EditorStyle,
|
||||||
|
cursor_shape: CursorShape,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
view,
|
||||||
|
style,
|
||||||
|
cursor_shape,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
|
||||||
|
@ -338,7 +347,7 @@ impl EditorElement {
|
||||||
|
|
||||||
let mut cursors = SmallVec::<[Cursor; 32]>::new();
|
let mut cursors = SmallVec::<[Cursor; 32]>::new();
|
||||||
for (replica_id, selections) in &layout.selections {
|
for (replica_id, selections) in &layout.selections {
|
||||||
let style = style.replica_selection_style(*replica_id);
|
let selection_style = style.replica_selection_style(*replica_id);
|
||||||
let corner_radius = 0.15 * layout.line_height;
|
let corner_radius = 0.15 * layout.line_height;
|
||||||
|
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
|
@ -346,7 +355,7 @@ impl EditorElement {
|
||||||
selection.start..selection.end,
|
selection.start..selection.end,
|
||||||
start_row,
|
start_row,
|
||||||
end_row,
|
end_row,
|
||||||
style.selection,
|
selection_style.selection,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
corner_radius * 2.,
|
corner_radius * 2.,
|
||||||
layout,
|
layout,
|
||||||
|
@ -362,13 +371,50 @@ impl EditorElement {
|
||||||
if (start_row..end_row).contains(&cursor_position.row()) {
|
if (start_row..end_row).contains(&cursor_position.row()) {
|
||||||
let cursor_row_layout =
|
let cursor_row_layout =
|
||||||
&layout.line_layouts[(cursor_position.row() - start_row) as usize];
|
&layout.line_layouts[(cursor_position.row() - start_row) as usize];
|
||||||
let x = cursor_row_layout.x_for_index(cursor_position.column() as usize)
|
let cursor_column = cursor_position.column() as usize;
|
||||||
- scroll_left;
|
|
||||||
|
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||||
|
let mut block_width =
|
||||||
|
cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
|
||||||
|
if block_width == 0.0 {
|
||||||
|
block_width = layout.em_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let block_text =
|
||||||
|
if matches!(self.cursor_shape, CursorShape::Block) {
|
||||||
|
layout.snapshot.chars_at(cursor_position).next().and_then(
|
||||||
|
|character| {
|
||||||
|
let font_id =
|
||||||
|
cursor_row_layout.font_for_index(cursor_column)?;
|
||||||
|
let text = character.to_string();
|
||||||
|
|
||||||
|
Some(cx.text_layout_cache.layout_str(
|
||||||
|
&text,
|
||||||
|
cursor_row_layout.font_size(),
|
||||||
|
&[(
|
||||||
|
text.len(),
|
||||||
|
RunStyle {
|
||||||
|
font_id,
|
||||||
|
color: style.background,
|
||||||
|
underline: None,
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = cursor_character_x - scroll_left;
|
||||||
let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
|
let y = cursor_position.row() as f32 * layout.line_height - scroll_top;
|
||||||
cursors.push(Cursor {
|
cursors.push(Cursor {
|
||||||
color: style.cursor,
|
color: selection_style.cursor,
|
||||||
|
block_width,
|
||||||
origin: content_origin + vec2f(x, y),
|
origin: content_origin + vec2f(x, y),
|
||||||
line_height: layout.line_height,
|
line_height: layout.line_height,
|
||||||
|
shape: self.cursor_shape,
|
||||||
|
block_text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1168,6 +1214,7 @@ fn layout_line(
|
||||||
while !line.is_char_boundary(len) {
|
while !line.is_char_boundary(len) {
|
||||||
len -= 1;
|
len -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
line.truncate(len);
|
line.truncate(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1219,20 +1266,51 @@ impl PaintState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum CursorShape {
|
||||||
|
Bar,
|
||||||
|
Block,
|
||||||
|
Underscore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CursorShape {
|
||||||
|
fn default() -> Self {
|
||||||
|
CursorShape::Bar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Cursor {
|
struct Cursor {
|
||||||
origin: Vector2F,
|
origin: Vector2F,
|
||||||
|
block_width: f32,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
shape: CursorShape,
|
||||||
|
block_text: Option<Line>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
fn paint(&self, cx: &mut PaintContext) {
|
fn paint(&self, cx: &mut PaintContext) {
|
||||||
|
let bounds = match self.shape {
|
||||||
|
CursorShape::Bar => RectF::new(self.origin, vec2f(2.0, self.line_height)),
|
||||||
|
CursorShape::Block => {
|
||||||
|
RectF::new(self.origin, vec2f(self.block_width, self.line_height))
|
||||||
|
}
|
||||||
|
CursorShape::Underscore => RectF::new(
|
||||||
|
self.origin + Vector2F::new(0.0, self.line_height - 2.0),
|
||||||
|
vec2f(self.block_width, 2.0),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
cx.scene.push_quad(Quad {
|
cx.scene.push_quad(Quad {
|
||||||
bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
|
bounds,
|
||||||
background: Some(self.color),
|
background: Some(self.color),
|
||||||
border: Border::new(0., Color::black()),
|
border: Border::new(0., Color::black()),
|
||||||
corner_radius: 0.,
|
corner_radius: 0.,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(block_text) = &self.block_text {
|
||||||
|
block_text.paint(self.origin, bounds, self.line_height, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1396,7 +1474,7 @@ mod tests {
|
||||||
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
let (window_id, editor) = cx.add_window(Default::default(), |cx| {
|
||||||
Editor::new(EditorMode::Full, buffer, None, settings.1, None, cx)
|
Editor::new(EditorMode::Full, buffer, None, settings.1, None, cx)
|
||||||
});
|
});
|
||||||
let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
|
let element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx), CursorShape::Bar);
|
||||||
|
|
||||||
let layouts = editor.update(cx, |editor, cx| {
|
let layouts = editor.update(cx, |editor, cx| {
|
||||||
let snapshot = editor.snapshot(cx);
|
let snapshot = editor.snapshot(cx);
|
||||||
|
|
|
@ -1655,7 +1655,7 @@ impl MultiBufferSnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
|
pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range<Point>)> {
|
||||||
let mut cursor = self.excerpts.cursor::<Point>();
|
let mut cursor = self.excerpts.cursor::<Point>();
|
||||||
cursor.seek(&Point::new(row, 0), Bias::Right, &());
|
cursor.seek(&Point::new(row, 0), Bias::Right, &());
|
||||||
if let Some(excerpt) = cursor.item() {
|
if let Some(excerpt) = cursor.item() {
|
||||||
|
|
|
@ -740,6 +740,7 @@ type ActionCallback =
|
||||||
type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext);
|
type GlobalActionCallback = dyn FnMut(&dyn AnyAction, &mut MutableAppContext);
|
||||||
|
|
||||||
type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext) -> bool>;
|
type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext) -> bool>;
|
||||||
|
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||||
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||||
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||||
|
|
||||||
|
@ -757,6 +758,7 @@ pub struct MutableAppContext {
|
||||||
next_subscription_id: usize,
|
next_subscription_id: usize,
|
||||||
frame_count: usize,
|
frame_count: usize,
|
||||||
subscriptions: Arc<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>,
|
subscriptions: Arc<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>,
|
||||||
|
global_subscriptions: Arc<Mutex<HashMap<TypeId, BTreeMap<usize, GlobalSubscriptionCallback>>>>,
|
||||||
observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ObservationCallback>>>>,
|
observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ObservationCallback>>>>,
|
||||||
release_observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ReleaseObservationCallback>>>>,
|
release_observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ReleaseObservationCallback>>>>,
|
||||||
presenters_and_platform_windows:
|
presenters_and_platform_windows:
|
||||||
|
@ -804,6 +806,7 @@ impl MutableAppContext {
|
||||||
next_subscription_id: 0,
|
next_subscription_id: 0,
|
||||||
frame_count: 0,
|
frame_count: 0,
|
||||||
subscriptions: Default::default(),
|
subscriptions: Default::default(),
|
||||||
|
global_subscriptions: Default::default(),
|
||||||
observations: Default::default(),
|
observations: Default::default(),
|
||||||
release_observations: Default::default(),
|
release_observations: Default::default(),
|
||||||
presenters_and_platform_windows: HashMap::new(),
|
presenters_and_platform_windows: HashMap::new(),
|
||||||
|
@ -1062,6 +1065,12 @@ impl MutableAppContext {
|
||||||
self.foreground_platform.prompt_for_new_path(directory)
|
self.foreground_platform.prompt_for_new_path(directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn emit_global<E: Any>(&mut self, payload: E) {
|
||||||
|
self.pending_effects.push_back(Effect::GlobalEvent {
|
||||||
|
payload: Box::new(payload),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||||
where
|
where
|
||||||
E: Entity,
|
E: Entity,
|
||||||
|
@ -1075,6 +1084,31 @@ impl MutableAppContext {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_global<E, F>(&mut self, mut callback: F) -> Subscription
|
||||||
|
where
|
||||||
|
E: Any,
|
||||||
|
F: 'static + FnMut(&E, &mut Self),
|
||||||
|
{
|
||||||
|
let id = post_inc(&mut self.next_subscription_id);
|
||||||
|
let type_id = TypeId::of::<E>();
|
||||||
|
self.global_subscriptions
|
||||||
|
.lock()
|
||||||
|
.entry(type_id)
|
||||||
|
.or_default()
|
||||||
|
.insert(
|
||||||
|
id,
|
||||||
|
Box::new(move |payload, cx| {
|
||||||
|
let payload = payload.downcast_ref().expect("downcast is type safe");
|
||||||
|
callback(payload, cx)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
Subscription::GlobalSubscription {
|
||||||
|
id,
|
||||||
|
type_id,
|
||||||
|
subscriptions: Some(Arc::downgrade(&self.global_subscriptions)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
pub fn observe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||||
where
|
where
|
||||||
E: Entity,
|
E: Entity,
|
||||||
|
@ -1573,6 +1607,7 @@ impl MutableAppContext {
|
||||||
if let Some(effect) = self.pending_effects.pop_front() {
|
if let Some(effect) = self.pending_effects.pop_front() {
|
||||||
match effect {
|
match effect {
|
||||||
Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload),
|
Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload),
|
||||||
|
Effect::GlobalEvent { payload } => self.emit_global_event(payload),
|
||||||
Effect::ModelNotification { model_id } => {
|
Effect::ModelNotification { model_id } => {
|
||||||
self.notify_model_observers(model_id)
|
self.notify_model_observers(model_id)
|
||||||
}
|
}
|
||||||
|
@ -1700,6 +1735,16 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn emit_global_event(&mut self, payload: Box<dyn Any>) {
|
||||||
|
let type_id = (&*payload).type_id();
|
||||||
|
let callbacks = self.global_subscriptions.lock().remove(&type_id);
|
||||||
|
if let Some(callbacks) = callbacks {
|
||||||
|
for (_, mut callback) in callbacks {
|
||||||
|
callback(payload.as_ref(), self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn notify_model_observers(&mut self, observed_id: usize) {
|
fn notify_model_observers(&mut self, observed_id: usize) {
|
||||||
let callbacks = self.observations.lock().remove(&observed_id);
|
let callbacks = self.observations.lock().remove(&observed_id);
|
||||||
if let Some(callbacks) = callbacks {
|
if let Some(callbacks) = callbacks {
|
||||||
|
@ -2071,6 +2116,9 @@ pub enum Effect {
|
||||||
entity_id: usize,
|
entity_id: usize,
|
||||||
payload: Box<dyn Any>,
|
payload: Box<dyn Any>,
|
||||||
},
|
},
|
||||||
|
GlobalEvent {
|
||||||
|
payload: Box<dyn Any>,
|
||||||
|
},
|
||||||
ModelNotification {
|
ModelNotification {
|
||||||
model_id: usize,
|
model_id: usize,
|
||||||
},
|
},
|
||||||
|
@ -2104,6 +2152,10 @@ impl Debug for Effect {
|
||||||
.debug_struct("Effect::Event")
|
.debug_struct("Effect::Event")
|
||||||
.field("entity_id", entity_id)
|
.field("entity_id", entity_id)
|
||||||
.finish(),
|
.finish(),
|
||||||
|
Effect::GlobalEvent { payload, .. } => f
|
||||||
|
.debug_struct("Effect::GlobalEvent")
|
||||||
|
.field("type_id", &(&*payload).type_id())
|
||||||
|
.finish(),
|
||||||
Effect::ModelNotification { model_id } => f
|
Effect::ModelNotification { model_id } => f
|
||||||
.debug_struct("Effect::ModelNotification")
|
.debug_struct("Effect::ModelNotification")
|
||||||
.field("model_id", model_id)
|
.field("model_id", model_id)
|
||||||
|
@ -3762,6 +3814,12 @@ pub enum Subscription {
|
||||||
entity_id: usize,
|
entity_id: usize,
|
||||||
subscriptions: Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>>,
|
subscriptions: Option<Weak<Mutex<HashMap<usize, BTreeMap<usize, SubscriptionCallback>>>>>,
|
||||||
},
|
},
|
||||||
|
GlobalSubscription {
|
||||||
|
id: usize,
|
||||||
|
type_id: TypeId,
|
||||||
|
subscriptions:
|
||||||
|
Option<Weak<Mutex<HashMap<TypeId, BTreeMap<usize, GlobalSubscriptionCallback>>>>>,
|
||||||
|
},
|
||||||
Observation {
|
Observation {
|
||||||
id: usize,
|
id: usize,
|
||||||
entity_id: usize,
|
entity_id: usize,
|
||||||
|
@ -3781,6 +3839,9 @@ impl Subscription {
|
||||||
Subscription::Subscription { subscriptions, .. } => {
|
Subscription::Subscription { subscriptions, .. } => {
|
||||||
subscriptions.take();
|
subscriptions.take();
|
||||||
}
|
}
|
||||||
|
Subscription::GlobalSubscription { subscriptions, .. } => {
|
||||||
|
subscriptions.take();
|
||||||
|
}
|
||||||
Subscription::Observation { observations, .. } => {
|
Subscription::Observation { observations, .. } => {
|
||||||
observations.take();
|
observations.take();
|
||||||
}
|
}
|
||||||
|
@ -3794,6 +3855,28 @@ impl Subscription {
|
||||||
impl Drop for Subscription {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
match self {
|
match self {
|
||||||
|
Subscription::Subscription {
|
||||||
|
id,
|
||||||
|
entity_id,
|
||||||
|
subscriptions,
|
||||||
|
} => {
|
||||||
|
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
||||||
|
if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) {
|
||||||
|
subscriptions.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Subscription::GlobalSubscription {
|
||||||
|
id,
|
||||||
|
type_id,
|
||||||
|
subscriptions,
|
||||||
|
} => {
|
||||||
|
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
||||||
|
if let Some(subscriptions) = subscriptions.lock().get_mut(type_id) {
|
||||||
|
subscriptions.remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Subscription::Observation {
|
Subscription::Observation {
|
||||||
id,
|
id,
|
||||||
entity_id,
|
entity_id,
|
||||||
|
@ -3816,17 +3899,6 @@ impl Drop for Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Subscription::Subscription {
|
|
||||||
id,
|
|
||||||
entity_id,
|
|
||||||
subscriptions,
|
|
||||||
} => {
|
|
||||||
if let Some(subscriptions) = subscriptions.as_ref().and_then(Weak::upgrade) {
|
|
||||||
if let Some(subscriptions) = subscriptions.lock().get_mut(entity_id) {
|
|
||||||
subscriptions.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,15 @@ use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext,
|
geometry::vector::Vector2F, DebugContext, Element, ElementBox, Event, EventContext,
|
||||||
LayoutContext, PaintContext, SizeConstraint,
|
LayoutContext, NavigationDirection, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
child: ElementBox,
|
child: ElementBox,
|
||||||
capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>,
|
capture: Option<Box<dyn FnMut(&Event, RectF, &mut EventContext) -> bool>>,
|
||||||
mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
|
mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
|
||||||
|
right_mouse_down: Option<Box<dyn FnMut(&mut EventContext) -> bool>>,
|
||||||
|
navigate_mouse_down: Option<Box<dyn FnMut(NavigationDirection, &mut EventContext) -> bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
|
@ -18,6 +20,8 @@ impl EventHandler {
|
||||||
child,
|
child,
|
||||||
capture: None,
|
capture: None,
|
||||||
mouse_down: None,
|
mouse_down: None,
|
||||||
|
right_mouse_down: None,
|
||||||
|
navigate_mouse_down: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +33,22 @@ impl EventHandler {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_right_mouse_down<F>(mut self, callback: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(&mut EventContext) -> bool,
|
||||||
|
{
|
||||||
|
self.right_mouse_down = Some(Box::new(callback));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_navigate_mouse_down<F>(mut self, callback: F) -> Self
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(NavigationDirection, &mut EventContext) -> bool,
|
||||||
|
{
|
||||||
|
self.navigate_mouse_down = Some(Box::new(callback));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn capture<F>(mut self, callback: F) -> Self
|
pub fn capture<F>(mut self, callback: F) -> Self
|
||||||
where
|
where
|
||||||
F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool,
|
F: 'static + FnMut(&Event, RectF, &mut EventContext) -> bool,
|
||||||
|
@ -87,6 +107,26 @@ impl Element for EventHandler {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
Event::RightMouseDown { position, .. } => {
|
||||||
|
if let Some(callback) = self.right_mouse_down.as_mut() {
|
||||||
|
if bounds.contains_point(*position) {
|
||||||
|
return callback(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Event::NavigateMouseDown {
|
||||||
|
position,
|
||||||
|
direction,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(callback) = self.navigate_mouse_down.as_mut() {
|
||||||
|
if bounds.contains_point(*position) {
|
||||||
|
return callback(*direction, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub mod keymap;
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
pub use gpui_macros::test;
|
pub use gpui_macros::test;
|
||||||
pub use platform::FontSystem;
|
pub use platform::FontSystem;
|
||||||
pub use platform::{Event, PathPromptOptions, Platform, PromptLevel};
|
pub use platform::{Event, NavigationDirection, PathPromptOptions, Platform, PromptLevel};
|
||||||
pub use presenter::{
|
pub use presenter::{
|
||||||
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
pub use event::Event;
|
pub use event::{Event, NavigationDirection};
|
||||||
use postage::oneshot;
|
use postage::oneshot;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
use crate::{geometry::vector::Vector2F, keymap::Keystroke};
|
use crate::{geometry::vector::Vector2F, keymap::Keystroke};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum NavigationDirection {
|
||||||
|
Back,
|
||||||
|
Forward,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
KeyDown {
|
KeyDown {
|
||||||
|
@ -26,6 +32,30 @@ pub enum Event {
|
||||||
LeftMouseDragged {
|
LeftMouseDragged {
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
},
|
},
|
||||||
|
RightMouseDown {
|
||||||
|
position: Vector2F,
|
||||||
|
ctrl: bool,
|
||||||
|
alt: bool,
|
||||||
|
shift: bool,
|
||||||
|
cmd: bool,
|
||||||
|
click_count: usize,
|
||||||
|
},
|
||||||
|
RightMouseUp {
|
||||||
|
position: Vector2F,
|
||||||
|
},
|
||||||
|
NavigateMouseDown {
|
||||||
|
position: Vector2F,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
ctrl: bool,
|
||||||
|
alt: bool,
|
||||||
|
shift: bool,
|
||||||
|
cmd: bool,
|
||||||
|
click_count: usize,
|
||||||
|
},
|
||||||
|
NavigateMouseUp {
|
||||||
|
position: Vector2F,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
},
|
||||||
MouseMoved {
|
MouseMoved {
|
||||||
position: Vector2F,
|
position: Vector2F,
|
||||||
left_mouse_down: bool,
|
left_mouse_down: bool,
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
|
use crate::{
|
||||||
|
geometry::vector::vec2f,
|
||||||
|
keymap::Keystroke,
|
||||||
|
platform::{Event, NavigationDirection},
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
|
appkit::{NSEvent, NSEventModifierFlags, NSEventType},
|
||||||
base::{id, nil, YES},
|
base::{id, nil, YES},
|
||||||
|
@ -125,6 +129,64 @@ impl Event {
|
||||||
window_height - native_event.locationInWindow().y as f32,
|
window_height - native_event.locationInWindow().y as f32,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
NSEventType::NSRightMouseDown => {
|
||||||
|
let modifiers = native_event.modifierFlags();
|
||||||
|
window_height.map(|window_height| Self::RightMouseDown {
|
||||||
|
position: vec2f(
|
||||||
|
native_event.locationInWindow().x as f32,
|
||||||
|
window_height - native_event.locationInWindow().y as f32,
|
||||||
|
),
|
||||||
|
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||||
|
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||||
|
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||||
|
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||||
|
click_count: native_event.clickCount() as usize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
NSEventType::NSRightMouseUp => window_height.map(|window_height| Self::RightMouseUp {
|
||||||
|
position: vec2f(
|
||||||
|
native_event.locationInWindow().x as f32,
|
||||||
|
window_height - native_event.locationInWindow().y as f32,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
NSEventType::NSOtherMouseDown => {
|
||||||
|
let direction = match native_event.buttonNumber() {
|
||||||
|
3 => NavigationDirection::Back,
|
||||||
|
4 => NavigationDirection::Forward,
|
||||||
|
// Other mouse buttons aren't tracked currently
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let modifiers = native_event.modifierFlags();
|
||||||
|
window_height.map(|window_height| Self::NavigateMouseDown {
|
||||||
|
position: vec2f(
|
||||||
|
native_event.locationInWindow().x as f32,
|
||||||
|
window_height - native_event.locationInWindow().y as f32,
|
||||||
|
),
|
||||||
|
direction,
|
||||||
|
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||||
|
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||||
|
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||||
|
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||||
|
click_count: native_event.clickCount() as usize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
NSEventType::NSOtherMouseUp => {
|
||||||
|
let direction = match native_event.buttonNumber() {
|
||||||
|
3 => NavigationDirection::Back,
|
||||||
|
4 => NavigationDirection::Forward,
|
||||||
|
// Other mouse buttons aren't tracked currently
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
window_height.map(|window_height| Self::NavigateMouseUp {
|
||||||
|
position: vec2f(
|
||||||
|
native_event.locationInWindow().x as f32,
|
||||||
|
window_height - native_event.locationInWindow().y as f32,
|
||||||
|
),
|
||||||
|
direction,
|
||||||
|
})
|
||||||
|
}
|
||||||
NSEventType::NSLeftMouseDragged => {
|
NSEventType::NSLeftMouseDragged => {
|
||||||
window_height.map(|window_height| Self::LeftMouseDragged {
|
window_height.map(|window_height| Self::LeftMouseDragged {
|
||||||
position: vec2f(
|
position: vec2f(
|
||||||
|
|
|
@ -95,6 +95,22 @@ unsafe fn build_classes() {
|
||||||
sel!(mouseUp:),
|
sel!(mouseUp:),
|
||||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(rightMouseDown:),
|
||||||
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(rightMouseUp:),
|
||||||
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(otherMouseDown:),
|
||||||
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(otherMouseUp:),
|
||||||
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(mouseMoved:),
|
sel!(mouseMoved:),
|
||||||
handle_view_event as extern "C" fn(&Object, Sel, id),
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
||||||
|
|
|
@ -186,7 +186,7 @@ pub struct Run {
|
||||||
pub glyphs: Vec<Glyph>,
|
pub glyphs: Vec<Glyph>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Glyph {
|
pub struct Glyph {
|
||||||
pub id: GlyphId,
|
pub id: GlyphId,
|
||||||
pub position: Vector2F,
|
pub position: Vector2F,
|
||||||
|
@ -210,10 +210,14 @@ impl Line {
|
||||||
self.layout.width
|
self.layout.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn font_size(&self) -> f32 {
|
||||||
|
self.layout.font_size
|
||||||
|
}
|
||||||
|
|
||||||
pub fn x_for_index(&self, index: usize) -> f32 {
|
pub fn x_for_index(&self, index: usize) -> f32 {
|
||||||
for run in &self.layout.runs {
|
for run in &self.layout.runs {
|
||||||
for glyph in &run.glyphs {
|
for glyph in &run.glyphs {
|
||||||
if glyph.index == index {
|
if glyph.index >= index {
|
||||||
return glyph.position.x();
|
return glyph.position.x();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,6 +225,18 @@ impl Line {
|
||||||
self.layout.width
|
self.layout.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||||
|
for run in &self.layout.runs {
|
||||||
|
for glyph in &run.glyphs {
|
||||||
|
if glyph.index >= index {
|
||||||
|
return Some(run.font_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn index_for_x(&self, x: f32) -> Option<usize> {
|
pub fn index_for_x(&self, x: f32) -> Option<usize> {
|
||||||
if x >= self.layout.width {
|
if x >= self.layout.width {
|
||||||
None
|
None
|
||||||
|
|
|
@ -47,9 +47,6 @@ lazy_static! {
|
||||||
static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
|
static ref QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - Make this configurable
|
|
||||||
const INDENT_SIZE: u32 = 4;
|
|
||||||
|
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
text: TextBuffer,
|
text: TextBuffer,
|
||||||
file: Option<Box<dyn File>>,
|
file: Option<Box<dyn File>>,
|
||||||
|
@ -70,6 +67,7 @@ pub struct Buffer {
|
||||||
file_update_count: usize,
|
file_update_count: usize,
|
||||||
completion_triggers: Vec<String>,
|
completion_triggers: Vec<String>,
|
||||||
deferred_ops: OperationQueue<Operation>,
|
deferred_ops: OperationQueue<Operation>,
|
||||||
|
indent_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BufferSnapshot {
|
pub struct BufferSnapshot {
|
||||||
|
@ -81,9 +79,9 @@ pub struct BufferSnapshot {
|
||||||
file_update_count: usize,
|
file_update_count: usize,
|
||||||
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
||||||
selections_update_count: usize,
|
selections_update_count: usize,
|
||||||
is_parsing: bool,
|
|
||||||
language: Option<Arc<Language>>,
|
language: Option<Arc<Language>>,
|
||||||
parse_count: usize,
|
parse_count: usize,
|
||||||
|
indent_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -417,6 +415,8 @@ impl Buffer {
|
||||||
file_update_count: 0,
|
file_update_count: 0,
|
||||||
completion_triggers: Default::default(),
|
completion_triggers: Default::default(),
|
||||||
deferred_ops: OperationQueue::new(),
|
deferred_ops: OperationQueue::new(),
|
||||||
|
// TODO: make this configurable
|
||||||
|
indent_size: 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,10 +429,10 @@ impl Buffer {
|
||||||
diagnostics: self.diagnostics.clone(),
|
diagnostics: self.diagnostics.clone(),
|
||||||
diagnostics_update_count: self.diagnostics_update_count,
|
diagnostics_update_count: self.diagnostics_update_count,
|
||||||
file_update_count: self.file_update_count,
|
file_update_count: self.file_update_count,
|
||||||
is_parsing: self.parsing_in_background,
|
|
||||||
language: self.language.clone(),
|
language: self.language.clone(),
|
||||||
parse_count: self.parse_count,
|
parse_count: self.parse_count,
|
||||||
selections_update_count: self.selections_update_count,
|
selections_update_count: self.selections_update_count,
|
||||||
|
indent_size: self.indent_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,7 +769,11 @@ impl Buffer {
|
||||||
.before_edit
|
.before_edit
|
||||||
.indent_column_for_line(suggestion.basis_row)
|
.indent_column_for_line(suggestion.basis_row)
|
||||||
});
|
});
|
||||||
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
let delta = if suggestion.indent {
|
||||||
|
snapshot.indent_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
old_suggestions.insert(
|
old_suggestions.insert(
|
||||||
*old_to_new_rows.get(&old_row).unwrap(),
|
*old_to_new_rows.get(&old_row).unwrap(),
|
||||||
indentation_basis + delta,
|
indentation_basis + delta,
|
||||||
|
@ -788,7 +792,11 @@ impl Buffer {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten();
|
.flatten();
|
||||||
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
|
for (new_row, suggestion) in new_edited_row_range.zip(suggestions) {
|
||||||
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
let delta = if suggestion.indent {
|
||||||
|
snapshot.indent_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
let new_indentation = indent_columns
|
let new_indentation = indent_columns
|
||||||
.get(&suggestion.basis_row)
|
.get(&suggestion.basis_row)
|
||||||
.copied()
|
.copied()
|
||||||
|
@ -820,7 +828,11 @@ impl Buffer {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten();
|
.flatten();
|
||||||
for (row, suggestion) in inserted_row_range.zip(suggestions) {
|
for (row, suggestion) in inserted_row_range.zip(suggestions) {
|
||||||
let delta = if suggestion.indent { INDENT_SIZE } else { 0 };
|
let delta = if suggestion.indent {
|
||||||
|
snapshot.indent_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
let new_indentation = indent_columns
|
let new_indentation = indent_columns
|
||||||
.get(&suggestion.basis_row)
|
.get(&suggestion.basis_row)
|
||||||
.copied()
|
.copied()
|
||||||
|
@ -1869,6 +1881,10 @@ impl BufferSnapshot {
|
||||||
pub fn file_update_count(&self) -> usize {
|
pub fn file_update_count(&self) -> usize {
|
||||||
self.file_update_count
|
self.file_update_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn indent_size(&self) -> u32 {
|
||||||
|
self.indent_size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for BufferSnapshot {
|
impl Clone for BufferSnapshot {
|
||||||
|
@ -1882,9 +1898,9 @@ impl Clone for BufferSnapshot {
|
||||||
selections_update_count: self.selections_update_count,
|
selections_update_count: self.selections_update_count,
|
||||||
diagnostics_update_count: self.diagnostics_update_count,
|
diagnostics_update_count: self.diagnostics_update_count,
|
||||||
file_update_count: self.file_update_count,
|
file_update_count: self.file_update_count,
|
||||||
is_parsing: self.is_parsing,
|
|
||||||
language: self.language.clone(),
|
language: self.language.clone(),
|
||||||
parse_count: self.parse_count,
|
parse_count: self.parse_count,
|
||||||
|
indent_size: self.indent_size,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
next_id: AtomicUsize,
|
next_id: AtomicUsize,
|
||||||
outbound_tx: channel::Sender<Vec<u8>>,
|
outbound_tx: channel::Sender<Vec<u8>>,
|
||||||
|
name: String,
|
||||||
capabilities: ServerCapabilities,
|
capabilities: ServerCapabilities,
|
||||||
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<RwLock<HashMap<&'static str, NotificationHandler>>>,
|
||||||
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
response_handlers: Arc<Mutex<HashMap<usize, ResponseHandler>>>,
|
||||||
|
@ -118,9 +119,11 @@ impl LanguageServer {
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
let stdin = server.stdin.take().unwrap();
|
let stdin = server.stdin.take().unwrap();
|
||||||
let stdout = server.stdout.take().unwrap();
|
let stdout = server.stdout.take().unwrap();
|
||||||
Ok(Self::new_internal(
|
let mut server = Self::new_internal(stdin, stdout, root_path, options, background);
|
||||||
stdin, stdout, root_path, options, background,
|
if let Some(name) = binary_path.file_name() {
|
||||||
))
|
server.name = name.to_string_lossy().to_string();
|
||||||
|
}
|
||||||
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal<Stdin, Stdout>(
|
fn new_internal<Stdin, Stdout>(
|
||||||
|
@ -222,6 +225,7 @@ impl LanguageServer {
|
||||||
Self {
|
Self {
|
||||||
notification_handlers,
|
notification_handlers,
|
||||||
response_handlers,
|
response_handlers,
|
||||||
|
name: Default::default(),
|
||||||
capabilities: Default::default(),
|
capabilities: Default::default(),
|
||||||
next_id: Default::default(),
|
next_id: Default::default(),
|
||||||
outbound_tx,
|
outbound_tx,
|
||||||
|
@ -292,7 +296,13 @@ impl LanguageServer {
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = this.request::<request::Initialize>(params).await?;
|
let response = this.request::<request::Initialize>(params).await?;
|
||||||
Arc::get_mut(&mut this).unwrap().capabilities = response.capabilities;
|
{
|
||||||
|
let this = Arc::get_mut(&mut this).unwrap();
|
||||||
|
if let Some(info) = response.server_info {
|
||||||
|
this.name = info.name;
|
||||||
|
}
|
||||||
|
this.capabilities = response.capabilities;
|
||||||
|
}
|
||||||
this.notify::<notification::Initialized>(InitializedParams {})?;
|
this.notify::<notification::Initialized>(InitializedParams {})?;
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
@ -355,6 +365,10 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name<'a>(self: &'a Arc<Self>) -> &'a str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
pub fn capabilities<'a>(self: &'a Arc<Self>) -> &'a ServerCapabilities {
|
pub fn capabilities<'a>(self: &'a Arc<Self>) -> &'a ServerCapabilities {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub mod worktree;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||||
use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt};
|
use futures::{future::Shared, Future, FutureExt, StreamExt, TryFutureExt};
|
||||||
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -28,7 +28,6 @@ use rand::prelude::*;
|
||||||
use search::SearchQuery;
|
use search::SearchQuery;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use similar::{ChangeTag, TextDiff};
|
use similar::{ChangeTag, TextDiff};
|
||||||
use smol::block_on;
|
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
|
@ -52,6 +51,8 @@ pub struct Project {
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
|
language_servers: HashMap<(WorktreeId, Arc<str>), Arc<LanguageServer>>,
|
||||||
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
|
started_language_servers: HashMap<(WorktreeId, Arc<str>), Task<Option<Arc<LanguageServer>>>>,
|
||||||
|
language_server_statuses: BTreeMap<usize, LanguageServerStatus>,
|
||||||
|
next_language_server_id: usize,
|
||||||
client: Arc<client::Client>,
|
client: Arc<client::Client>,
|
||||||
user_store: ModelHandle<UserStore>,
|
user_store: ModelHandle<UserStore>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -115,6 +116,33 @@ pub enum Event {
|
||||||
DiagnosticsUpdated(ProjectPath),
|
DiagnosticsUpdated(ProjectPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LanguageServerEvent {
|
||||||
|
WorkStart {
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
|
WorkProgress {
|
||||||
|
token: String,
|
||||||
|
progress: LanguageServerProgress,
|
||||||
|
},
|
||||||
|
WorkEnd {
|
||||||
|
token: String,
|
||||||
|
},
|
||||||
|
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LanguageServerStatus {
|
||||||
|
pub name: String,
|
||||||
|
pub pending_work: BTreeMap<String, LanguageServerProgress>,
|
||||||
|
pending_diagnostic_updates: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LanguageServerProgress {
|
||||||
|
pub message: Option<String>,
|
||||||
|
pub percentage: Option<usize>,
|
||||||
|
pub last_update_at: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
pub struct ProjectPath {
|
pub struct ProjectPath {
|
||||||
pub worktree_id: WorktreeId,
|
pub worktree_id: WorktreeId,
|
||||||
|
@ -203,8 +231,8 @@ impl Project {
|
||||||
client.add_entity_message_handler(Self::handle_add_collaborator);
|
client.add_entity_message_handler(Self::handle_add_collaborator);
|
||||||
client.add_entity_message_handler(Self::handle_buffer_reloaded);
|
client.add_entity_message_handler(Self::handle_buffer_reloaded);
|
||||||
client.add_entity_message_handler(Self::handle_buffer_saved);
|
client.add_entity_message_handler(Self::handle_buffer_saved);
|
||||||
client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updated);
|
client.add_entity_message_handler(Self::handle_start_language_server);
|
||||||
client.add_entity_message_handler(Self::handle_disk_based_diagnostics_updating);
|
client.add_entity_message_handler(Self::handle_update_language_server);
|
||||||
client.add_entity_message_handler(Self::handle_remove_collaborator);
|
client.add_entity_message_handler(Self::handle_remove_collaborator);
|
||||||
client.add_entity_message_handler(Self::handle_register_worktree);
|
client.add_entity_message_handler(Self::handle_register_worktree);
|
||||||
client.add_entity_message_handler(Self::handle_unregister_worktree);
|
client.add_entity_message_handler(Self::handle_unregister_worktree);
|
||||||
|
@ -304,6 +332,8 @@ impl Project {
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
started_language_servers: Default::default(),
|
started_language_servers: Default::default(),
|
||||||
|
language_server_statuses: Default::default(),
|
||||||
|
next_language_server_id: 0,
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -373,6 +403,21 @@ impl Project {
|
||||||
language_servers_with_diagnostics_running: 0,
|
language_servers_with_diagnostics_running: 0,
|
||||||
language_servers: Default::default(),
|
language_servers: Default::default(),
|
||||||
started_language_servers: Default::default(),
|
started_language_servers: Default::default(),
|
||||||
|
language_server_statuses: response
|
||||||
|
.language_servers
|
||||||
|
.into_iter()
|
||||||
|
.map(|server| {
|
||||||
|
(
|
||||||
|
server.id as usize,
|
||||||
|
LanguageServerStatus {
|
||||||
|
name: server.name,
|
||||||
|
pending_work: Default::default(),
|
||||||
|
pending_diagnostic_updates: 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
next_language_server_id: 0,
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
buffer_snapshots: Default::default(),
|
buffer_snapshots: Default::default(),
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
|
@ -1155,92 +1200,71 @@ impl Project {
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
enum LspEvent {
|
|
||||||
DiagnosticsStart,
|
|
||||||
DiagnosticsUpdate(lsp::PublishDiagnosticsParams),
|
|
||||||
DiagnosticsFinish,
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = (worktree_id, language.name());
|
let key = (worktree_id, language.name());
|
||||||
self.started_language_servers
|
self.started_language_servers
|
||||||
.entry(key.clone())
|
.entry(key.clone())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
|
let server_id = post_inc(&mut self.next_language_server_id);
|
||||||
let language_server = self.languages.start_language_server(
|
let language_server = self.languages.start_language_server(
|
||||||
language.clone(),
|
language.clone(),
|
||||||
worktree_path,
|
worktree_path,
|
||||||
self.client.http_client(),
|
self.client.http_client(),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
let rpc = self.client.clone();
|
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
let mut language_server = language_server?.await.log_err()?;
|
let mut language_server = language_server?.await.log_err()?;
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
|
let (language_server_events_tx, language_server_events_rx) =
|
||||||
|
smol::channel::unbounded();
|
||||||
|
|
||||||
let disk_based_sources = language
|
|
||||||
.disk_based_diagnostic_sources()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default();
|
|
||||||
let disk_based_diagnostics_progress_token =
|
|
||||||
language.disk_based_diagnostics_progress_token().cloned();
|
|
||||||
let has_disk_based_diagnostic_progress_token =
|
|
||||||
disk_based_diagnostics_progress_token.is_some();
|
|
||||||
let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
|
|
||||||
|
|
||||||
// Listen for `PublishDiagnostics` notifications.
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
.on_notification::<lsp::notification::PublishDiagnostics, _>({
|
||||||
let diagnostics_tx = diagnostics_tx.clone();
|
let language_server_events_tx = language_server_events_tx.clone();
|
||||||
move |params| {
|
move |params| {
|
||||||
if !has_disk_based_diagnostic_progress_token {
|
language_server_events_tx
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsStart)).ok();
|
.try_send(LanguageServerEvent::DiagnosticsUpdate(params))
|
||||||
}
|
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsUpdate(params)))
|
|
||||||
.ok();
|
.ok();
|
||||||
if !has_disk_based_diagnostic_progress_token {
|
|
||||||
block_on(diagnostics_tx.send(LspEvent::DiagnosticsFinish)).ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
// Listen for `Progress` notifications. Send an event when the language server
|
|
||||||
// transitions between running jobs and not running any jobs.
|
|
||||||
let mut running_jobs_for_this_server: i32 = 0;
|
|
||||||
language_server
|
language_server
|
||||||
.on_notification::<lsp::notification::Progress, _>(move |params| {
|
.on_notification::<lsp::notification::Progress, _>(move |params| {
|
||||||
let token = match params.token {
|
let token = match params.token {
|
||||||
lsp::NumberOrString::Number(_) => None,
|
lsp::NumberOrString::String(token) => token,
|
||||||
lsp::NumberOrString::String(token) => Some(token),
|
lsp::NumberOrString::Number(token) => {
|
||||||
|
log::info!("skipping numeric progress token {}", token);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if token == disk_based_diagnostics_progress_token {
|
|
||||||
match params.value {
|
match params.value {
|
||||||
lsp::ProgressParamsValue::WorkDone(progress) => {
|
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
|
||||||
match progress {
|
|
||||||
lsp::WorkDoneProgress::Begin(_) => {
|
lsp::WorkDoneProgress::Begin(_) => {
|
||||||
running_jobs_for_this_server += 1;
|
language_server_events_tx
|
||||||
if running_jobs_for_this_server == 1 {
|
.try_send(LanguageServerEvent::WorkStart { token })
|
||||||
block_on(
|
|
||||||
diagnostics_tx
|
|
||||||
.send(LspEvent::DiagnosticsStart),
|
|
||||||
)
|
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
lsp::WorkDoneProgress::Report(report) => {
|
||||||
|
language_server_events_tx
|
||||||
|
.try_send(LanguageServerEvent::WorkProgress {
|
||||||
|
token,
|
||||||
|
progress: LanguageServerProgress {
|
||||||
|
message: report.message,
|
||||||
|
percentage: report
|
||||||
|
.percentage
|
||||||
|
.map(|p| p as usize),
|
||||||
|
last_update_at: Instant::now(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
lsp::WorkDoneProgress::End(_) => {
|
lsp::WorkDoneProgress::End(_) => {
|
||||||
running_jobs_for_this_server -= 1;
|
language_server_events_tx
|
||||||
if running_jobs_for_this_server == 0 {
|
.try_send(LanguageServerEvent::WorkEnd { token })
|
||||||
block_on(
|
|
||||||
diagnostics_tx
|
|
||||||
.send(LspEvent::DiagnosticsFinish),
|
|
||||||
)
|
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -1249,43 +1273,14 @@ impl Project {
|
||||||
cx.spawn(|mut cx| {
|
cx.spawn(|mut cx| {
|
||||||
let this = this.downgrade();
|
let this = this.downgrade();
|
||||||
async move {
|
async move {
|
||||||
while let Ok(message) = diagnostics_rx.recv().await {
|
while let Ok(event) = language_server_events_rx.recv().await {
|
||||||
let this = this.upgrade(&cx)?;
|
let this = this.upgrade(&cx)?;
|
||||||
match message {
|
|
||||||
LspEvent::DiagnosticsStart => {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.disk_based_diagnostics_started(cx);
|
this.on_lsp_event(server_id, event, &language, cx)
|
||||||
if let Some(project_id) = this.remote_id() {
|
|
||||||
rpc.send(proto::DiskBasedDiagnosticsUpdating {
|
|
||||||
project_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
LspEvent::DiagnosticsUpdate(mut params) => {
|
// Don't starve the main thread when lots of events arrive all at once.
|
||||||
language.process_diagnostics(&mut params);
|
smol::future::yield_now().await;
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.update_diagnostics(
|
|
||||||
params,
|
|
||||||
&disk_based_sources,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
LspEvent::DiagnosticsFinish => {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.disk_based_diagnostics_finished(cx);
|
|
||||||
if let Some(project_id) = this.remote_id() {
|
|
||||||
rpc.send(proto::DiskBasedDiagnosticsUpdated {
|
|
||||||
project_id,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
@ -1296,6 +1291,26 @@ impl Project {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.language_servers
|
this.language_servers
|
||||||
.insert(key.clone(), language_server.clone());
|
.insert(key.clone(), language_server.clone());
|
||||||
|
this.language_server_statuses.insert(
|
||||||
|
server_id,
|
||||||
|
LanguageServerStatus {
|
||||||
|
name: language_server.name().to_string(),
|
||||||
|
pending_work: Default::default(),
|
||||||
|
pending_diagnostic_updates: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(project_id) = this.remote_id() {
|
||||||
|
this.client
|
||||||
|
.send(proto::StartLanguageServer {
|
||||||
|
project_id,
|
||||||
|
server: Some(proto::LanguageServer {
|
||||||
|
id: server_id as u64,
|
||||||
|
name: language_server.name().to_string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
|
||||||
// Tell the language server about every open buffer in the worktree that matches the language.
|
// Tell the language server about every open buffer in the worktree that matches the language.
|
||||||
for buffer in this.opened_buffers.values() {
|
for buffer in this.opened_buffers.values() {
|
||||||
|
@ -1350,6 +1365,7 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1358,6 +1374,185 @@ impl Project {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_lsp_event(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
event: LanguageServerEvent,
|
||||||
|
language: &Arc<Language>,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
let disk_diagnostics_token = language.disk_based_diagnostics_progress_token();
|
||||||
|
let language_server_status =
|
||||||
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match event {
|
||||||
|
LanguageServerEvent::WorkStart { token } => {
|
||||||
|
if Some(&token) == disk_diagnostics_token {
|
||||||
|
language_server_status.pending_diagnostic_updates += 1;
|
||||||
|
if language_server_status.pending_diagnostic_updates == 1 {
|
||||||
|
self.disk_based_diagnostics_started(cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.on_lsp_work_start(language_server_id, token.clone(), cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::WorkStart(proto::LspWorkStart {
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LanguageServerEvent::WorkProgress { token, progress } => {
|
||||||
|
if Some(&token) != disk_diagnostics_token {
|
||||||
|
self.on_lsp_work_progress(
|
||||||
|
language_server_id,
|
||||||
|
token.clone(),
|
||||||
|
progress.clone(),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::WorkProgress(
|
||||||
|
proto::LspWorkProgress {
|
||||||
|
token,
|
||||||
|
message: progress.message,
|
||||||
|
percentage: progress.percentage.map(|p| p as u32),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LanguageServerEvent::WorkEnd { token } => {
|
||||||
|
if Some(&token) == disk_diagnostics_token {
|
||||||
|
language_server_status.pending_diagnostic_updates -= 1;
|
||||||
|
if language_server_status.pending_diagnostic_updates == 0 {
|
||||||
|
self.disk_based_diagnostics_finished(cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.on_lsp_work_end(language_server_id, token.clone(), cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::WorkEnd(proto::LspWorkEnd {
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LanguageServerEvent::DiagnosticsUpdate(mut params) => {
|
||||||
|
language.process_diagnostics(&mut params);
|
||||||
|
|
||||||
|
if disk_diagnostics_token.is_none() {
|
||||||
|
self.disk_based_diagnostics_started(cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdating {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.update_diagnostics(
|
||||||
|
params,
|
||||||
|
language
|
||||||
|
.disk_based_diagnostic_sources()
|
||||||
|
.unwrap_or(&Default::default()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
if disk_diagnostics_token.is_none() {
|
||||||
|
self.disk_based_diagnostics_finished(cx);
|
||||||
|
self.broadcast_language_server_update(
|
||||||
|
language_server_id,
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(
|
||||||
|
proto::LspDiskBasedDiagnosticsUpdated {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_lsp_work_start(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
token: String,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
|
status.pending_work.insert(
|
||||||
|
token,
|
||||||
|
LanguageServerProgress {
|
||||||
|
message: None,
|
||||||
|
percentage: None,
|
||||||
|
last_update_at: Instant::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_lsp_work_progress(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
token: String,
|
||||||
|
progress: LanguageServerProgress,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
|
status.pending_work.insert(token, progress);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_lsp_work_end(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: usize,
|
||||||
|
token: String,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(status) = self.language_server_statuses.get_mut(&language_server_id) {
|
||||||
|
status.pending_work.remove(&token);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast_language_server_update(
|
||||||
|
&self,
|
||||||
|
language_server_id: usize,
|
||||||
|
event: proto::update_language_server::Variant,
|
||||||
|
) {
|
||||||
|
if let Some(project_id) = self.remote_id() {
|
||||||
|
self.client
|
||||||
|
.send(proto::UpdateLanguageServer {
|
||||||
|
project_id,
|
||||||
|
language_server_id: language_server_id as u64,
|
||||||
|
variant: Some(event),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn language_server_statuses(
|
||||||
|
&self,
|
||||||
|
) -> impl DoubleEndedIterator<Item = &LanguageServerStatus> {
|
||||||
|
self.language_server_statuses.values()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: lsp::PublishDiagnosticsParams,
|
params: lsp::PublishDiagnosticsParams,
|
||||||
|
@ -3096,23 +3291,76 @@ impl Project {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_disk_based_diagnostics_updating(
|
async fn handle_start_language_server(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
_: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
|
envelope: TypedEnvelope<proto::StartLanguageServer>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_started(cx));
|
let server = envelope
|
||||||
|
.payload
|
||||||
|
.server
|
||||||
|
.ok_or_else(|| anyhow!("invalid server"))?;
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.language_server_statuses.insert(
|
||||||
|
server.id as usize,
|
||||||
|
LanguageServerStatus {
|
||||||
|
name: server.name,
|
||||||
|
pending_work: Default::default(),
|
||||||
|
pending_diagnostic_updates: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_disk_based_diagnostics_updated(
|
async fn handle_update_language_server(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
_: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
|
envelope: TypedEnvelope<proto::UpdateLanguageServer>,
|
||||||
_: Arc<Client>,
|
_: Arc<Client>,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let language_server_id = envelope.payload.language_server_id as usize;
|
||||||
|
match envelope
|
||||||
|
.payload
|
||||||
|
.variant
|
||||||
|
.ok_or_else(|| anyhow!("invalid variant"))?
|
||||||
|
{
|
||||||
|
proto::update_language_server::Variant::WorkStart(payload) => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.on_lsp_work_start(language_server_id, payload.token, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
proto::update_language_server::Variant::WorkProgress(payload) => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.on_lsp_work_progress(
|
||||||
|
language_server_id,
|
||||||
|
payload.token,
|
||||||
|
LanguageServerProgress {
|
||||||
|
message: payload.message,
|
||||||
|
percentage: payload.percentage.map(|p| p as usize),
|
||||||
|
last_update_at: Instant::now(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
proto::update_language_server::Variant::WorkEnd(payload) => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.on_lsp_work_end(language_server_id, payload.token, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdating(_) => {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.disk_based_diagnostics_started(cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
proto::update_language_server::Variant::DiskBasedDiagnosticsUpdated(_) => {
|
||||||
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
|
this.update(&mut cx, |this, cx| this.disk_based_diagnostics_finished(cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,8 @@ message Envelope {
|
||||||
UnregisterWorktree unregister_worktree = 29;
|
UnregisterWorktree unregister_worktree = 29;
|
||||||
UpdateWorktree update_worktree = 31;
|
UpdateWorktree update_worktree = 31;
|
||||||
UpdateDiagnosticSummary update_diagnostic_summary = 32;
|
UpdateDiagnosticSummary update_diagnostic_summary = 32;
|
||||||
DiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 33;
|
StartLanguageServer start_language_server = 33;
|
||||||
DiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 34;
|
UpdateLanguageServer update_language_server = 34;
|
||||||
|
|
||||||
OpenBuffer open_buffer = 35;
|
OpenBuffer open_buffer = 35;
|
||||||
OpenBufferResponse open_buffer_response = 36;
|
OpenBufferResponse open_buffer_response = 36;
|
||||||
|
@ -122,6 +122,7 @@ message JoinProjectResponse {
|
||||||
uint32 replica_id = 1;
|
uint32 replica_id = 1;
|
||||||
repeated Worktree worktrees = 2;
|
repeated Worktree worktrees = 2;
|
||||||
repeated Collaborator collaborators = 3;
|
repeated Collaborator collaborators = 3;
|
||||||
|
repeated LanguageServer language_servers = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LeaveProject {
|
message LeaveProject {
|
||||||
|
@ -410,6 +411,16 @@ message LocalTimestamp {
|
||||||
uint32 value = 2;
|
uint32 value = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LanguageServer {
|
||||||
|
uint64 id = 1;
|
||||||
|
string name = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartLanguageServer {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
LanguageServer server = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message UpdateDiagnosticSummary {
|
message UpdateDiagnosticSummary {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 worktree_id = 2;
|
||||||
|
@ -424,14 +435,36 @@ message DiagnosticSummary {
|
||||||
uint32 hint_count = 5;
|
uint32 hint_count = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DiskBasedDiagnosticsUpdating {
|
message UpdateLanguageServer {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
|
uint64 language_server_id = 2;
|
||||||
|
oneof variant {
|
||||||
|
LspWorkStart work_start = 3;
|
||||||
|
LspWorkProgress work_progress = 4;
|
||||||
|
LspWorkEnd work_end = 5;
|
||||||
|
LspDiskBasedDiagnosticsUpdating disk_based_diagnostics_updating = 6;
|
||||||
|
LspDiskBasedDiagnosticsUpdated disk_based_diagnostics_updated = 7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message DiskBasedDiagnosticsUpdated {
|
message LspWorkStart {
|
||||||
uint64 project_id = 1;
|
string token = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LspWorkProgress {
|
||||||
|
string token = 1;
|
||||||
|
optional string message = 2;
|
||||||
|
optional uint32 percentage = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspWorkEnd {
|
||||||
|
string token = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LspDiskBasedDiagnosticsUpdating {}
|
||||||
|
|
||||||
|
message LspDiskBasedDiagnosticsUpdated {}
|
||||||
|
|
||||||
message GetChannels {}
|
message GetChannels {}
|
||||||
|
|
||||||
message GetChannelsResponse {
|
message GetChannelsResponse {
|
||||||
|
|
|
@ -146,8 +146,6 @@ messages!(
|
||||||
(BufferReloaded, Foreground),
|
(BufferReloaded, Foreground),
|
||||||
(BufferSaved, Foreground),
|
(BufferSaved, Foreground),
|
||||||
(ChannelMessageSent, Foreground),
|
(ChannelMessageSent, Foreground),
|
||||||
(DiskBasedDiagnosticsUpdated, Background),
|
|
||||||
(DiskBasedDiagnosticsUpdating, Background),
|
|
||||||
(Error, Foreground),
|
(Error, Foreground),
|
||||||
(FormatBuffers, Foreground),
|
(FormatBuffers, Foreground),
|
||||||
(FormatBuffersResponse, Foreground),
|
(FormatBuffersResponse, Foreground),
|
||||||
|
@ -173,6 +171,8 @@ messages!(
|
||||||
(JoinChannelResponse, Foreground),
|
(JoinChannelResponse, Foreground),
|
||||||
(JoinProject, Foreground),
|
(JoinProject, Foreground),
|
||||||
(JoinProjectResponse, Foreground),
|
(JoinProjectResponse, Foreground),
|
||||||
|
(StartLanguageServer, Foreground),
|
||||||
|
(UpdateLanguageServer, Foreground),
|
||||||
(LeaveChannel, Foreground),
|
(LeaveChannel, Foreground),
|
||||||
(LeaveProject, Foreground),
|
(LeaveProject, Foreground),
|
||||||
(OpenBuffer, Background),
|
(OpenBuffer, Background),
|
||||||
|
@ -246,8 +246,6 @@ entity_messages!(
|
||||||
ApplyCompletionAdditionalEdits,
|
ApplyCompletionAdditionalEdits,
|
||||||
BufferReloaded,
|
BufferReloaded,
|
||||||
BufferSaved,
|
BufferSaved,
|
||||||
DiskBasedDiagnosticsUpdated,
|
|
||||||
DiskBasedDiagnosticsUpdating,
|
|
||||||
FormatBuffers,
|
FormatBuffers,
|
||||||
GetCodeActions,
|
GetCodeActions,
|
||||||
GetCompletions,
|
GetCompletions,
|
||||||
|
@ -264,11 +262,13 @@ entity_messages!(
|
||||||
RemoveProjectCollaborator,
|
RemoveProjectCollaborator,
|
||||||
SaveBuffer,
|
SaveBuffer,
|
||||||
SearchProject,
|
SearchProject,
|
||||||
|
StartLanguageServer,
|
||||||
UnregisterWorktree,
|
UnregisterWorktree,
|
||||||
UnshareProject,
|
UnshareProject,
|
||||||
UpdateBuffer,
|
UpdateBuffer,
|
||||||
UpdateBufferFile,
|
UpdateBufferFile,
|
||||||
UpdateDiagnosticSummary,
|
UpdateDiagnosticSummary,
|
||||||
|
UpdateLanguageServer,
|
||||||
RegisterWorktree,
|
RegisterWorktree,
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
);
|
);
|
||||||
|
|
|
@ -83,9 +83,9 @@ impl Server {
|
||||||
.add_request_handler(Server::register_worktree)
|
.add_request_handler(Server::register_worktree)
|
||||||
.add_message_handler(Server::unregister_worktree)
|
.add_message_handler(Server::unregister_worktree)
|
||||||
.add_request_handler(Server::update_worktree)
|
.add_request_handler(Server::update_worktree)
|
||||||
|
.add_message_handler(Server::start_language_server)
|
||||||
|
.add_message_handler(Server::update_language_server)
|
||||||
.add_message_handler(Server::update_diagnostic_summary)
|
.add_message_handler(Server::update_diagnostic_summary)
|
||||||
.add_message_handler(Server::disk_based_diagnostics_updating)
|
|
||||||
.add_message_handler(Server::disk_based_diagnostics_updated)
|
|
||||||
.add_request_handler(Server::forward_project_request::<proto::GetDefinition>)
|
.add_request_handler(Server::forward_project_request::<proto::GetDefinition>)
|
||||||
.add_request_handler(Server::forward_project_request::<proto::GetReferences>)
|
.add_request_handler(Server::forward_project_request::<proto::GetReferences>)
|
||||||
.add_request_handler(Server::forward_project_request::<proto::SearchProject>)
|
.add_request_handler(Server::forward_project_request::<proto::SearchProject>)
|
||||||
|
@ -386,6 +386,7 @@ impl Server {
|
||||||
worktrees,
|
worktrees,
|
||||||
replica_id: joined.replica_id as u32,
|
replica_id: joined.replica_id as u32,
|
||||||
collaborators,
|
collaborators,
|
||||||
|
language_servers: joined.project.language_servers.clone(),
|
||||||
};
|
};
|
||||||
let connection_ids = joined.project.connection_ids();
|
let connection_ids = joined.project.connection_ids();
|
||||||
let contact_user_ids = joined.project.authorized_user_ids();
|
let contact_user_ids = joined.project.authorized_user_ids();
|
||||||
|
@ -535,13 +536,19 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disk_based_diagnostics_updating(
|
async fn start_language_server(
|
||||||
self: Arc<Server>,
|
mut self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdating>,
|
request: TypedEnvelope<proto::StartLanguageServer>,
|
||||||
) -> tide::Result<()> {
|
) -> tide::Result<()> {
|
||||||
let receiver_ids = self
|
let receiver_ids = self.state_mut().start_language_server(
|
||||||
.state()
|
request.payload.project_id,
|
||||||
.project_connection_ids(request.payload.project_id, request.sender_id)?;
|
request.sender_id,
|
||||||
|
request
|
||||||
|
.payload
|
||||||
|
.server
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("invalid language server"))?,
|
||||||
|
)?;
|
||||||
broadcast(request.sender_id, receiver_ids, |connection_id| {
|
broadcast(request.sender_id, receiver_ids, |connection_id| {
|
||||||
self.peer
|
self.peer
|
||||||
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
.forward_send(request.sender_id, connection_id, request.payload.clone())
|
||||||
|
@ -549,9 +556,9 @@ impl Server {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn disk_based_diagnostics_updated(
|
async fn update_language_server(
|
||||||
self: Arc<Server>,
|
self: Arc<Server>,
|
||||||
request: TypedEnvelope<proto::DiskBasedDiagnosticsUpdated>,
|
request: TypedEnvelope<proto::UpdateLanguageServer>,
|
||||||
) -> tide::Result<()> {
|
) -> tide::Result<()> {
|
||||||
let receiver_ids = self
|
let receiver_ids = self
|
||||||
.state()
|
.state()
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub struct Project {
|
||||||
pub host_user_id: UserId,
|
pub host_user_id: UserId,
|
||||||
pub share: Option<ProjectShare>,
|
pub share: Option<ProjectShare>,
|
||||||
pub worktrees: HashMap<u64, Worktree>,
|
pub worktrees: HashMap<u64, Worktree>,
|
||||||
|
pub language_servers: Vec<proto::LanguageServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Worktree {
|
pub struct Worktree {
|
||||||
|
@ -240,6 +241,7 @@ impl Store {
|
||||||
host_user_id,
|
host_user_id,
|
||||||
share: None,
|
share: None,
|
||||||
worktrees: Default::default(),
|
worktrees: Default::default(),
|
||||||
|
language_servers: Default::default(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.next_project_id += 1;
|
self.next_project_id += 1;
|
||||||
|
@ -438,6 +440,24 @@ impl Store {
|
||||||
Err(anyhow!("no such worktree"))?
|
Err(anyhow!("no such worktree"))?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_language_server(
|
||||||
|
&mut self,
|
||||||
|
project_id: u64,
|
||||||
|
connection_id: ConnectionId,
|
||||||
|
language_server: proto::LanguageServer,
|
||||||
|
) -> tide::Result<Vec<ConnectionId>> {
|
||||||
|
let project = self
|
||||||
|
.projects
|
||||||
|
.get_mut(&project_id)
|
||||||
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
|
if project.host_connection_id == connection_id {
|
||||||
|
project.language_servers.push(language_server);
|
||||||
|
return Ok(project.connection_ids());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!("no such project"))?
|
||||||
|
}
|
||||||
|
|
||||||
pub fn join_project(
|
pub fn join_project(
|
||||||
&mut self,
|
&mut self,
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
|
|
|
@ -24,6 +24,7 @@ futures = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
postage = { version = "0.4.1", features = ["futures-traits"] }
|
postage = { version = "0.4.1", features = ["futures-traits"] }
|
||||||
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
use crate::{ItemViewHandle, Settings, StatusItemView};
|
use crate::{ItemViewHandle, Settings, StatusItemView};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
use gpui::AppContext;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action, elements::*, platform::CursorStyle, Entity, MutableAppContext, RenderContext, View,
|
action, elements::*, platform::CursorStyle, Entity, ModelHandle, MutableAppContext,
|
||||||
ViewContext,
|
RenderContext, View, ViewContext,
|
||||||
};
|
};
|
||||||
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
use language::{LanguageRegistry, LanguageServerBinaryStatus};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use project::{LanguageServerProgress, Project};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::cmp::Reverse;
|
||||||
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
action!(DismissErrorMessage);
|
action!(DismissErrorMessage);
|
||||||
|
@ -15,6 +20,7 @@ pub struct LspStatus {
|
||||||
checking_for_update: Vec<String>,
|
checking_for_update: Vec<String>,
|
||||||
downloading: Vec<String>,
|
downloading: Vec<String>,
|
||||||
failed: Vec<String>,
|
failed: Vec<String>,
|
||||||
|
project: ModelHandle<Project>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
@ -23,6 +29,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
|
||||||
impl LspStatus {
|
impl LspStatus {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
project: &ModelHandle<Project>,
|
||||||
languages: Arc<LanguageRegistry>,
|
languages: Arc<LanguageRegistry>,
|
||||||
settings_rx: watch::Receiver<Settings>,
|
settings_rx: watch::Receiver<Settings>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -62,11 +69,14 @@ impl LspStatus {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
cx.observe(project, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
settings_rx,
|
settings_rx,
|
||||||
checking_for_update: Default::default(),
|
checking_for_update: Default::default(),
|
||||||
downloading: Default::default(),
|
downloading: Default::default(),
|
||||||
failed: Default::default(),
|
failed: Default::default(),
|
||||||
|
project: project.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +84,30 @@ impl LspStatus {
|
||||||
self.failed.clear();
|
self.failed.clear();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pending_language_server_work<'a>(
|
||||||
|
&self,
|
||||||
|
cx: &'a AppContext,
|
||||||
|
) -> impl Iterator<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
|
||||||
|
self.project
|
||||||
|
.read(cx)
|
||||||
|
.language_server_statuses()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|status| {
|
||||||
|
if status.pending_work.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let mut pending_work = status
|
||||||
|
.pending_work
|
||||||
|
.iter()
|
||||||
|
.map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
|
||||||
|
.collect::<SmallVec<[_; 4]>>();
|
||||||
|
pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
|
||||||
|
Some(pending_work)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for LspStatus {
|
impl Entity for LspStatus {
|
||||||
|
@ -87,7 +121,29 @@ impl View for LspStatus {
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
let theme = &self.settings_rx.borrow().theme;
|
let theme = &self.settings_rx.borrow().theme;
|
||||||
if !self.downloading.is_empty() {
|
|
||||||
|
let mut pending_work = self.pending_language_server_work(cx);
|
||||||
|
if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
|
||||||
|
let mut message = lang_server_name.to_string();
|
||||||
|
|
||||||
|
message.push_str(": ");
|
||||||
|
if let Some(progress_message) = progress.message.as_ref() {
|
||||||
|
message.push_str(progress_message);
|
||||||
|
} else {
|
||||||
|
message.push_str(progress_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(percentage) = progress.percentage {
|
||||||
|
write!(&mut message, " ({}%)", percentage).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let additional_work_count = pending_work.count();
|
||||||
|
if additional_work_count > 0 {
|
||||||
|
write!(&mut message, " + {} more", additional_work_count).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Label::new(message, theme.workspace.status_bar.lsp_message.clone()).boxed()
|
||||||
|
} else if !self.downloading.is_empty() {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
"Downloading {} language server{}...",
|
"Downloading {} language server{}...",
|
||||||
|
@ -112,6 +168,7 @@ impl View for LspStatus {
|
||||||
)
|
)
|
||||||
.boxed()
|
.boxed()
|
||||||
} else if !self.failed.is_empty() {
|
} else if !self.failed.is_empty() {
|
||||||
|
drop(pending_work);
|
||||||
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
MouseEventHandler::new::<Self, _, _>(0, cx, |_, _| {
|
||||||
Label::new(
|
Label::new(
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -6,9 +6,9 @@ use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
keymap::Binding,
|
keymap::Binding,
|
||||||
platform::CursorStyle,
|
platform::{CursorStyle, NavigationDirection},
|
||||||
AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext,
|
AnyViewHandle, Entity, MutableAppContext, Quad, RenderContext, Task, View, ViewContext,
|
||||||
ViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
|
@ -27,8 +27,8 @@ action!(ActivateNextItem);
|
||||||
action!(CloseActiveItem);
|
action!(CloseActiveItem);
|
||||||
action!(CloseInactiveItems);
|
action!(CloseInactiveItems);
|
||||||
action!(CloseItem, usize);
|
action!(CloseItem, usize);
|
||||||
action!(GoBack);
|
action!(GoBack, Option<WeakViewHandle<Pane>>);
|
||||||
action!(GoForward);
|
action!(GoForward, Option<WeakViewHandle<Pane>>);
|
||||||
|
|
||||||
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||||
|
|
||||||
|
@ -54,11 +54,27 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
cx.add_action(|pane: &mut Pane, action: &Split, cx| {
|
||||||
pane.split(action.0, cx);
|
pane.split(action.0, cx);
|
||||||
});
|
});
|
||||||
cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
|
cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
|
||||||
Pane::go_back(workspace, cx).detach();
|
Pane::go_back(
|
||||||
|
workspace,
|
||||||
|
action
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|weak_handle| weak_handle.upgrade(cx)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
});
|
});
|
||||||
cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
|
cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
|
||||||
Pane::go_forward(workspace, cx).detach();
|
Pane::go_forward(
|
||||||
|
workspace,
|
||||||
|
action
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|weak_handle| weak_handle.upgrade(cx)),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
|
@ -70,8 +86,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
|
Binding::new("cmd-k down", Split(SplitDirection::Down), Some("Pane")),
|
||||||
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
|
Binding::new("cmd-k left", Split(SplitDirection::Left), Some("Pane")),
|
||||||
Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
|
Binding::new("cmd-k right", Split(SplitDirection::Right), Some("Pane")),
|
||||||
Binding::new("ctrl--", GoBack, Some("Pane")),
|
Binding::new("ctrl--", GoBack(None), Some("Pane")),
|
||||||
Binding::new("shift-ctrl-_", GoForward, Some("Pane")),
|
Binding::new("shift-ctrl-_", GoForward(None), Some("Pane")),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,19 +179,27 @@ impl Pane {
|
||||||
cx.emit(Event::Activate);
|
cx.emit(Event::Activate);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_back(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Task<()> {
|
pub fn go_back(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
pane: Option<ViewHandle<Pane>>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Task<()> {
|
||||||
Self::navigate_history(
|
Self::navigate_history(
|
||||||
workspace,
|
workspace,
|
||||||
workspace.active_pane().clone(),
|
pane.unwrap_or_else(|| workspace.active_pane().clone()),
|
||||||
NavigationMode::GoingBack,
|
NavigationMode::GoingBack,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_forward(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Task<()> {
|
pub fn go_forward(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
pane: Option<ViewHandle<Pane>>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) -> Task<()> {
|
||||||
Self::navigate_history(
|
Self::navigate_history(
|
||||||
workspace,
|
workspace,
|
||||||
workspace.active_pane().clone(),
|
pane.unwrap_or_else(|| workspace.active_pane().clone()),
|
||||||
NavigationMode::GoingForward,
|
NavigationMode::GoingForward,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -187,6 +211,8 @@ impl Pane {
|
||||||
mode: NavigationMode,
|
mode: NavigationMode,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
|
workspace.activate_pane(pane.clone(), cx);
|
||||||
|
|
||||||
let to_load = pane.update(cx, |pane, cx| {
|
let to_load = pane.update(cx, |pane, cx| {
|
||||||
// Retrieve the weak item handle from the history.
|
// Retrieve the weak item handle from the history.
|
||||||
let entry = pane.nav_history.borrow_mut().pop(mode)?;
|
let entry = pane.nav_history.borrow_mut().pop(mode)?;
|
||||||
|
@ -634,7 +660,9 @@ impl View for Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
|
||||||
if let Some(active_item) = self.active_item() {
|
let this = cx.handle();
|
||||||
|
|
||||||
|
EventHandler::new(if let Some(active_item) = self.active_item() {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(self.render_tabs(cx))
|
.with_child(self.render_tabs(cx))
|
||||||
.with_children(
|
.with_children(
|
||||||
|
@ -643,10 +671,20 @@ impl View for Pane {
|
||||||
.map(|view| ChildView::new(view).boxed()),
|
.map(|view| ChildView::new(view).boxed()),
|
||||||
)
|
)
|
||||||
.with_child(ChildView::new(active_item).flexible(1., true).boxed())
|
.with_child(ChildView::new(active_item).flexible(1., true).boxed())
|
||||||
.named("pane")
|
.boxed()
|
||||||
} else {
|
} else {
|
||||||
Empty::new().named("pane")
|
Empty::new().boxed()
|
||||||
|
})
|
||||||
|
.on_navigate_mouse_down(move |direction, cx| {
|
||||||
|
let this = this.clone();
|
||||||
|
match direction {
|
||||||
|
NavigationDirection::Back => cx.dispatch_action(GoBack(Some(this))),
|
||||||
|
NavigationDirection::Forward => cx.dispatch_action(GoForward(Some(this))),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.named("pane")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
|
|
@ -1511,6 +1511,8 @@ fn open(action: &Open, cx: &mut MutableAppContext) {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
|
||||||
|
|
||||||
pub fn open_paths(
|
pub fn open_paths(
|
||||||
abs_paths: &[PathBuf],
|
abs_paths: &[PathBuf],
|
||||||
app_state: &Arc<AppState>,
|
app_state: &Arc<AppState>,
|
||||||
|
@ -1537,7 +1539,7 @@ pub fn open_paths(
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace = existing.unwrap_or_else(|| {
|
let workspace = existing.unwrap_or_else(|| {
|
||||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
let project = Project::local(
|
let project = Project::local(
|
||||||
app_state.client.clone(),
|
app_state.client.clone(),
|
||||||
app_state.user_store.clone(),
|
app_state.user_store.clone(),
|
||||||
|
@ -1546,8 +1548,9 @@ pub fn open_paths(
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
(app_state.build_workspace)(project, &app_state, cx)
|
(app_state.build_workspace)(project, &app_state, cx)
|
||||||
})
|
});
|
||||||
.1
|
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||||
|
workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
|
let task = workspace.update(cx, |workspace, cx| workspace.open_paths(abs_paths, cx));
|
||||||
|
@ -1581,12 +1584,13 @@ pub fn join_project(
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let (_, workspace) = cx.update(|cx| {
|
Ok(cx.update(|cx| {
|
||||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
let (_, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
|
||||||
(app_state.build_workspace)(project, &app_state, cx)
|
(app_state.build_workspace)(project, &app_state, cx)
|
||||||
})
|
|
||||||
});
|
});
|
||||||
Ok(workspace)
|
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||||
|
workspace
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1601,5 +1605,6 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
);
|
);
|
||||||
(app_state.build_workspace)(project, &app_state, cx)
|
(app_state.build_workspace)(project, &app_state, cx)
|
||||||
});
|
});
|
||||||
|
cx.emit_global(WorkspaceCreated(workspace.downgrade()));
|
||||||
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
|
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ pub fn build_workspace(
|
||||||
});
|
});
|
||||||
let lsp_status = cx.add_view(|cx| {
|
let lsp_status = cx.add_view(|cx| {
|
||||||
workspace::lsp_status::LspStatus::new(
|
workspace::lsp_status::LspStatus::new(
|
||||||
|
workspace.project(),
|
||||||
app_state.languages.clone(),
|
app_state.languages.clone(),
|
||||||
app_state.settings.clone(),
|
app_state.settings.clone(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -747,44 +748,58 @@ mod tests {
|
||||||
(file3.clone(), DisplayPoint::new(15, 0))
|
(file3.clone(), DisplayPoint::new(15, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file3.clone(), DisplayPoint::new(0, 0))
|
(file3.clone(), DisplayPoint::new(0, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file2.clone(), DisplayPoint::new(0, 0))
|
(file2.clone(), DisplayPoint::new(0, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file1.clone(), DisplayPoint::new(10, 0))
|
(file1.clone(), DisplayPoint::new(10, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file1.clone(), DisplayPoint::new(0, 0))
|
(file1.clone(), DisplayPoint::new(0, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Go back one more time and ensure we don't navigate past the first item in the history.
|
// Go back one more time and ensure we don't navigate past the first item in the history.
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file1.clone(), DisplayPoint::new(0, 0))
|
(file1.clone(), DisplayPoint::new(0, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file1.clone(), DisplayPoint::new(10, 0))
|
(file1.clone(), DisplayPoint::new(10, 0))
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file2.clone(), DisplayPoint::new(0, 0))
|
(file2.clone(), DisplayPoint::new(0, 0))
|
||||||
|
@ -798,7 +813,9 @@ mod tests {
|
||||||
.update(cx, |pane, cx| pane.close_item(editor3.id(), cx));
|
.update(cx, |pane, cx| pane.close_item(editor3.id(), cx));
|
||||||
drop(editor3);
|
drop(editor3);
|
||||||
});
|
});
|
||||||
workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file3.clone(), DisplayPoint::new(0, 0))
|
(file3.clone(), DisplayPoint::new(0, 0))
|
||||||
|
@ -818,12 +835,16 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
workspace.update(cx, |w, cx| Pane::go_back(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_back(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file1.clone(), DisplayPoint::new(10, 0))
|
(file1.clone(), DisplayPoint::new(10, 0))
|
||||||
);
|
);
|
||||||
workspace.update(cx, |w, cx| Pane::go_forward(w, cx)).await;
|
workspace
|
||||||
|
.update(cx, |w, cx| Pane::go_forward(w, None, cx))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
active_location(&workspace, cx),
|
active_location(&workspace, cx),
|
||||||
(file3.clone(), DisplayPoint::new(0, 0))
|
(file3.clone(), DisplayPoint::new(0, 0))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue