Merge branch 'master' into rescan
This commit is contained in:
@@ -69,6 +69,28 @@ impl Anchor {
|
||||
.then_with(|| self_bias.cmp(other_bias)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bias_left(&self, buffer: &Buffer) -> Result<Anchor> {
|
||||
match self {
|
||||
Anchor::Start
|
||||
| Anchor::Middle {
|
||||
bias: AnchorBias::Left,
|
||||
..
|
||||
} => Ok(self.clone()),
|
||||
_ => buffer.anchor_before(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bias_right(&self, buffer: &Buffer) -> Result<Anchor> {
|
||||
match self {
|
||||
Anchor::End
|
||||
| Anchor::Middle {
|
||||
bias: AnchorBias::Right,
|
||||
..
|
||||
} => Ok(self.clone()),
|
||||
_ => buffer.anchor_after(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnchorRangeExt {
|
||||
|
||||
@@ -563,10 +563,13 @@ impl Buffer {
|
||||
self.chars().collect()
|
||||
}
|
||||
|
||||
pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> Result<String> {
|
||||
pub fn text_for_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> Result<impl 'a + Iterator<Item = char>> {
|
||||
let start = range.start.to_offset(self)?;
|
||||
let end = range.end.to_offset(self)?;
|
||||
Ok(self.chars_at(start)?.take(end - start).collect())
|
||||
Ok(self.chars_at(start)?.take(end - start))
|
||||
}
|
||||
|
||||
pub fn chars(&self) -> CharIter {
|
||||
@@ -2261,6 +2264,12 @@ impl ToOffset for Anchor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToOffset for &'a Anchor {
|
||||
fn to_offset(&self, buffer: &Buffer) -> Result<usize> {
|
||||
Ok(buffer.summary_for_anchor(self)?.chars)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToPoint {
|
||||
fn to_point(&self, buffer: &Buffer) -> Result<Point>;
|
||||
}
|
||||
@@ -2470,13 +2479,9 @@ mod tests {
|
||||
let old_len = old_range.end - old_range.start;
|
||||
let new_len = new_range.end - new_range.start;
|
||||
let old_start = (old_range.start as isize + delta) as usize;
|
||||
|
||||
let new_text: String = buffer.text_for_range(new_range).unwrap().collect();
|
||||
old_buffer
|
||||
.edit(
|
||||
Some(old_start..old_start + old_len),
|
||||
buffer.text_for_range(new_range).unwrap(),
|
||||
None,
|
||||
)
|
||||
.edit(Some(old_start..old_start + old_len), new_text, None)
|
||||
.unwrap();
|
||||
|
||||
delta += new_len as isize - old_len as isize;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
use super::{
|
||||
buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point,
|
||||
Selection, SelectionSetId, ToOffset,
|
||||
Selection, SelectionSetId, ToOffset, ToPoint,
|
||||
};
|
||||
use crate::{settings::Settings, watch, workspace};
|
||||
use anyhow::Result;
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use gpui::{
|
||||
fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, Element,
|
||||
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
|
||||
fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, ClipboardItem,
|
||||
Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use gpui::{geometry::vector::Vector2F, TextLayoutCache};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use smol::Timer;
|
||||
use std::{
|
||||
cmp::{self, Ordering},
|
||||
fmt::Write,
|
||||
iter::FromIterator,
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -28,6 +30,9 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_bindings(vec![
|
||||
Binding::new("backspace", "buffer:backspace", Some("BufferView")),
|
||||
Binding::new("enter", "buffer:newline", Some("BufferView")),
|
||||
Binding::new("cmd-x", "buffer:cut", Some("BufferView")),
|
||||
Binding::new("cmd-c", "buffer:copy", Some("BufferView")),
|
||||
Binding::new("cmd-v", "buffer:paste", Some("BufferView")),
|
||||
Binding::new("cmd-z", "buffer:undo", Some("BufferView")),
|
||||
Binding::new("cmd-shift-Z", "buffer:redo", Some("BufferView")),
|
||||
Binding::new("up", "buffer:move_up", Some("BufferView")),
|
||||
@@ -54,6 +59,9 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_action("buffer:insert", BufferView::insert);
|
||||
app.add_action("buffer:newline", BufferView::newline);
|
||||
app.add_action("buffer:backspace", BufferView::backspace);
|
||||
app.add_action("buffer:cut", BufferView::cut);
|
||||
app.add_action("buffer:copy", BufferView::copy);
|
||||
app.add_action("buffer:paste", BufferView::paste);
|
||||
app.add_action("buffer:undo", BufferView::undo);
|
||||
app.add_action("buffer:redo", BufferView::redo);
|
||||
app.add_action("buffer:move_up", BufferView::move_up);
|
||||
@@ -102,6 +110,12 @@ pub struct BufferView {
|
||||
single_line: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ClipboardSelection {
|
||||
len: usize,
|
||||
is_entire_line: bool,
|
||||
}
|
||||
|
||||
impl BufferView {
|
||||
pub fn single_line(settings: watch::Receiver<Settings>, ctx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = ctx.add_model(|_| Buffer::new(0, String::new()));
|
||||
@@ -354,6 +368,25 @@ impl BufferView {
|
||||
|
||||
#[cfg(test)]
|
||||
fn select_ranges<'a, T>(&mut self, ranges: T, ctx: &mut ViewContext<Self>) -> Result<()>
|
||||
where
|
||||
T: IntoIterator<Item = &'a Range<usize>>,
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let mut selections = Vec::new();
|
||||
for range in ranges {
|
||||
selections.push(Selection {
|
||||
start: buffer.anchor_before(range.start)?,
|
||||
end: buffer.anchor_before(range.end)?,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
});
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn select_display_ranges<'a, T>(&mut self, ranges: T, ctx: &mut ViewContext<Self>) -> Result<()>
|
||||
where
|
||||
T: IntoIterator<Item = &'a Range<DisplayPoint>>,
|
||||
{
|
||||
@@ -361,7 +394,7 @@ impl BufferView {
|
||||
let mut selections = Vec::new();
|
||||
for range in ranges {
|
||||
selections.push(Selection {
|
||||
start: map.anchor_after(range.start, Bias::Left, ctx.as_ref())?,
|
||||
start: map.anchor_before(range.start, Bias::Left, ctx.as_ref())?,
|
||||
end: map.anchor_before(range.end, Bias::Left, ctx.as_ref())?,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
@@ -454,6 +487,133 @@ impl BufferView {
|
||||
self.end_transaction(ctx);
|
||||
}
|
||||
|
||||
pub fn cut(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
self.start_transaction(ctx);
|
||||
let mut text = String::new();
|
||||
let mut selections = self.selections(ctx.as_ref()).to_vec();
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
{
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let max_point = buffer.max_point();
|
||||
for selection in &mut selections {
|
||||
let mut start = selection.start.to_point(buffer).expect("invalid start");
|
||||
let mut end = selection.end.to_point(buffer).expect("invalid end");
|
||||
let is_entire_line = start == end;
|
||||
if is_entire_line {
|
||||
start = Point::new(start.row, 0);
|
||||
end = cmp::min(max_point, Point::new(start.row + 1, 0));
|
||||
selection.start = buffer.anchor_before(start).unwrap();
|
||||
selection.end = buffer.anchor_before(end).unwrap();
|
||||
}
|
||||
let mut len = 0;
|
||||
for ch in buffer.text_for_range(start..end).unwrap() {
|
||||
text.push(ch);
|
||||
len += 1;
|
||||
}
|
||||
clipboard_selections.push(ClipboardSelection {
|
||||
len,
|
||||
is_entire_line,
|
||||
});
|
||||
}
|
||||
}
|
||||
self.update_selections(selections, ctx);
|
||||
self.changed_selections(ctx);
|
||||
self.insert(&String::new(), ctx);
|
||||
self.end_transaction(ctx);
|
||||
|
||||
ctx.as_mut()
|
||||
.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
}
|
||||
|
||||
pub fn copy(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
let buffer = self.buffer.read(ctx);
|
||||
let max_point = buffer.max_point();
|
||||
let mut text = String::new();
|
||||
let selections = self.selections(ctx.as_ref());
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
for selection in selections {
|
||||
let mut start = selection.start.to_point(buffer).expect("invalid start");
|
||||
let mut end = selection.end.to_point(buffer).expect("invalid end");
|
||||
let is_entire_line = start == end;
|
||||
if is_entire_line {
|
||||
start = Point::new(start.row, 0);
|
||||
end = cmp::min(max_point, Point::new(start.row + 1, 0));
|
||||
}
|
||||
let mut len = 0;
|
||||
for ch in buffer.text_for_range(start..end).unwrap() {
|
||||
text.push(ch);
|
||||
len += 1;
|
||||
}
|
||||
clipboard_selections.push(ClipboardSelection {
|
||||
len,
|
||||
is_entire_line,
|
||||
});
|
||||
}
|
||||
|
||||
ctx.as_mut()
|
||||
.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
}
|
||||
|
||||
pub fn paste(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = ctx.as_mut().read_from_clipboard() {
|
||||
let clipboard_text = item.text();
|
||||
if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
|
||||
let selections = self.selections(ctx.as_ref()).to_vec();
|
||||
if clipboard_selections.len() != selections.len() {
|
||||
let merged_selection = ClipboardSelection {
|
||||
len: clipboard_selections.iter().map(|s| s.len).sum(),
|
||||
is_entire_line: clipboard_selections.iter().all(|s| s.is_entire_line),
|
||||
};
|
||||
clipboard_selections.clear();
|
||||
clipboard_selections.push(merged_selection);
|
||||
}
|
||||
|
||||
self.start_transaction(ctx);
|
||||
let mut new_selections = Vec::with_capacity(selections.len());
|
||||
let mut clipboard_chars = clipboard_text.chars().cycle();
|
||||
for (selection, clipboard_selection) in
|
||||
selections.iter().zip(clipboard_selections.iter().cycle())
|
||||
{
|
||||
let to_insert =
|
||||
String::from_iter(clipboard_chars.by_ref().take(clipboard_selection.len));
|
||||
|
||||
self.buffer.update(ctx, |buffer, ctx| {
|
||||
let selection_start = selection.start.to_point(buffer).unwrap();
|
||||
let selection_end = selection.end.to_point(buffer).unwrap();
|
||||
|
||||
// If the corresponding selection was empty when this slice of the
|
||||
// clipboard text was written, then the entire line containing the
|
||||
// selection was copied. If this selection is also currently empty,
|
||||
// then paste the line before the current line of the buffer.
|
||||
let new_selection_start = selection.end.bias_right(buffer).unwrap();
|
||||
if selection_start == selection_end && clipboard_selection.is_entire_line {
|
||||
let line_start = Point::new(selection_start.row, 0);
|
||||
buffer
|
||||
.edit(Some(line_start..line_start), to_insert, Some(ctx))
|
||||
.unwrap();
|
||||
} else {
|
||||
buffer
|
||||
.edit(Some(&selection.start..&selection.end), to_insert, Some(ctx))
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
let new_selection_start = new_selection_start.bias_left(buffer).unwrap();
|
||||
new_selections.push(Selection {
|
||||
start: new_selection_start.clone(),
|
||||
end: new_selection_start,
|
||||
reversed: false,
|
||||
goal_column: None,
|
||||
});
|
||||
});
|
||||
}
|
||||
self.update_selections(new_selections, ctx);
|
||||
self.end_transaction(ctx);
|
||||
} else {
|
||||
self.insert(clipboard_text, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
self.buffer
|
||||
.update(ctx, |buffer, ctx| buffer.undo(Some(ctx)));
|
||||
@@ -1417,8 +1577,11 @@ mod tests {
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
|
||||
.unwrap();
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
view.fold(&(), ctx);
|
||||
assert_eq!(
|
||||
view.text(ctx.as_ref()),
|
||||
@@ -1525,7 +1688,7 @@ mod tests {
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
// an empty selection - the preceding character is deleted
|
||||
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
|
||||
@@ -1547,6 +1710,147 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clipboard() {
|
||||
App::test((), |app| {
|
||||
let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six "));
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let view = app
|
||||
.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx))
|
||||
.1;
|
||||
|
||||
// Cut with three selections. Clipboard text is divided into three slices.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(&[0..4, 8..14, 19..24], ctx).unwrap();
|
||||
view.cut(&(), ctx);
|
||||
});
|
||||
assert_eq!(view.read(app).text(app.as_ref()), "two four six ");
|
||||
|
||||
// Paste with three cursors. Each cursor pastes one slice of the clipboard text.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(&[4..4, 9..9, 13..13], ctx).unwrap();
|
||||
view.paste(&(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"two one four three six five "
|
||||
);
|
||||
assert_eq!(
|
||||
view.read(app).selection_ranges(app.as_ref()),
|
||||
&[
|
||||
DisplayPoint::new(0, 8)..DisplayPoint::new(0, 8),
|
||||
DisplayPoint::new(0, 19)..DisplayPoint::new(0, 19),
|
||||
DisplayPoint::new(0, 28)..DisplayPoint::new(0, 28)
|
||||
]
|
||||
);
|
||||
|
||||
// Paste again but with only two cursors. Since the number of cursors doesn't
|
||||
// match the number of slices in the clipboard, the entire clipboard text
|
||||
// is pasted at each cursor.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(&[0..0, 28..28], ctx).unwrap();
|
||||
view.insert(&"( ".to_string(), ctx);
|
||||
view.paste(&(), ctx);
|
||||
view.insert(&") ".to_string(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"( one three five ) two one four three six five ( one three five ) "
|
||||
);
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_ranges(&[0..0], ctx).unwrap();
|
||||
view.insert(&"123\n4567\n89\n".to_string(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"123\n4567\n89\n( one three five ) two one four three six five ( one three five ) "
|
||||
);
|
||||
|
||||
// Cut with three selections, one of which is full-line.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2),
|
||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
||||
DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
|
||||
],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
view.cut(&(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"13\n9\n( one three five ) two one four three six five ( one three five ) "
|
||||
);
|
||||
|
||||
// Paste with three selections, noticing how the copied selection that was full-line
|
||||
// gets inserted before the second cursor.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
|
||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
||||
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
|
||||
],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
view.paste(&(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"123\n4567\n9\n( 8ne three five ) two one four three six five ( one three five ) "
|
||||
);
|
||||
assert_eq!(
|
||||
view.read(app).selection_ranges(app.as_ref()),
|
||||
&[
|
||||
DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
|
||||
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
|
||||
DisplayPoint::new(3, 3)..DisplayPoint::new(3, 3),
|
||||
]
|
||||
);
|
||||
|
||||
// Copy with a single cursor only, which writes the whole line into the clipboard.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
view.copy(&(), ctx);
|
||||
});
|
||||
|
||||
// Paste with three selections, noticing how the copied full-line selection is inserted
|
||||
// before the empty selections but replaces the selection that is non-empty.
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[
|
||||
DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
|
||||
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2),
|
||||
DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
|
||||
],
|
||||
ctx,
|
||||
)
|
||||
.unwrap();
|
||||
view.paste(&(), ctx);
|
||||
});
|
||||
assert_eq!(
|
||||
view.read(app).text(app.as_ref()),
|
||||
"123\n123\n123\n67\n123\n9\n( 8ne three five ) two one four three six five ( one three five ) "
|
||||
);
|
||||
assert_eq!(
|
||||
view.read(app).selection_ranges(app.as_ref()),
|
||||
&[
|
||||
DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
|
||||
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
||||
DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
impl BufferView {
|
||||
fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
|
||||
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
|
||||
|
||||
@@ -77,7 +77,7 @@ impl View for FileFinder {
|
||||
.with_max_height(400.0)
|
||||
.boxed(),
|
||||
)
|
||||
.top_center()
|
||||
.top()
|
||||
.named("file finder")
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -29,19 +29,19 @@ pub struct OpenParams {
|
||||
}
|
||||
|
||||
fn open(settings: &Receiver<Settings>, ctx: &mut MutableAppContext) {
|
||||
if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions {
|
||||
files: true,
|
||||
directories: true,
|
||||
multiple: true,
|
||||
}) {
|
||||
ctx.dispatch_global_action(
|
||||
"workspace:open_paths",
|
||||
OpenParams {
|
||||
paths,
|
||||
settings: settings.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
let settings = settings.clone();
|
||||
ctx.prompt_for_paths(
|
||||
PathPromptOptions {
|
||||
files: true,
|
||||
directories: true,
|
||||
multiple: true,
|
||||
},
|
||||
move |paths, ctx| {
|
||||
if let Some(paths) = paths {
|
||||
ctx.dispatch_global_action("workspace:open_paths", OpenParams { paths, settings });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn open_paths(params: &OpenParams, app: &mut MutableAppContext) {
|
||||
|
||||
+55
-38
@@ -189,33 +189,28 @@ impl Pane {
|
||||
|
||||
let padding = 6.;
|
||||
let mut container = Container::new(
|
||||
Align::new(
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Stack::new()
|
||||
.with_child(
|
||||
Align::new(
|
||||
Label::new(title, settings.ui_font_family, settings.ui_font_size)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
Container::new(
|
||||
LineBox::new(
|
||||
settings.ui_font_family,
|
||||
settings.ui_font_size,
|
||||
ConstrainedBox::new(Self::render_modified_icon(
|
||||
item.is_dirty(app),
|
||||
))
|
||||
.with_max_width(12.)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(
|
||||
LineBox::new(
|
||||
settings.ui_font_family,
|
||||
settings.ui_font_size,
|
||||
Align::new(Self::render_modified_icon(item.is_dirty(app)))
|
||||
.right()
|
||||
.boxed(),
|
||||
)
|
||||
.with_margin_left(20.)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_uniform_padding(padding)
|
||||
.with_vertical_padding(padding)
|
||||
.with_horizontal_padding(10.)
|
||||
.with_border(border);
|
||||
|
||||
if ix == self.active_item {
|
||||
@@ -237,6 +232,7 @@ impl Pane {
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.with_min_width(80.0)
|
||||
.with_max_width(264.0)
|
||||
.boxed(),
|
||||
)
|
||||
@@ -244,9 +240,29 @@ impl Pane {
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure there's always a minimum amount of space after the last tab,
|
||||
// so that the tab's border doesn't abut the window's border.
|
||||
row.add_child(
|
||||
ConstrainedBox::new(
|
||||
Container::new(
|
||||
LineBox::new(
|
||||
settings.ui_font_family,
|
||||
settings.ui_font_size,
|
||||
Empty::new().boxed(),
|
||||
)
|
||||
.boxed(),
|
||||
)
|
||||
.with_uniform_padding(6.0)
|
||||
.with_border(Border::bottom(1.0, border_color))
|
||||
.boxed(),
|
||||
)
|
||||
.with_min_width(20.)
|
||||
.named("fixed-filler"),
|
||||
);
|
||||
|
||||
row.add_child(
|
||||
Expanded::new(
|
||||
1.0,
|
||||
0.0,
|
||||
Container::new(
|
||||
LineBox::new(
|
||||
settings.ui_font_family,
|
||||
@@ -266,23 +282,24 @@ impl Pane {
|
||||
}
|
||||
|
||||
fn render_modified_icon(is_modified: bool) -> ElementBox {
|
||||
Canvas::new(move |bounds, ctx| {
|
||||
if is_modified {
|
||||
let padding = if bounds.height() < bounds.width() {
|
||||
vec2f(bounds.width() - bounds.height(), 0.0)
|
||||
} else {
|
||||
vec2f(0.0, bounds.height() - bounds.width())
|
||||
};
|
||||
let square = RectF::new(bounds.origin() + padding / 2., bounds.size() - padding);
|
||||
ctx.scene.push_quad(Quad {
|
||||
bounds: square,
|
||||
background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()),
|
||||
border: Default::default(),
|
||||
corner_radius: square.width() / 2.,
|
||||
});
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
let diameter = 8.;
|
||||
ConstrainedBox::new(
|
||||
Canvas::new(move |bounds, ctx| {
|
||||
if is_modified {
|
||||
let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
|
||||
ctx.scene.push_quad(Quad {
|
||||
bounds: square,
|
||||
background: Some(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8()),
|
||||
border: Default::default(),
|
||||
corner_radius: diameter / 2.,
|
||||
});
|
||||
}
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.with_width(diameter)
|
||||
.with_height(diameter)
|
||||
.named("tab-right-icon")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{settings::Settings, watch};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use gpui::{
|
||||
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
|
||||
Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
||||
ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use log::{error, info};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
@@ -258,10 +258,11 @@ impl WorkspaceView {
|
||||
pub fn debug_elements(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
match to_string_pretty(&ctx.debug_elements()) {
|
||||
Ok(json) => {
|
||||
ctx.as_mut().copy(&json);
|
||||
let kib = json.len() as f32 / 1024.;
|
||||
ctx.as_mut().write_to_clipboard(ClipboardItem::new(json));
|
||||
log::info!(
|
||||
"copied {:.1} KiB of element debug JSON to the clipboard",
|
||||
json.len() as f32 / 1024.
|
||||
kib
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
|
||||
Reference in New Issue
Block a user