Merge branch 'main' into theme-variables

This commit is contained in:
Nathan Sobo 2021-08-04 16:47:43 -06:00
commit 85a076312a
7 changed files with 181 additions and 174 deletions

View file

@ -23,7 +23,7 @@ use core_graphics::{
use core_text::{line::CTLine, string_attributes::kCTFontAttributeName}; use core_text::{line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource}; use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{cell::RefCell, char, convert::TryFrom, ffi::c_void}; use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void};
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7; const kCGImageAlphaOnly: u32 = 7;
@ -199,6 +199,7 @@ impl FontSystemState {
let mut string = CFMutableAttributedString::new(); let mut string = CFMutableAttributedString::new();
{ {
string.replace_str(&CFString::new(text), CFRange::init(0, 0)); string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let utf16_line_len = string.char_len() as usize;
let last_run: RefCell<Option<(usize, FontId)>> = Default::default(); let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
let font_runs = runs let font_runs = runs
@ -226,12 +227,16 @@ impl FontSystemState {
for (run_len, font_id) in font_runs { for (run_len, font_id) in font_runs {
let utf8_end = ix_converter.utf8_ix + run_len; let utf8_end = ix_converter.utf8_ix + run_len;
let utf16_start = ix_converter.utf16_ix; let utf16_start = ix_converter.utf16_ix;
ix_converter.advance_to_utf8_ix(utf8_end);
let cf_range = CFRange::init( if utf16_start >= utf16_line_len {
utf16_start as isize, break;
(ix_converter.utf16_ix - utf16_start) as isize, }
);
ix_converter.advance_to_utf8_ix(utf8_end);
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
let cf_range =
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
let font = &self.fonts[font_id.0]; let font = &self.fonts[font_id.0];
unsafe { unsafe {
string.set_attribute( string.set_attribute(
@ -245,6 +250,10 @@ impl FontSystemState {
&CFNumber::from(font_id.0 as i64), &CFNumber::from(font_id.0 as i64),
); );
} }
if utf16_end == utf16_line_len {
break;
}
} }
} }
@ -483,7 +492,7 @@ mod tests {
} }
#[test] #[test]
fn test_layout_line() { fn test_wrap_line() {
let fonts = FontSystem::new(); let fonts = FontSystem::new();
let font_ids = fonts.load_family("Helvetica").unwrap(); let font_ids = fonts.load_family("Helvetica").unwrap();
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
@ -499,4 +508,25 @@ mod tests {
&["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
); );
} }
#[test]
fn test_layout_line_bom_char() {
let fonts = FontSystem::new();
let font_ids = fonts.load_family("Helvetica").unwrap();
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
let line = "\u{feff}";
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
assert_eq!(layout.len, line.len());
assert!(layout.runs.is_empty());
let line = "a\u{feff}b";
let layout = fonts.layout_line(line, 16., &[(line.len(), font_id, Default::default())]);
assert_eq!(layout.len, line.len());
assert_eq!(layout.runs.len(), 1);
assert_eq!(layout.runs[0].glyphs.len(), 2);
assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
// There's no glyph for \u{feff}
assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 579 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Before After
Before After

View file

@ -3,7 +3,7 @@ use crate::{
settings::Settings, settings::Settings,
util, util,
workspace::Workspace, workspace::Workspace,
worktree::{match_paths, PathMatch, Worktree}, worktree::{match_paths, PathMatch},
}; };
use gpui::{ use gpui::{
elements::*, elements::*,
@ -124,9 +124,12 @@ impl FileFinder {
let finder = finder.read(cx); let finder = finder.read(cx);
let start = range.start; let start = range.start;
range.end = cmp::min(range.end, finder.matches.len()); range.end = cmp::min(range.end, finder.matches.len());
items.extend(finder.matches[range].iter().enumerate().filter_map( items.extend(
move |(i, path_match)| finder.render_match(path_match, start + i, cx), finder.matches[range]
)); .iter()
.enumerate()
.map(move |(i, path_match)| finder.render_match(path_match, start + i)),
);
}, },
); );
@ -135,12 +138,7 @@ impl FileFinder {
.named("matches") .named("matches")
} }
fn render_match( fn render_match(&self, path_match: &PathMatch, index: usize) -> ElementBox {
&self,
path_match: &PathMatch,
index: usize,
cx: &AppContext,
) -> Option<ElementBox> {
let selected_index = self.selected_index(); let selected_index = self.selected_index();
let settings = self.settings.borrow(); let settings = self.settings.borrow();
let style = if index == selected_index { let style = if index == selected_index {
@ -148,102 +146,88 @@ impl FileFinder {
} else { } else {
&settings.theme.ui.selector.item &settings.theme.ui.selector.item
}; };
self.labels_for_match(path_match, cx).map( let (file_name, file_name_positions, full_path, full_path_positions) =
|(file_name, file_name_positions, full_path, full_path_positions)| { self.labels_for_match(path_match);
let container = Container::new( let container = Container::new(
Flex::row() Flex::row()
.with_child( .with_child(
Container::new( Container::new(
LineBox::new( LineBox::new(
settings.ui_font_family, settings.ui_font_family,
settings.ui_font_size, settings.ui_font_size,
Svg::new("icons/file-16.svg") Svg::new("icons/file-16.svg")
.with_color(style.label.text.color) .with_color(style.label.text.color)
.boxed(),
)
.boxed(), .boxed(),
)
.with_padding_right(6.0)
.boxed(),
)
.with_child(
Expanded::new(
1.0,
Flex::column()
.with_child(
Label::new(
file_name.to_string(),
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(file_name_positions)
.boxed(),
)
.with_child(
Label::new(
full_path,
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(full_path_positions)
.boxed(),
)
.boxed(),
)
.boxed(),
) )
.boxed(), .boxed(),
)
.with_padding_right(6.0)
.boxed(),
) )
.with_style(&style.container); .with_child(
Expanded::new(
let entry = (path_match.tree_id, path_match.path.clone()); 1.0,
EventHandler::new(container.boxed()) Flex::column()
.on_mouse_down(move |cx| { .with_child(
cx.dispatch_action("file_finder:select", entry.clone()); Label::new(
true file_name.to_string(),
}) settings.ui_font_family,
.named("match") settings.ui_font_size,
}, )
.with_style(&style.label)
.with_highlights(file_name_positions)
.boxed(),
)
.with_child(
Label::new(
full_path,
settings.ui_font_family,
settings.ui_font_size,
)
.with_style(&style.label)
.with_highlights(full_path_positions)
.boxed(),
)
.boxed(),
)
.boxed(),
)
.boxed(),
) )
.with_style(&style.container);
let entry = (path_match.tree_id, path_match.path.clone());
EventHandler::new(container.boxed())
.on_mouse_down(move |cx| {
cx.dispatch_action("file_finder:select", entry.clone());
true
})
.named("match")
} }
fn labels_for_match( fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
&self, let path_string = path_match.path.to_string_lossy();
path_match: &PathMatch, let full_path = [path_match.path_prefix.as_ref(), path_string.as_ref()].join("");
cx: &AppContext, let path_positions = path_match.positions.clone();
) -> Option<(String, Vec<usize>, String, Vec<usize>)> {
self.worktree(path_match.tree_id, cx).map(|tree| {
let prefix = if path_match.include_root_name {
tree.root_name()
} else {
""
};
let path_string = path_match.path.to_string_lossy(); let file_name = path_match.path.file_name().map_or_else(
let full_path = [prefix, path_string.as_ref()].join(""); || path_match.path_prefix.to_string(),
let path_positions = path_match.positions.clone(); |file_name| file_name.to_string_lossy().to_string(),
);
let file_name_start = path_match.path_prefix.chars().count() + path_string.chars().count()
- file_name.chars().count();
let file_name_positions = path_positions
.iter()
.filter_map(|pos| {
if pos >= &file_name_start {
Some(pos - file_name_start)
} else {
None
}
})
.collect();
let file_name = path_match.path.file_name().map_or_else( (file_name, file_name_positions, full_path, path_positions)
|| prefix.to_string(),
|file_name| file_name.to_string_lossy().to_string(),
);
let file_name_start =
prefix.chars().count() + path_string.chars().count() - file_name.chars().count();
let file_name_positions = path_positions
.iter()
.filter_map(|pos| {
if pos >= &file_name_start {
Some(pos - file_name_start)
} else {
None
}
})
.collect();
(file_name, file_name_positions, full_path, path_positions)
})
} }
fn toggle(workspace: &mut Workspace, _: &(), cx: &mut ViewContext<Workspace>) { fn toggle(workspace: &mut Workspace, _: &(), cx: &mut ViewContext<Workspace>) {
@ -392,11 +376,9 @@ impl FileFinder {
self.cancel_flag = Arc::new(AtomicBool::new(false)); self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone(); let cancel_flag = self.cancel_flag.clone();
Some(cx.spawn(|this, mut cx| async move { Some(cx.spawn(|this, mut cx| async move {
let include_root_name = snapshots.len() > 1;
let matches = match_paths( let matches = match_paths(
snapshots.iter(), &snapshots,
&query, &query,
include_root_name,
false, false,
false, false,
100, 100,
@ -429,15 +411,6 @@ impl FileFinder {
cx.notify(); cx.notify();
} }
} }
fn worktree<'a>(&'a self, tree_id: usize, cx: &'a AppContext) -> Option<&'a Worktree> {
self.workspace
.upgrade(cx)?
.read(cx)
.worktrees()
.get(&tree_id)
.map(|worktree| worktree.read(cx))
}
} }
#[cfg(test)] #[cfg(test)]
@ -625,7 +598,7 @@ mod tests {
assert_eq!(finder.matches.len(), 1); assert_eq!(finder.matches.len(), 1);
let (file_name, file_name_positions, full_path, full_path_positions) = let (file_name, file_name_positions, full_path, full_path_positions) =
finder.labels_for_match(&finder.matches[0], cx).unwrap(); finder.labels_for_match(&finder.matches[0]);
assert_eq!(file_name, "the-file"); assert_eq!(file_name, "the-file");
assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(file_name_positions, &[0, 1, 4]);
assert_eq!(full_path, "the-file"); assert_eq!(full_path, "the-file");

View file

@ -1322,14 +1322,31 @@ mod tests {
cx.dispatch_global_action("workspace:new_file", app_state); cx.dispatch_global_action("workspace:new_file", app_state);
let window_id = *cx.window_ids().first().unwrap(); let window_id = *cx.window_ids().first().unwrap();
let workspace = cx.root_view::<Workspace>(window_id).unwrap(); let workspace = cx.root_view::<Workspace>(window_id).unwrap();
workspace.update(&mut cx, |workspace, cx| { let editor = workspace.update(&mut cx, |workspace, cx| {
let editor = workspace workspace
.active_item(cx) .active_item(cx)
.unwrap() .unwrap()
.to_any() .to_any()
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap()
assert!(editor.update(cx, |editor, cx| editor.text(cx).is_empty())); });
editor.update(&mut cx, |editor, cx| {
assert!(editor.text(cx).is_empty());
});
workspace.update(&mut cx, |workspace, cx| workspace.save_active_item(&(), cx));
let dir = TempDir::new("test-new-empty-workspace").unwrap();
cx.simulate_new_path_selection(|_| {
Some(dir.path().canonicalize().unwrap().join("the-new-name"))
});
editor
.condition(&cx, |editor, cx| editor.title(cx) == "the-new-name")
.await;
editor.update(&mut cx, |editor, cx| {
assert!(!editor.is_dirty(cx));
}); });
} }

View file

@ -586,17 +586,11 @@ impl LocalWorktree {
// After determining whether the root entry is a file or a directory, populate the // After determining whether the root entry is a file or a directory, populate the
// snapshot's "root name", which will be used for the purpose of fuzzy matching. // snapshot's "root name", which will be used for the purpose of fuzzy matching.
let mut root_name = abs_path let root_name = abs_path
.file_name() .file_name()
.map_or(String::new(), |f| f.to_string_lossy().to_string()); .map_or(String::new(), |f| f.to_string_lossy().to_string());
let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect(); let root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
let metadata = fs let metadata = fs.metadata(&abs_path).await?;
.metadata(&abs_path)
.await?
.ok_or_else(|| anyhow!("root entry does not exist"))?;
if metadata.is_dir {
root_name.push('/');
}
let (scan_states_tx, scan_states_rx) = smol::channel::unbounded(); let (scan_states_tx, scan_states_rx) = smol::channel::unbounded();
let (mut last_scan_state_tx, last_scan_state_rx) = watch::channel_with(ScanState::Scanning); let (mut last_scan_state_tx, last_scan_state_rx) = watch::channel_with(ScanState::Scanning);
@ -613,12 +607,14 @@ impl LocalWorktree {
removed_entry_ids: Default::default(), removed_entry_ids: Default::default(),
next_entry_id: Arc::new(next_entry_id), next_entry_id: Arc::new(next_entry_id),
}; };
snapshot.insert_entry(Entry::new( if let Some(metadata) = metadata {
path.into(), snapshot.insert_entry(Entry::new(
&metadata, path.into(),
&snapshot.next_entry_id, &metadata,
snapshot.root_char_bag, &snapshot.next_entry_id,
)); snapshot.root_char_bag,
));
}
let tree = Self { let tree = Self {
snapshot: snapshot.clone(), snapshot: snapshot.clone(),
@ -1229,12 +1225,10 @@ impl Snapshot {
ChildEntriesIter::new(path, self) ChildEntriesIter::new(path, self)
} }
pub fn root_entry(&self) -> &Entry { pub fn root_entry(&self) -> Option<&Entry> {
self.entry_for_path("").unwrap() self.entry_for_path("")
} }
/// Returns the filename of the snapshot's root, plus a trailing slash if the snapshot's root is
/// a directory.
pub fn root_name(&self) -> &str { pub fn root_name(&self) -> &str {
&self.root_name &self.root_name
} }
@ -1856,8 +1850,8 @@ impl BackgroundScanner {
let snapshot = self.snapshot.lock(); let snapshot = self.snapshot.lock();
root_char_bag = snapshot.root_char_bag; root_char_bag = snapshot.root_char_bag;
next_entry_id = snapshot.next_entry_id.clone(); next_entry_id = snapshot.next_entry_id.clone();
is_dir = snapshot.root_entry().is_dir(); is_dir = snapshot.root_entry().map_or(false, |e| e.is_dir())
} };
if is_dir { if is_dir {
let path: Arc<Path> = Arc::from(Path::new("")); let path: Arc<Path> = Arc::from(Path::new(""));
@ -2605,25 +2599,23 @@ mod tests {
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await; .await;
let snapshot = cx.read(|cx| { let snapshots = [cx.read(|cx| {
let tree = tree.read(cx); let tree = tree.read(cx);
assert_eq!(tree.file_count(), 5); assert_eq!(tree.file_count(), 5);
assert_eq!( assert_eq!(
tree.inode_for_path("fennel/grape"), tree.inode_for_path("fennel/grape"),
tree.inode_for_path("finnochio/grape") tree.inode_for_path("finnochio/grape")
); );
tree.snapshot() tree.snapshot()
}); })];
let cancel_flag = Default::default(); let cancel_flag = Default::default();
let results = cx let results = cx
.read(|cx| { .read(|cx| {
match_paths( match_paths(
Some(&snapshot).into_iter(), &snapshots,
"bna", "bna",
false, false,
false, false,
false,
10, 10,
&cancel_flag, &cancel_flag,
cx.background().clone(), cx.background().clone(),
@ -2663,20 +2655,19 @@ mod tests {
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await; .await;
let snapshot = cx.read(|cx| { let snapshots = [cx.read(|cx| {
let tree = tree.read(cx); let tree = tree.read(cx);
assert_eq!(tree.file_count(), 0); assert_eq!(tree.file_count(), 0);
tree.snapshot() tree.snapshot()
}); })];
let cancel_flag = Default::default(); let cancel_flag = Default::default();
let results = cx let results = cx
.read(|cx| { .read(|cx| {
match_paths( match_paths(
Some(&snapshot).into_iter(), &snapshots,
"dir", "dir",
false, false,
false, false,
false,
10, 10,
&cancel_flag, &cancel_flag,
cx.background().clone(), cx.background().clone(),

View file

@ -48,7 +48,7 @@ pub struct PathMatch {
pub positions: Vec<usize>, pub positions: Vec<usize>,
pub tree_id: usize, pub tree_id: usize,
pub path: Arc<Path>, pub path: Arc<Path>,
pub include_root_name: bool, pub path_prefix: Arc<str>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -207,23 +207,19 @@ pub async fn match_strings(
results results
} }
pub async fn match_paths<'a, T>( pub async fn match_paths(
snapshots: T, snapshots: &[Snapshot],
query: &str, query: &str,
include_root_name: bool,
include_ignored: bool, include_ignored: bool,
smart_case: bool, smart_case: bool,
max_results: usize, max_results: usize,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
background: Arc<executor::Background>, background: Arc<executor::Background>,
) -> Vec<PathMatch> ) -> Vec<PathMatch> {
where
T: Clone + Send + Iterator<Item = &'a Snapshot> + 'a,
{
let path_count: usize = if include_ignored { let path_count: usize = if include_ignored {
snapshots.clone().map(Snapshot::file_count).sum() snapshots.iter().map(Snapshot::file_count).sum()
} else { } else {
snapshots.clone().map(Snapshot::visible_file_count).sum() snapshots.iter().map(Snapshot::visible_file_count).sum()
}; };
if path_count == 0 { if path_count == 0 {
return Vec::new(); return Vec::new();
@ -245,7 +241,6 @@ where
background background
.scoped(|scope| { .scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() { for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let snapshots = snapshots.clone();
scope.spawn(async move { scope.spawn(async move {
let segment_start = segment_idx * segment_size; let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size; let segment_end = segment_start + segment_size;
@ -265,9 +260,16 @@ where
tree_start + snapshot.visible_file_count() tree_start + snapshot.visible_file_count()
}; };
let include_root_name =
include_root_name || snapshot.root_entry().is_file();
if tree_start < segment_end && segment_start < tree_end { if tree_start < segment_end && segment_start < tree_end {
let path_prefix: Arc<str> =
if snapshot.root_entry().map_or(false, |e| e.is_file()) {
snapshot.root_name().into()
} else if snapshots.len() > 1 {
format!("{}/", snapshot.root_name()).into()
} else {
"".into()
};
let start = max(tree_start, segment_start) - tree_start; let start = max(tree_start, segment_start) - tree_start;
let end = min(tree_end, segment_end) - tree_start; let end = min(tree_end, segment_end) - tree_start;
let entries = if include_ignored { let entries = if include_ignored {
@ -288,7 +290,7 @@ where
matcher.match_paths( matcher.match_paths(
snapshot, snapshot,
include_root_name, path_prefix,
paths, paths,
results, results,
&cancel_flag, &cancel_flag,
@ -360,19 +362,13 @@ impl<'a> Matcher<'a> {
fn match_paths( fn match_paths(
&mut self, &mut self,
snapshot: &Snapshot, snapshot: &Snapshot,
include_root_name: bool, path_prefix: Arc<str>,
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>, path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
results: &mut Vec<PathMatch>, results: &mut Vec<PathMatch>,
cancel_flag: &AtomicBool, cancel_flag: &AtomicBool,
) { ) {
let tree_id = snapshot.id; let tree_id = snapshot.id;
let prefix = if include_root_name { let prefix = path_prefix.chars().collect::<Vec<_>>();
snapshot.root_name()
} else {
""
}
.chars()
.collect::<Vec<_>>();
let lowercase_prefix = prefix let lowercase_prefix = prefix
.iter() .iter()
.map(|c| c.to_ascii_lowercase()) .map(|c| c.to_ascii_lowercase())
@ -388,7 +384,7 @@ impl<'a> Matcher<'a> {
tree_id, tree_id,
positions: Vec::new(), positions: Vec::new(),
path: candidate.path.clone(), path: candidate.path.clone(),
include_root_name, path_prefix: path_prefix.clone(),
}, },
) )
} }
@ -772,7 +768,7 @@ mod tests {
root_char_bag: Default::default(), root_char_bag: Default::default(),
next_entry_id: Default::default(), next_entry_id: Default::default(),
}, },
false, "".into(),
path_entries.into_iter(), path_entries.into_iter(),
&mut results, &mut results,
&cancel_flag, &cancel_flag,