Add a theme picker
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
e080739d57
commit
b30d0daabf
20 changed files with 982 additions and 437 deletions
|
@ -28,7 +28,7 @@ impl gpui::View for TextView {
|
||||||
"View"
|
"View"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&self, _: &gpui::AppContext) -> gpui::ElementBox {
|
fn render(&self, _: &gpui::RenderContext<Self>) -> gpui::ElementBox {
|
||||||
TextElement.boxed()
|
TextElement.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@ pub trait Entity: 'static + Send + Sync {
|
||||||
fn release(&mut self, _: &mut MutableAppContext) {}
|
fn release(&mut self, _: &mut MutableAppContext) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait View: Entity {
|
pub trait View: Entity + Sized {
|
||||||
fn ui_name() -> &'static str;
|
fn ui_name() -> &'static str;
|
||||||
fn render<'a>(&self, cx: &AppContext) -> ElementBox;
|
fn render(&self, cx: &RenderContext<'_, Self>) -> ElementBox;
|
||||||
fn on_focus(&mut self, _: &mut ViewContext<Self>) {}
|
fn on_focus(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
fn on_blur(&mut self, _: &mut ViewContext<Self>) {}
|
fn on_blur(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
fn keymap_context(&self, _: &AppContext) -> keymap::Context {
|
fn keymap_context(&self, _: &AppContext) -> keymap::Context {
|
||||||
|
@ -1503,7 +1503,7 @@ impl AppContext {
|
||||||
pub fn render_view(&self, window_id: usize, view_id: usize) -> Result<ElementBox> {
|
pub fn render_view(&self, window_id: usize, view_id: usize) -> Result<ElementBox> {
|
||||||
self.views
|
self.views
|
||||||
.get(&(window_id, view_id))
|
.get(&(window_id, view_id))
|
||||||
.map(|v| v.render(self))
|
.map(|v| v.render(window_id, view_id, self))
|
||||||
.ok_or(anyhow!("view not found"))
|
.ok_or(anyhow!("view not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1512,7 +1512,7 @@ impl AppContext {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|((win_id, view_id), view)| {
|
.filter_map(|((win_id, view_id), view)| {
|
||||||
if *win_id == window_id {
|
if *win_id == window_id {
|
||||||
Some((*view_id, view.render(self)))
|
Some((*view_id, view.render(*win_id, *view_id, self)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1650,7 +1650,7 @@ pub trait AnyView: Send + Sync {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn release(&mut self, cx: &mut MutableAppContext);
|
fn release(&mut self, cx: &mut MutableAppContext);
|
||||||
fn ui_name(&self) -> &'static str;
|
fn ui_name(&self) -> &'static str;
|
||||||
fn render<'a>(&self, cx: &AppContext) -> ElementBox;
|
fn render<'a>(&self, window_id: usize, view_id: usize, cx: &AppContext) -> ElementBox;
|
||||||
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
||||||
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
||||||
fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
|
fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
|
||||||
|
@ -1676,8 +1676,16 @@ where
|
||||||
T::ui_name()
|
T::ui_name()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&self, cx: &AppContext) -> ElementBox {
|
fn render<'a>(&self, window_id: usize, view_id: usize, cx: &AppContext) -> ElementBox {
|
||||||
View::render(self, cx)
|
View::render(
|
||||||
|
self,
|
||||||
|
&RenderContext {
|
||||||
|
window_id,
|
||||||
|
view_id,
|
||||||
|
app: cx,
|
||||||
|
view_type: PhantomData::<T>,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
|
fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
|
||||||
|
@ -2094,12 +2102,33 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RenderContext<'a, T: View> {
|
||||||
|
pub app: &'a AppContext,
|
||||||
|
window_id: usize,
|
||||||
|
view_id: usize,
|
||||||
|
view_type: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: View> RenderContext<'a, T> {
|
||||||
|
pub fn handle(&self) -> WeakViewHandle<T> {
|
||||||
|
WeakViewHandle::new(self.window_id, self.view_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AsRef<AppContext> for &AppContext {
|
impl AsRef<AppContext> for &AppContext {
|
||||||
fn as_ref(&self) -> &AppContext {
|
fn as_ref(&self) -> &AppContext {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V: View> Deref for RenderContext<'_, V> {
|
||||||
|
type Target = AppContext;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<M> AsRef<AppContext> for ViewContext<'_, M> {
|
impl<M> AsRef<AppContext> for ViewContext<'_, M> {
|
||||||
fn as_ref(&self) -> &AppContext {
|
fn as_ref(&self) -> &AppContext {
|
||||||
&self.app.cx
|
&self.app.cx
|
||||||
|
@ -3004,7 +3033,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3067,7 +3096,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
let mouse_down_count = self.mouse_down_count.clone();
|
let mouse_down_count = self.mouse_down_count.clone();
|
||||||
EventHandler::new(Empty::new().boxed())
|
EventHandler::new(Empty::new().boxed())
|
||||||
.on_mouse_down(move |_| {
|
.on_mouse_down(move |_| {
|
||||||
|
@ -3129,7 +3158,7 @@ mod tests {
|
||||||
"View"
|
"View"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3169,7 +3198,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3222,7 +3251,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3272,7 +3301,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3315,7 +3344,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3362,7 +3391,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3420,7 +3449,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for ViewA {
|
impl View for ViewA {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3438,7 +3467,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for ViewB {
|
impl View for ViewB {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3541,7 +3570,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::View for View {
|
impl super::View for View {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3674,7 +3703,7 @@ mod tests {
|
||||||
"test view"
|
"test view"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _: &AppContext) -> ElementBox {
|
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3719,7 +3748,7 @@ mod tests {
|
||||||
"test view"
|
"test view"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _: &AppContext) -> ElementBox {
|
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3742,7 +3771,7 @@ mod tests {
|
||||||
"test view"
|
"test view"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _: &AppContext) -> ElementBox {
|
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
Empty::new().boxed()
|
Empty::new().boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use std::{borrow::Cow, cell::RefCell, collections::HashMap};
|
use std::{borrow::Cow, cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
pub trait AssetSource: 'static {
|
pub trait AssetSource: 'static + Send + Sync {
|
||||||
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
|
||||||
|
fn list(&self, path: &str) -> Vec<Cow<'static, str>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetSource for () {
|
impl AssetSource for () {
|
||||||
|
@ -12,6 +13,10 @@ impl AssetSource for () {
|
||||||
path
|
path
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list(&self, _: &str) -> Vec<Cow<'static, str>> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AssetCache {
|
pub struct AssetCache {
|
||||||
|
|
|
@ -13,17 +13,10 @@ use json::ToJson;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{cmp, ops::Range, sync::Arc};
|
use std::{cmp, ops::Range, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct UniformListState(Arc<Mutex<StateInner>>);
|
pub struct UniformListState(Arc<Mutex<StateInner>>);
|
||||||
|
|
||||||
impl UniformListState {
|
impl UniformListState {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(Arc::new(Mutex::new(StateInner {
|
|
||||||
scroll_top: 0.0,
|
|
||||||
scroll_to: None,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scroll_to(&self, item_ix: usize) {
|
pub fn scroll_to(&self, item_ix: usize) {
|
||||||
self.0.lock().scroll_to = Some(item_ix);
|
self.0.lock().scroll_to = Some(item_ix);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +26,7 @@ impl UniformListState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct StateInner {
|
struct StateInner {
|
||||||
scroll_top: f32,
|
scroll_top: f32,
|
||||||
scroll_to: Option<usize>,
|
scroll_to: Option<usize>,
|
||||||
|
@ -57,11 +51,11 @@ impl<F> UniformList<F>
|
||||||
where
|
where
|
||||||
F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
|
F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
|
||||||
{
|
{
|
||||||
pub fn new(state: UniformListState, item_count: usize, build_items: F) -> Self {
|
pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state,
|
state,
|
||||||
item_count,
|
item_count,
|
||||||
append_items: build_items,
|
append_items,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +73,7 @@ where
|
||||||
|
|
||||||
let mut state = self.state.0.lock();
|
let mut state = self.state.0.lock();
|
||||||
state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
|
state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max);
|
||||||
cx.dispatch_action("uniform_list:scroll", state.scroll_top);
|
cx.notify();
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -607,7 +607,7 @@ impl gpui::View for EmptyView {
|
||||||
"empty view"
|
"empty view"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&self, _: &gpui::AppContext) -> gpui::ElementBox {
|
fn render<'a>(&self, _: &gpui::RenderContext<Self>) -> gpui::ElementBox {
|
||||||
gpui::Element::boxed(gpui::elements::Empty)
|
gpui::Element::boxed(gpui::elements::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
extends = "base"
|
extends = "_base"
|
||||||
|
|
||||||
[variables]
|
[variables]
|
||||||
elevation_1 = 0x050101
|
elevation_1 = 0x050101
|
||||||
|
|
21
zed/assets/themes/light.toml
Normal file
21
zed/assets/themes/light.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
extends = "_base"
|
||||||
|
|
||||||
|
[variables]
|
||||||
|
elevation_1 = 0xffffff
|
||||||
|
elevation_2 = 0xf3f3f3
|
||||||
|
elevation_3 = 0xececec
|
||||||
|
elevation_4 = 0x3a3b3c
|
||||||
|
text_dull = 0xacacac
|
||||||
|
text_bright = 0x111111
|
||||||
|
text_normal = 0x333333
|
||||||
|
|
||||||
|
[syntax]
|
||||||
|
keyword = 0x0000fa
|
||||||
|
function = 0x795e26
|
||||||
|
string = 0xa82121
|
||||||
|
type = 0x267f29
|
||||||
|
number = 0xb5cea8
|
||||||
|
comment = 0x6a9955
|
||||||
|
property = 0x4e94ce
|
||||||
|
variant = 0x4fc1ff
|
||||||
|
constant = 0x9cdcfe
|
|
@ -10,4 +10,8 @@ impl AssetSource for Assets {
|
||||||
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
|
||||||
Self::get(path).ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
Self::get(path).ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
|
||||||
|
Self::iter().filter(|p| p.starts_with(path)).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ pub use element::*;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::ColorU, font_cache::FamilyId, fonts::Properties as FontProperties,
|
color::ColorU, font_cache::FamilyId, fonts::Properties as FontProperties,
|
||||||
geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element,
|
geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element,
|
||||||
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, Task, TextLayoutCache, View,
|
ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, RenderContext, Task,
|
||||||
ViewContext, WeakViewHandle,
|
TextLayoutCache, View, ViewContext, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -2533,7 +2533,7 @@ impl Entity for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View for Editor {
|
impl View for Editor {
|
||||||
fn render<'a>(&self, _: &AppContext) -> ElementBox {
|
fn render<'a>(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
EditorElement::new(self.handle.clone()).boxed()
|
EditorElement::new(self.handle.clone()).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ use gpui::{
|
||||||
fonts::{Properties, Weight},
|
fonts::{Properties, Weight},
|
||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
keymap::{self, Binding},
|
keymap::{self, Binding},
|
||||||
AppContext, Axis, Border, Entity, MutableAppContext, Task, View, ViewContext, ViewHandle,
|
AppContext, Axis, Border, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
|
||||||
WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -45,7 +45,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action("file_finder:select", FileFinder::select);
|
cx.add_action("file_finder:select", FileFinder::select);
|
||||||
cx.add_action("menu:select_prev", FileFinder::select_prev);
|
cx.add_action("menu:select_prev", FileFinder::select_prev);
|
||||||
cx.add_action("menu:select_next", FileFinder::select_next);
|
cx.add_action("menu:select_next", FileFinder::select_next);
|
||||||
cx.add_action("uniform_list:scroll", FileFinder::scroll);
|
|
||||||
|
|
||||||
cx.add_bindings(vec![
|
cx.add_bindings(vec![
|
||||||
Binding::new("cmd-p", "file_finder:toggle", None),
|
Binding::new("cmd-p", "file_finder:toggle", None),
|
||||||
|
@ -68,7 +67,7 @@ impl View for FileFinder {
|
||||||
"FileFinder"
|
"FileFinder"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _: &AppContext) -> ElementBox {
|
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
let settings = self.settings.borrow();
|
let settings = self.settings.borrow();
|
||||||
|
|
||||||
Align::new(
|
Align::new(
|
||||||
|
@ -267,31 +266,30 @@ impl FileFinder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle(workspace_view: &mut Workspace, _: &(), cx: &mut ViewContext<Workspace>) {
|
fn toggle(workspace: &mut Workspace, _: &(), cx: &mut ViewContext<Workspace>) {
|
||||||
workspace_view.toggle_modal(cx, |cx, workspace_view| {
|
workspace.toggle_modal(cx, |cx, workspace| {
|
||||||
let workspace = cx.handle();
|
let handle = cx.handle();
|
||||||
let finder =
|
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx));
|
||||||
cx.add_view(|cx| Self::new(workspace_view.settings.clone(), workspace, cx));
|
|
||||||
cx.subscribe_to_view(&finder, Self::on_event);
|
cx.subscribe_to_view(&finder, Self::on_event);
|
||||||
finder
|
finder
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
workspace_view: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
_: ViewHandle<FileFinder>,
|
_: ViewHandle<FileFinder>,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
Event::Selected(tree_id, path) => {
|
Event::Selected(tree_id, path) => {
|
||||||
workspace_view
|
workspace
|
||||||
.open_entry((*tree_id, path.clone()), cx)
|
.open_entry((*tree_id, path.clone()), cx)
|
||||||
.map(|d| d.detach());
|
.map(|d| d.detach());
|
||||||
workspace_view.dismiss_modal(cx);
|
workspace.dismiss_modal(cx);
|
||||||
}
|
}
|
||||||
Event::Dismissed => {
|
Event::Dismissed => {
|
||||||
workspace_view.dismiss_modal(cx);
|
workspace.dismiss_modal(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -318,7 +316,7 @@ impl FileFinder {
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
selected: None,
|
selected: None,
|
||||||
cancel_flag: Arc::new(AtomicBool::new(false)),
|
cancel_flag: Arc::new(AtomicBool::new(false)),
|
||||||
list_state: UniformListState::new(),
|
list_state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,10 +386,6 @@ impl FileFinder {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll(&mut self, _: &f32, cx: &mut ViewContext<Self>) {
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn confirm(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
fn confirm(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
||||||
if let Some(m) = self.matches.get(self.selected_index()) {
|
if let Some(m) = self.matches.get(self.selected_index()) {
|
||||||
cx.emit(Event::Selected(m.tree_id, m.path.clone()));
|
cx.emit(Event::Selected(m.tree_id, m.path.clone()));
|
||||||
|
@ -426,7 +420,7 @@ impl FileFinder {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
100,
|
100,
|
||||||
cancel_flag.clone(),
|
cancel_flag.as_ref(),
|
||||||
background,
|
background,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use zrpc::ForegroundRouter;
|
|
||||||
|
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod file_finder;
|
pub mod file_finder;
|
||||||
|
@ -12,6 +10,7 @@ pub mod settings;
|
||||||
mod sum_tree;
|
mod sum_tree;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod test;
|
pub mod test;
|
||||||
|
pub mod theme_picker;
|
||||||
mod time;
|
mod time;
|
||||||
mod util;
|
mod util;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
@ -19,13 +18,19 @@ pub mod worktree;
|
||||||
|
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
|
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
use postage::watch;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use zrpc::ForegroundRouter;
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub settings: postage::watch::Receiver<Settings>,
|
pub settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
||||||
pub languages: std::sync::Arc<language::LanguageRegistry>,
|
pub settings: watch::Receiver<Settings>,
|
||||||
pub themes: std::sync::Arc<settings::ThemeRegistry>,
|
pub languages: Arc<language::LanguageRegistry>,
|
||||||
pub rpc_router: std::sync::Arc<ForegroundRouter>,
|
pub themes: Arc<settings::ThemeRegistry>,
|
||||||
|
pub rpc_router: Arc<ForegroundRouter>,
|
||||||
pub rpc: rpc::Client,
|
pub rpc: rpc::Client,
|
||||||
pub fs: std::sync::Arc<dyn fs::Fs>,
|
pub fs: Arc<dyn fs::Fs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(cx: &mut gpui::MutableAppContext) {
|
pub fn init(cx: &mut gpui::MutableAppContext) {
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use fs::OpenOptions;
|
use fs::OpenOptions;
|
||||||
|
use futures::lock::Mutex;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
use std::{fs, path::PathBuf, sync::Arc};
|
use std::{fs, path::PathBuf, sync::Arc};
|
||||||
use zed::{
|
use zed::{
|
||||||
self, assets, editor, file_finder,
|
self, assets, editor, file_finder,
|
||||||
fs::RealFs,
|
fs::RealFs,
|
||||||
language, menus, rpc, settings,
|
language, menus, rpc, settings, theme_picker,
|
||||||
workspace::{self, OpenParams},
|
workspace::{self, OpenParams},
|
||||||
worktree::{self},
|
worktree::{self},
|
||||||
AppState,
|
AppState,
|
||||||
|
@ -21,12 +22,14 @@ fn main() {
|
||||||
let app = gpui::App::new(assets::Assets).unwrap();
|
let app = gpui::App::new(assets::Assets).unwrap();
|
||||||
|
|
||||||
let themes = settings::ThemeRegistry::new(assets::Assets);
|
let themes = settings::ThemeRegistry::new(assets::Assets);
|
||||||
let (_, settings) = settings::channel_with_themes(&app.font_cache(), &themes).unwrap();
|
let (settings_tx, settings) =
|
||||||
|
settings::channel_with_themes(&app.font_cache(), &themes).unwrap();
|
||||||
let languages = Arc::new(language::LanguageRegistry::new());
|
let languages = Arc::new(language::LanguageRegistry::new());
|
||||||
languages.set_theme(&settings.borrow().theme);
|
languages.set_theme(&settings.borrow().theme);
|
||||||
|
|
||||||
let mut app_state = AppState {
|
let mut app_state = AppState {
|
||||||
languages: languages.clone(),
|
languages: languages.clone(),
|
||||||
|
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
||||||
settings,
|
settings,
|
||||||
themes,
|
themes,
|
||||||
rpc_router: Arc::new(ForegroundRouter::new()),
|
rpc_router: Arc::new(ForegroundRouter::new()),
|
||||||
|
@ -40,12 +43,14 @@ fn main() {
|
||||||
&app_state.rpc,
|
&app_state.rpc,
|
||||||
Arc::get_mut(&mut app_state.rpc_router).unwrap(),
|
Arc::get_mut(&mut app_state.rpc_router).unwrap(),
|
||||||
);
|
);
|
||||||
|
let app_state = Arc::new(app_state);
|
||||||
|
|
||||||
zed::init(cx);
|
zed::init(cx);
|
||||||
workspace::init(cx);
|
workspace::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
file_finder::init(cx);
|
file_finder::init(cx);
|
||||||
|
theme_picker::init(cx, &app_state);
|
||||||
|
|
||||||
let app_state = Arc::new(app_state);
|
|
||||||
cx.set_menus(menus::menus(&app_state.clone()));
|
cx.set_menus(menus::menus(&app_state.clone()));
|
||||||
|
|
||||||
if stdout_is_a_pty() {
|
if stdout_is_a_pty() {
|
||||||
|
|
|
@ -133,6 +133,18 @@ impl ThemeRegistry {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list(&self) -> impl Iterator<Item = String> {
|
||||||
|
self.assets.list("themes/").into_iter().filter_map(|path| {
|
||||||
|
let filename = path.strip_prefix("themes/")?;
|
||||||
|
let theme_name = filename.strip_suffix(".toml")?;
|
||||||
|
if theme_name.starts_with('_') {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(theme_name.to_string())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
|
pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
|
||||||
if let Some(theme) = self.themes.lock().get(name) {
|
if let Some(theme) = self.themes.lock().get(name) {
|
||||||
return Ok(theme.clone());
|
return Ok(theme.clone());
|
||||||
|
@ -497,8 +509,10 @@ mod tests {
|
||||||
fn test_parse_extended_theme() {
|
fn test_parse_extended_theme() {
|
||||||
let assets = TestAssets(&[
|
let assets = TestAssets(&[
|
||||||
(
|
(
|
||||||
"themes/base.toml",
|
"themes/_base.toml",
|
||||||
r#"
|
r#"
|
||||||
|
abstract = true
|
||||||
|
|
||||||
[ui]
|
[ui]
|
||||||
tab_background = 0x111111
|
tab_background = 0x111111
|
||||||
tab_text = "$variable_1"
|
tab_text = "$variable_1"
|
||||||
|
@ -511,7 +525,7 @@ mod tests {
|
||||||
(
|
(
|
||||||
"themes/light.toml",
|
"themes/light.toml",
|
||||||
r#"
|
r#"
|
||||||
extends = "base"
|
extends = "_base"
|
||||||
|
|
||||||
[variables]
|
[variables]
|
||||||
variable_1 = 0x333333
|
variable_1 = 0x333333
|
||||||
|
@ -524,6 +538,16 @@ mod tests {
|
||||||
background = 0x666666
|
background = 0x666666
|
||||||
"#,
|
"#,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"themes/dark.toml",
|
||||||
|
r#"
|
||||||
|
extends = "_base"
|
||||||
|
|
||||||
|
[variables]
|
||||||
|
variable_1 = 0x555555
|
||||||
|
variable_2 = 0x666666
|
||||||
|
"#,
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let registry = ThemeRegistry::new(assets);
|
let registry = ThemeRegistry::new(assets);
|
||||||
|
@ -533,6 +557,11 @@ mod tests {
|
||||||
assert_eq!(theme.ui.tab_text, ColorU::from_u32(0x333333ff));
|
assert_eq!(theme.ui.tab_text, ColorU::from_u32(0x333333ff));
|
||||||
assert_eq!(theme.editor.background, ColorU::from_u32(0x666666ff));
|
assert_eq!(theme.editor.background, ColorU::from_u32(0x666666ff));
|
||||||
assert_eq!(theme.editor.default_text, ColorU::from_u32(0x444444ff));
|
assert_eq!(theme.editor.default_text, ColorU::from_u32(0x444444ff));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
registry.list().collect::<Vec<_>>(),
|
||||||
|
&["light".to_string(), "dark".to_string()]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -585,5 +614,19 @@ mod tests {
|
||||||
Err(anyhow!("no such path {}", path))
|
Err(anyhow!("no such path {}", path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list(&self, prefix: &str) -> Vec<std::borrow::Cow<'static, str>> {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter_map(|(path, _)| {
|
||||||
|
if path.starts_with(prefix) {
|
||||||
|
Some(path.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
time::ReplicaId,
|
time::ReplicaId,
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
use futures::lock::Mutex;
|
||||||
use gpui::{AppContext, Entity, ModelHandle};
|
use gpui::{AppContext, Entity, ModelHandle};
|
||||||
use smol::channel;
|
use smol::channel;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -154,10 +155,11 @@ fn write_tree(path: &Path, tree: serde_json::Value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_app_state(cx: &AppContext) -> Arc<AppState> {
|
pub fn build_app_state(cx: &AppContext) -> Arc<AppState> {
|
||||||
let settings = settings::channel(&cx.font_cache()).unwrap().1;
|
let (settings_tx, settings) = settings::channel(&cx.font_cache()).unwrap();
|
||||||
let languages = Arc::new(LanguageRegistry::new());
|
let languages = Arc::new(LanguageRegistry::new());
|
||||||
let themes = ThemeRegistry::new(());
|
let themes = ThemeRegistry::new(());
|
||||||
Arc::new(AppState {
|
Arc::new(AppState {
|
||||||
|
settings_tx: Arc::new(Mutex::new(settings_tx)),
|
||||||
settings,
|
settings,
|
||||||
themes,
|
themes,
|
||||||
languages: languages.clone(),
|
languages: languages.clone(),
|
||||||
|
|
308
zed/src/theme_picker.rs
Normal file
308
zed/src/theme_picker.rs
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
use std::{cmp, sync::Arc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
editor::{self, Editor},
|
||||||
|
settings::ThemeRegistry,
|
||||||
|
workspace::Workspace,
|
||||||
|
worktree::fuzzy::{match_strings, StringMatch, StringMatchCandidate},
|
||||||
|
AppState, Settings,
|
||||||
|
};
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
use gpui::{
|
||||||
|
color::ColorF,
|
||||||
|
elements::{
|
||||||
|
Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, ParentElement,
|
||||||
|
UniformList, UniformListState,
|
||||||
|
},
|
||||||
|
fonts::{Properties, Weight},
|
||||||
|
geometry::vector::vec2f,
|
||||||
|
keymap::{self, Binding},
|
||||||
|
AppContext, Axis, Border, Element, ElementBox, Entity, MutableAppContext, RenderContext, View,
|
||||||
|
ViewContext, ViewHandle,
|
||||||
|
};
|
||||||
|
use postage::watch;
|
||||||
|
|
||||||
|
pub struct ThemePicker {
|
||||||
|
settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
||||||
|
settings: watch::Receiver<Settings>,
|
||||||
|
registry: Arc<ThemeRegistry>,
|
||||||
|
matches: Vec<StringMatch>,
|
||||||
|
query_buffer: ViewHandle<Editor>,
|
||||||
|
list_state: UniformListState,
|
||||||
|
selected_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext, app_state: &Arc<AppState>) {
|
||||||
|
cx.add_action("theme_picker:confirm", ThemePicker::confirm);
|
||||||
|
// cx.add_action("file_finder:select", ThemePicker::select);
|
||||||
|
cx.add_action("menu:select_prev", ThemePicker::select_prev);
|
||||||
|
cx.add_action("menu:select_next", ThemePicker::select_next);
|
||||||
|
cx.add_action("theme_picker:toggle", ThemePicker::toggle);
|
||||||
|
|
||||||
|
cx.add_bindings(vec![
|
||||||
|
Binding::new("cmd-k cmd-t", "theme_picker:toggle", None).with_arg(app_state.clone()),
|
||||||
|
Binding::new("escape", "theme_picker:toggle", Some("ThemePicker"))
|
||||||
|
.with_arg(app_state.clone()),
|
||||||
|
Binding::new("enter", "theme_picker:confirm", Some("ThemePicker")),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Event {
|
||||||
|
Dismissed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThemePicker {
|
||||||
|
fn new(
|
||||||
|
settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
||||||
|
settings: watch::Receiver<Settings>,
|
||||||
|
registry: Arc<ThemeRegistry>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let query_buffer = cx.add_view(|cx| Editor::single_line(settings.clone(), cx));
|
||||||
|
cx.subscribe_to_view(&query_buffer, Self::on_query_editor_event);
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
settings,
|
||||||
|
settings_tx,
|
||||||
|
registry,
|
||||||
|
query_buffer,
|
||||||
|
matches: Vec::new(),
|
||||||
|
list_state: Default::default(),
|
||||||
|
selected_index: 0,
|
||||||
|
};
|
||||||
|
this.update_matches(cx);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toggle(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
app_state: &Arc<AppState>,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
workspace.toggle_modal(cx, |cx, _| {
|
||||||
|
let picker = cx.add_view(|cx| {
|
||||||
|
Self::new(
|
||||||
|
app_state.settings_tx.clone(),
|
||||||
|
app_state.settings.clone(),
|
||||||
|
app_state.themes.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
cx.subscribe_to_view(&picker, Self::on_event);
|
||||||
|
picker
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(mat) = self.matches.get(self.selected_index) {
|
||||||
|
let settings_tx = self.settings_tx.clone();
|
||||||
|
if let Ok(theme) = self.registry.get(&mat.string) {
|
||||||
|
cx.foreground()
|
||||||
|
.spawn(async move {
|
||||||
|
settings_tx.lock().await.borrow_mut().theme = theme;
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cx.emit(Event::Dismissed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_prev(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
||||||
|
if self.selected_index > 0 {
|
||||||
|
self.selected_index -= 1;
|
||||||
|
}
|
||||||
|
self.list_state.scroll_to(self.selected_index);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_next(&mut self, _: &(), cx: &mut ViewContext<Self>) {
|
||||||
|
if self.selected_index + 1 < self.matches.len() {
|
||||||
|
self.selected_index += 1;
|
||||||
|
}
|
||||||
|
self.list_state.scroll_to(self.selected_index);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn select(&mut self, selected_index: &usize, cx: &mut ViewContext<Self>) {
|
||||||
|
// self.selected_index = *selected_index;
|
||||||
|
// self.confirm(&(), cx);
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn update_matches(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
let background = cx.background().clone();
|
||||||
|
let candidates = self
|
||||||
|
.registry
|
||||||
|
.list()
|
||||||
|
.map(|name| StringMatchCandidate {
|
||||||
|
char_bag: name.as_str().into(),
|
||||||
|
string: name,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let query = self.query_buffer.update(cx, |buffer, cx| buffer.text(cx));
|
||||||
|
|
||||||
|
self.matches = if query.is_empty() {
|
||||||
|
candidates
|
||||||
|
.into_iter()
|
||||||
|
.map(|candidate| StringMatch {
|
||||||
|
string: candidate.string,
|
||||||
|
positions: Vec::new(),
|
||||||
|
score: 0.0,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
smol::block_on(match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
false,
|
||||||
|
100,
|
||||||
|
&Default::default(),
|
||||||
|
background,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_event(
|
||||||
|
workspace: &mut Workspace,
|
||||||
|
_: ViewHandle<ThemePicker>,
|
||||||
|
event: &Event,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
Event::Dismissed => {
|
||||||
|
workspace.dismiss_modal(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_query_editor_event(
|
||||||
|
&mut self,
|
||||||
|
_: ViewHandle<Editor>,
|
||||||
|
event: &editor::Event,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
editor::Event::Edited => self.update_matches(cx),
|
||||||
|
editor::Event::Blurred => cx.emit(Event::Dismissed),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_matches(&self, cx: &RenderContext<Self>) -> ElementBox {
|
||||||
|
if self.matches.is_empty() {
|
||||||
|
let settings = self.settings.borrow();
|
||||||
|
return Container::new(
|
||||||
|
Label::new(
|
||||||
|
"No matches".into(),
|
||||||
|
settings.ui_font_family,
|
||||||
|
settings.ui_font_size,
|
||||||
|
)
|
||||||
|
.with_default_color(settings.theme.editor.default_text.0)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_margin_top(6.0)
|
||||||
|
.named("empty matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = cx.handle();
|
||||||
|
let list = UniformList::new(
|
||||||
|
self.list_state.clone(),
|
||||||
|
self.matches.len(),
|
||||||
|
move |mut range, items, cx| {
|
||||||
|
let cx = cx.as_ref();
|
||||||
|
let picker = handle.upgrade(cx).unwrap();
|
||||||
|
let picker = picker.read(cx);
|
||||||
|
let start = range.start;
|
||||||
|
range.end = cmp::min(range.end, picker.matches.len());
|
||||||
|
items.extend(
|
||||||
|
picker.matches[range]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(i, path_match)| picker.render_match(path_match, start + i)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Container::new(list.boxed())
|
||||||
|
.with_margin_top(6.0)
|
||||||
|
.named("matches")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_match(&self, theme_match: &StringMatch, index: usize) -> ElementBox {
|
||||||
|
let settings = self.settings.borrow();
|
||||||
|
let theme = &settings.theme.ui;
|
||||||
|
let bold = *Properties::new().weight(Weight::BOLD);
|
||||||
|
|
||||||
|
let mut container = Container::new(
|
||||||
|
Label::new(
|
||||||
|
theme_match.string.clone(),
|
||||||
|
settings.ui_font_family,
|
||||||
|
settings.ui_font_size,
|
||||||
|
)
|
||||||
|
.with_default_color(theme.modal_match_text.0)
|
||||||
|
.with_highlights(
|
||||||
|
theme.modal_match_text_highlight.0,
|
||||||
|
bold,
|
||||||
|
theme_match.positions.clone(),
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_uniform_padding(6.0)
|
||||||
|
.with_background_color(if index == self.selected_index {
|
||||||
|
theme.modal_match_background_active.0
|
||||||
|
} else {
|
||||||
|
theme.modal_match_background.0
|
||||||
|
});
|
||||||
|
|
||||||
|
if index == self.selected_index || index < self.matches.len() - 1 {
|
||||||
|
container = container.with_border(Border::bottom(1.0, theme.modal_match_border));
|
||||||
|
}
|
||||||
|
|
||||||
|
container.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity for ThemePicker {
|
||||||
|
type Event = Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View for ThemePicker {
|
||||||
|
fn ui_name() -> &'static str {
|
||||||
|
"ThemePicker"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, cx: &RenderContext<Self>) -> ElementBox {
|
||||||
|
let settings = self.settings.borrow();
|
||||||
|
|
||||||
|
Align::new(
|
||||||
|
ConstrainedBox::new(
|
||||||
|
Container::new(
|
||||||
|
Flex::new(Axis::Vertical)
|
||||||
|
.with_child(ChildView::new(self.query_buffer.id()).boxed())
|
||||||
|
.with_child(Expanded::new(1.0, self.render_matches(cx)).boxed())
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_margin_top(12.0)
|
||||||
|
.with_uniform_padding(6.0)
|
||||||
|
.with_corner_radius(6.0)
|
||||||
|
.with_background_color(settings.theme.ui.modal_background)
|
||||||
|
.with_shadow(vec2f(0., 4.), 12., ColorF::new(0.0, 0.0, 0.0, 0.5).to_u8())
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_max_width(600.0)
|
||||||
|
.with_max_height(400.0)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.top()
|
||||||
|
.named("theme picker")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.focus(&self.query_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keymap_context(&self, _: &AppContext) -> keymap::Context {
|
||||||
|
let mut cx = Self::default_keymap_context();
|
||||||
|
cx.set.insert("menu".into());
|
||||||
|
cx
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,8 +13,8 @@ use crate::{
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, ClipboardItem,
|
elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, ClipboardItem,
|
||||||
Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, Task, View,
|
Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, Task,
|
||||||
ViewContext, ViewHandle, WeakModelHandle,
|
View, ViewContext, ViewHandle, WeakModelHandle,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::error;
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
|
@ -879,7 +879,7 @@ impl View for Workspace {
|
||||||
"Workspace"
|
"Workspace"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, _: &AppContext) -> ElementBox {
|
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
|
||||||
let settings = self.settings.borrow();
|
let settings = self.settings.borrow();
|
||||||
Container::new(
|
Container::new(
|
||||||
Stack::new()
|
Stack::new()
|
||||||
|
@ -974,8 +974,8 @@ mod tests {
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(cx.window_ids().len(), 1);
|
assert_eq!(cx.window_ids().len(), 1);
|
||||||
let workspace_view_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
|
let workspace_1 = cx.root_view::<Workspace>(cx.window_ids()[0]).unwrap();
|
||||||
workspace_view_1.read_with(&cx, |workspace, _| {
|
workspace_1.read_with(&cx, |workspace, _| {
|
||||||
assert_eq!(workspace.worktrees().len(), 2)
|
assert_eq!(workspace.worktrees().len(), 2)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1380,9 +1380,9 @@ mod tests {
|
||||||
assert_eq!(pane2_item.entry_id(cx.as_ref()), Some(file1.clone()));
|
assert_eq!(pane2_item.entry_id(cx.as_ref()), Some(file1.clone()));
|
||||||
|
|
||||||
cx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
cx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
||||||
let workspace_view = workspace.read(cx);
|
let workspace = workspace.read(cx);
|
||||||
assert_eq!(workspace_view.panes.len(), 1);
|
assert_eq!(workspace.panes.len(), 1);
|
||||||
assert_eq!(workspace_view.active_pane(), &pane_1);
|
assert_eq!(workspace.active_pane(), &pane_1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
geometry::{rect::RectF, vector::vec2f},
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
keymap::Binding,
|
keymap::Binding,
|
||||||
AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext, ViewHandle,
|
AppContext, Border, Entity, MutableAppContext, Quad, RenderContext, View, ViewContext,
|
||||||
|
ViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
use std::{cmp, path::Path, sync::Arc};
|
use std::{cmp, path::Path, sync::Arc};
|
||||||
|
@ -371,7 +372,7 @@ impl View for Pane {
|
||||||
"Pane"
|
"Pane"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render<'a>(&self, cx: &AppContext) -> ElementBox {
|
fn render<'a>(&self, cx: &RenderContext<Self>) -> ElementBox {
|
||||||
if let Some(active_item) = self.active_item() {
|
if let Some(active_item) = self.active_item() {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(self.render_tabs(cx))
|
.with_child(self.render_tabs(cx))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod char_bag;
|
mod char_bag;
|
||||||
mod fuzzy;
|
pub(crate) mod fuzzy;
|
||||||
mod ignore;
|
mod ignore;
|
||||||
|
|
||||||
use self::{char_bag::CharBag, ignore::IgnoreStack};
|
use self::{char_bag::CharBag, ignore::IgnoreStack};
|
||||||
|
@ -2615,6 +2615,7 @@ mod tests {
|
||||||
|
|
||||||
tree.snapshot()
|
tree.snapshot()
|
||||||
});
|
});
|
||||||
|
let cancel_flag = Default::default();
|
||||||
let results = cx
|
let results = cx
|
||||||
.read(|cx| {
|
.read(|cx| {
|
||||||
match_paths(
|
match_paths(
|
||||||
|
@ -2624,7 +2625,7 @@ mod tests {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
10,
|
10,
|
||||||
Default::default(),
|
&cancel_flag,
|
||||||
cx.background().clone(),
|
cx.background().clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -2667,6 +2668,7 @@ mod tests {
|
||||||
assert_eq!(tree.file_count(), 0);
|
assert_eq!(tree.file_count(), 0);
|
||||||
tree.snapshot()
|
tree.snapshot()
|
||||||
});
|
});
|
||||||
|
let cancel_flag = Default::default();
|
||||||
let results = cx
|
let results = cx
|
||||||
.read(|cx| {
|
.read(|cx| {
|
||||||
match_paths(
|
match_paths(
|
||||||
|
@ -2676,7 +2678,7 @@ mod tests {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
10,
|
10,
|
||||||
Default::default(),
|
&cancel_flag,
|
||||||
cx.background().clone(),
|
cx.background().clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ use super::{char_bag::CharBag, EntryKind, Snapshot};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use gpui::executor;
|
use gpui::executor;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
cmp::{max, min, Ordering},
|
cmp::{max, min, Ordering},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::atomic::{self, AtomicBool},
|
sync::atomic::{self, AtomicBool},
|
||||||
|
@ -12,8 +13,31 @@ const BASE_DISTANCE_PENALTY: f64 = 0.6;
|
||||||
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
|
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
|
||||||
const MIN_DISTANCE_PENALTY: f64 = 0.2;
|
const MIN_DISTANCE_PENALTY: f64 = 0.2;
|
||||||
|
|
||||||
|
struct Matcher<'a> {
|
||||||
|
query: &'a [char],
|
||||||
|
lowercase_query: &'a [char],
|
||||||
|
query_char_bag: CharBag,
|
||||||
|
smart_case: bool,
|
||||||
|
max_results: usize,
|
||||||
|
min_score: f64,
|
||||||
|
match_positions: Vec<usize>,
|
||||||
|
last_positions: Vec<usize>,
|
||||||
|
score_matrix: Vec<Option<f64>>,
|
||||||
|
best_position_matrix: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Match: Ord {
|
||||||
|
fn score(&self) -> f64;
|
||||||
|
fn set_positions(&mut self, positions: Vec<usize>);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MatchCandidate {
|
||||||
|
fn has_chars(&self, bag: CharBag) -> bool;
|
||||||
|
fn to_string<'a>(&'a self) -> Cow<'a, str>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MatchCandidate<'a> {
|
pub struct PathMatchCandidate<'a> {
|
||||||
pub path: &'a Arc<Path>,
|
pub path: &'a Arc<Path>,
|
||||||
pub char_bag: CharBag,
|
pub char_bag: CharBag,
|
||||||
}
|
}
|
||||||
|
@ -27,6 +51,82 @@ pub struct PathMatch {
|
||||||
pub include_root_name: bool,
|
pub include_root_name: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct StringMatchCandidate {
|
||||||
|
pub string: String,
|
||||||
|
pub char_bag: CharBag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Match for PathMatch {
|
||||||
|
fn score(&self) -> f64 {
|
||||||
|
self.score
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_positions(&mut self, positions: Vec<usize>) {
|
||||||
|
self.positions = positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Match for StringMatch {
|
||||||
|
fn score(&self) -> f64 {
|
||||||
|
self.score
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_positions(&mut self, positions: Vec<usize>) {
|
||||||
|
self.positions = positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MatchCandidate for PathMatchCandidate<'a> {
|
||||||
|
fn has_chars(&self, bag: CharBag) -> bool {
|
||||||
|
self.char_bag.is_superset(bag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_string(&self) -> Cow<'a, str> {
|
||||||
|
self.path.to_string_lossy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MatchCandidate for &'a StringMatchCandidate {
|
||||||
|
fn has_chars(&self, bag: CharBag) -> bool {
|
||||||
|
self.char_bag.is_superset(bag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_string(&self) -> Cow<'a, str> {
|
||||||
|
self.string.as_str().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct StringMatch {
|
||||||
|
pub score: f64,
|
||||||
|
pub positions: Vec<usize>,
|
||||||
|
pub string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for StringMatch {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.score.eq(&other.score)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for StringMatch {}
|
||||||
|
|
||||||
|
impl PartialOrd for StringMatch {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for StringMatch {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.score
|
||||||
|
.partial_cmp(&other.score)
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
.then_with(|| self.string.cmp(&other.string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for PathMatch {
|
impl PartialEq for PathMatch {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.score.eq(&other.score)
|
self.score.eq(&other.score)
|
||||||
|
@ -51,6 +151,62 @@ impl Ord for PathMatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn match_strings(
|
||||||
|
candidates: &[StringMatchCandidate],
|
||||||
|
query: &str,
|
||||||
|
smart_case: bool,
|
||||||
|
max_results: usize,
|
||||||
|
cancel_flag: &AtomicBool,
|
||||||
|
background: Arc<executor::Background>,
|
||||||
|
) -> Vec<StringMatch> {
|
||||||
|
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
|
||||||
|
let query = query.chars().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let lowercase_query = &lowercase_query;
|
||||||
|
let query = &query;
|
||||||
|
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||||
|
|
||||||
|
let num_cpus = background.num_cpus().min(candidates.len());
|
||||||
|
let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
|
||||||
|
let mut segment_results = (0..num_cpus)
|
||||||
|
.map(|_| Vec::with_capacity(max_results))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
background
|
||||||
|
.scoped(|scope| {
|
||||||
|
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
|
||||||
|
let cancel_flag = &cancel_flag;
|
||||||
|
scope.spawn(async move {
|
||||||
|
let segment_start = segment_idx * segment_size;
|
||||||
|
let segment_end = segment_start + segment_size;
|
||||||
|
let mut matcher = Matcher::new(
|
||||||
|
query,
|
||||||
|
lowercase_query,
|
||||||
|
query_char_bag,
|
||||||
|
smart_case,
|
||||||
|
max_results,
|
||||||
|
);
|
||||||
|
matcher.match_strings(
|
||||||
|
&candidates[segment_start..segment_end],
|
||||||
|
results,
|
||||||
|
cancel_flag,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for segment_result in segment_results {
|
||||||
|
if results.is_empty() {
|
||||||
|
results = segment_result;
|
||||||
|
} else {
|
||||||
|
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn match_paths<'a, T>(
|
pub async fn match_paths<'a, T>(
|
||||||
snapshots: T,
|
snapshots: T,
|
||||||
query: &str,
|
query: &str,
|
||||||
|
@ -58,7 +214,7 @@ pub async fn match_paths<'a, T>(
|
||||||
include_ignored: bool,
|
include_ignored: bool,
|
||||||
smart_case: bool,
|
smart_case: bool,
|
||||||
max_results: usize,
|
max_results: usize,
|
||||||
cancel_flag: Arc<AtomicBool>,
|
cancel_flag: &AtomicBool,
|
||||||
background: Arc<executor::Background>,
|
background: Arc<executor::Background>,
|
||||||
) -> Vec<PathMatch>
|
) -> Vec<PathMatch>
|
||||||
where
|
where
|
||||||
|
@ -78,7 +234,7 @@ where
|
||||||
|
|
||||||
let lowercase_query = &lowercase_query;
|
let lowercase_query = &lowercase_query;
|
||||||
let query = &query;
|
let query = &query;
|
||||||
let query_chars = CharBag::from(&lowercase_query[..]);
|
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||||
|
|
||||||
let num_cpus = background.num_cpus().min(path_count);
|
let num_cpus = background.num_cpus().min(path_count);
|
||||||
let segment_size = (path_count + num_cpus - 1) / num_cpus;
|
let segment_size = (path_count + num_cpus - 1) / num_cpus;
|
||||||
|
@ -90,18 +246,16 @@ where
|
||||||
.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();
|
let snapshots = snapshots.clone();
|
||||||
let cancel_flag = &cancel_flag;
|
|
||||||
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;
|
||||||
|
let mut matcher = Matcher::new(
|
||||||
let mut min_score = 0.0;
|
query,
|
||||||
let mut last_positions = Vec::new();
|
lowercase_query,
|
||||||
last_positions.resize(query.len(), 0);
|
query_char_bag,
|
||||||
let mut match_positions = Vec::new();
|
smart_case,
|
||||||
match_positions.resize(query.len(), 0);
|
max_results,
|
||||||
let mut score_matrix = Vec::new();
|
);
|
||||||
let mut best_position_matrix = Vec::new();
|
|
||||||
|
|
||||||
let mut tree_start = 0;
|
let mut tree_start = 0;
|
||||||
for snapshot in snapshots {
|
for snapshot in snapshots {
|
||||||
|
@ -123,7 +277,7 @@ where
|
||||||
};
|
};
|
||||||
let paths = entries.map(|entry| {
|
let paths = entries.map(|entry| {
|
||||||
if let EntryKind::File(char_bag) = entry.kind {
|
if let EntryKind::File(char_bag) = entry.kind {
|
||||||
MatchCandidate {
|
PathMatchCandidate {
|
||||||
path: &entry.path,
|
path: &entry.path,
|
||||||
char_bag,
|
char_bag,
|
||||||
}
|
}
|
||||||
|
@ -132,21 +286,11 @@ where
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
match_single_tree_paths(
|
matcher.match_paths(
|
||||||
snapshot,
|
snapshot,
|
||||||
include_root_name,
|
include_root_name,
|
||||||
paths,
|
paths,
|
||||||
query,
|
|
||||||
lowercase_query,
|
|
||||||
query_chars,
|
|
||||||
smart_case,
|
|
||||||
results,
|
results,
|
||||||
max_results,
|
|
||||||
&mut min_score,
|
|
||||||
&mut match_positions,
|
|
||||||
&mut last_positions,
|
|
||||||
&mut score_matrix,
|
|
||||||
&mut best_position_matrix,
|
|
||||||
&cancel_flag,
|
&cancel_flag,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -171,26 +315,57 @@ where
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_single_tree_paths<'a>(
|
impl<'a> Matcher<'a> {
|
||||||
|
fn new(
|
||||||
|
query: &'a [char],
|
||||||
|
lowercase_query: &'a [char],
|
||||||
|
query_char_bag: CharBag,
|
||||||
|
smart_case: bool,
|
||||||
|
max_results: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
query,
|
||||||
|
lowercase_query,
|
||||||
|
query_char_bag,
|
||||||
|
min_score: 0.0,
|
||||||
|
last_positions: vec![0; query.len()],
|
||||||
|
match_positions: vec![0; query.len()],
|
||||||
|
score_matrix: Vec::new(),
|
||||||
|
best_position_matrix: Vec::new(),
|
||||||
|
smart_case,
|
||||||
|
max_results,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_strings(
|
||||||
|
&mut self,
|
||||||
|
candidates: &[StringMatchCandidate],
|
||||||
|
results: &mut Vec<StringMatch>,
|
||||||
|
cancel_flag: &AtomicBool,
|
||||||
|
) {
|
||||||
|
self.match_internal(
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
candidates.iter(),
|
||||||
|
results,
|
||||||
|
cancel_flag,
|
||||||
|
|candidate, score| StringMatch {
|
||||||
|
score,
|
||||||
|
positions: Vec::new(),
|
||||||
|
string: candidate.string.to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_paths(
|
||||||
|
&mut self,
|
||||||
snapshot: &Snapshot,
|
snapshot: &Snapshot,
|
||||||
include_root_name: bool,
|
include_root_name: bool,
|
||||||
path_entries: impl Iterator<Item = MatchCandidate<'a>>,
|
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
|
||||||
query: &[char],
|
|
||||||
lowercase_query: &[char],
|
|
||||||
query_chars: CharBag,
|
|
||||||
smart_case: bool,
|
|
||||||
results: &mut Vec<PathMatch>,
|
results: &mut Vec<PathMatch>,
|
||||||
max_results: usize,
|
|
||||||
min_score: &mut f64,
|
|
||||||
match_positions: &mut Vec<usize>,
|
|
||||||
last_positions: &mut Vec<usize>,
|
|
||||||
score_matrix: &mut Vec<Option<f64>>,
|
|
||||||
best_position_matrix: &mut Vec<usize>,
|
|
||||||
cancel_flag: &AtomicBool,
|
cancel_flag: &AtomicBool,
|
||||||
) {
|
) {
|
||||||
let mut path_chars = Vec::new();
|
let tree_id = snapshot.id;
|
||||||
let mut lowercase_path_chars = Vec::new();
|
|
||||||
|
|
||||||
let prefix = if include_root_name {
|
let prefix = if include_root_name {
|
||||||
snapshot.root_name()
|
snapshot.root_name()
|
||||||
} else {
|
} else {
|
||||||
|
@ -202,9 +377,39 @@ fn match_single_tree_paths<'a>(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.to_ascii_lowercase())
|
.map(|c| c.to_ascii_lowercase())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
self.match_internal(
|
||||||
|
&prefix,
|
||||||
|
&lowercase_prefix,
|
||||||
|
path_entries,
|
||||||
|
results,
|
||||||
|
cancel_flag,
|
||||||
|
|candidate, score| PathMatch {
|
||||||
|
score,
|
||||||
|
tree_id,
|
||||||
|
positions: Vec::new(),
|
||||||
|
path: candidate.path.clone(),
|
||||||
|
include_root_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for candidate in path_entries {
|
fn match_internal<C: MatchCandidate, R, F>(
|
||||||
if !candidate.char_bag.is_superset(query_chars) {
|
&mut self,
|
||||||
|
prefix: &[char],
|
||||||
|
lowercase_prefix: &[char],
|
||||||
|
candidates: impl Iterator<Item = C>,
|
||||||
|
results: &mut Vec<R>,
|
||||||
|
cancel_flag: &AtomicBool,
|
||||||
|
build_match: F,
|
||||||
|
) where
|
||||||
|
R: Match,
|
||||||
|
F: Fn(&C, f64) -> R,
|
||||||
|
{
|
||||||
|
let mut candidate_chars = Vec::new();
|
||||||
|
let mut lowercase_candidate_chars = Vec::new();
|
||||||
|
|
||||||
|
for candidate in candidates {
|
||||||
|
if !candidate.has_chars(self.query_char_bag) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,116 +417,80 @@ fn match_single_tree_paths<'a>(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
path_chars.clear();
|
candidate_chars.clear();
|
||||||
lowercase_path_chars.clear();
|
lowercase_candidate_chars.clear();
|
||||||
for c in candidate.path.to_string_lossy().chars() {
|
for c in candidate.to_string().chars() {
|
||||||
path_chars.push(c);
|
candidate_chars.push(c);
|
||||||
lowercase_path_chars.push(c.to_ascii_lowercase());
|
lowercase_candidate_chars.push(c.to_ascii_lowercase());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !find_last_positions(
|
if !self.find_last_positions(&lowercase_prefix, &lowercase_candidate_chars) {
|
||||||
last_positions,
|
|
||||||
&lowercase_prefix,
|
|
||||||
&lowercase_path_chars,
|
|
||||||
&lowercase_query[..],
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matrix_len = query.len() * (path_chars.len() + prefix.len());
|
let matrix_len = self.query.len() * (prefix.len() + candidate_chars.len());
|
||||||
score_matrix.clear();
|
self.score_matrix.clear();
|
||||||
score_matrix.resize(matrix_len, None);
|
self.score_matrix.resize(matrix_len, None);
|
||||||
best_position_matrix.clear();
|
self.best_position_matrix.clear();
|
||||||
best_position_matrix.resize(matrix_len, 0);
|
self.best_position_matrix.resize(matrix_len, 0);
|
||||||
|
|
||||||
let score = score_match(
|
let score = self.score_match(
|
||||||
&query[..],
|
&candidate_chars,
|
||||||
&lowercase_query[..],
|
&lowercase_candidate_chars,
|
||||||
&path_chars,
|
|
||||||
&lowercase_path_chars,
|
|
||||||
&prefix,
|
&prefix,
|
||||||
&lowercase_prefix,
|
&lowercase_prefix,
|
||||||
smart_case,
|
|
||||||
&last_positions,
|
|
||||||
score_matrix,
|
|
||||||
best_position_matrix,
|
|
||||||
match_positions,
|
|
||||||
*min_score,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if score > 0.0 {
|
if score > 0.0 {
|
||||||
let mat = PathMatch {
|
let mut mat = build_match(&candidate, score);
|
||||||
tree_id: snapshot.id,
|
|
||||||
path: candidate.path.clone(),
|
|
||||||
score,
|
|
||||||
positions: match_positions.clone(),
|
|
||||||
include_root_name,
|
|
||||||
};
|
|
||||||
if let Err(i) = results.binary_search_by(|m| mat.cmp(&m)) {
|
if let Err(i) = results.binary_search_by(|m| mat.cmp(&m)) {
|
||||||
if results.len() < max_results {
|
if results.len() < self.max_results {
|
||||||
|
mat.set_positions(self.match_positions.clone());
|
||||||
results.insert(i, mat);
|
results.insert(i, mat);
|
||||||
} else if i < results.len() {
|
} else if i < results.len() {
|
||||||
results.pop();
|
results.pop();
|
||||||
|
mat.set_positions(self.match_positions.clone());
|
||||||
results.insert(i, mat);
|
results.insert(i, mat);
|
||||||
}
|
}
|
||||||
if results.len() == max_results {
|
if results.len() == self.max_results {
|
||||||
*min_score = results.last().unwrap().score;
|
self.min_score = results.last().unwrap().score();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn find_last_positions(
|
fn find_last_positions(&mut self, prefix: &[char], path: &[char]) -> bool {
|
||||||
last_positions: &mut Vec<usize>,
|
|
||||||
prefix: &[char],
|
|
||||||
path: &[char],
|
|
||||||
query: &[char],
|
|
||||||
) -> bool {
|
|
||||||
let mut path = path.iter();
|
let mut path = path.iter();
|
||||||
let mut prefix_iter = prefix.iter();
|
let mut prefix_iter = prefix.iter();
|
||||||
for (i, char) in query.iter().enumerate().rev() {
|
for (i, char) in self.query.iter().enumerate().rev() {
|
||||||
if let Some(j) = path.rposition(|c| c == char) {
|
if let Some(j) = path.rposition(|c| c == char) {
|
||||||
last_positions[i] = j + prefix.len();
|
self.last_positions[i] = j + prefix.len();
|
||||||
} else if let Some(j) = prefix_iter.rposition(|c| c == char) {
|
} else if let Some(j) = prefix_iter.rposition(|c| c == char) {
|
||||||
last_positions[i] = j;
|
self.last_positions[i] = j;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn score_match(
|
fn score_match(
|
||||||
query: &[char],
|
&mut self,
|
||||||
query_cased: &[char],
|
|
||||||
path: &[char],
|
path: &[char],
|
||||||
path_cased: &[char],
|
path_cased: &[char],
|
||||||
prefix: &[char],
|
prefix: &[char],
|
||||||
lowercase_prefix: &[char],
|
lowercase_prefix: &[char],
|
||||||
smart_case: bool,
|
) -> f64 {
|
||||||
last_positions: &[usize],
|
let score = self.recursive_score_match(
|
||||||
score_matrix: &mut [Option<f64>],
|
|
||||||
best_position_matrix: &mut [usize],
|
|
||||||
match_positions: &mut [usize],
|
|
||||||
min_score: f64,
|
|
||||||
) -> f64 {
|
|
||||||
let score = recursive_score_match(
|
|
||||||
query,
|
|
||||||
query_cased,
|
|
||||||
path,
|
path,
|
||||||
path_cased,
|
path_cased,
|
||||||
prefix,
|
prefix,
|
||||||
lowercase_prefix,
|
lowercase_prefix,
|
||||||
smart_case,
|
|
||||||
last_positions,
|
|
||||||
score_matrix,
|
|
||||||
best_position_matrix,
|
|
||||||
min_score,
|
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
query.len() as f64,
|
self.query.len() as f64,
|
||||||
) * query.len() as f64;
|
) * self.query.len() as f64;
|
||||||
|
|
||||||
if score <= 0.0 {
|
if score <= 0.0 {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
@ -331,8 +500,8 @@ fn score_match(
|
||||||
let mut cur_start = 0;
|
let mut cur_start = 0;
|
||||||
let mut byte_ix = 0;
|
let mut byte_ix = 0;
|
||||||
let mut char_ix = 0;
|
let mut char_ix = 0;
|
||||||
for i in 0..query.len() {
|
for i in 0..self.query.len() {
|
||||||
let match_char_ix = best_position_matrix[i * path_len + cur_start];
|
let match_char_ix = self.best_position_matrix[i * path_len + cur_start];
|
||||||
while char_ix < match_char_ix {
|
while char_ix < match_char_ix {
|
||||||
let ch = prefix
|
let ch = prefix
|
||||||
.get(char_ix)
|
.get(char_ix)
|
||||||
|
@ -342,43 +511,37 @@ fn score_match(
|
||||||
char_ix += 1;
|
char_ix += 1;
|
||||||
}
|
}
|
||||||
cur_start = match_char_ix + 1;
|
cur_start = match_char_ix + 1;
|
||||||
match_positions[i] = byte_ix;
|
self.match_positions[i] = byte_ix;
|
||||||
}
|
}
|
||||||
|
|
||||||
score
|
score
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recursive_score_match(
|
fn recursive_score_match(
|
||||||
query: &[char],
|
&mut self,
|
||||||
query_cased: &[char],
|
|
||||||
path: &[char],
|
path: &[char],
|
||||||
path_cased: &[char],
|
path_cased: &[char],
|
||||||
prefix: &[char],
|
prefix: &[char],
|
||||||
lowercase_prefix: &[char],
|
lowercase_prefix: &[char],
|
||||||
smart_case: bool,
|
|
||||||
last_positions: &[usize],
|
|
||||||
score_matrix: &mut [Option<f64>],
|
|
||||||
best_position_matrix: &mut [usize],
|
|
||||||
min_score: f64,
|
|
||||||
query_idx: usize,
|
query_idx: usize,
|
||||||
path_idx: usize,
|
path_idx: usize,
|
||||||
cur_score: f64,
|
cur_score: f64,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
if query_idx == query.len() {
|
if query_idx == self.query.len() {
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let path_len = prefix.len() + path.len();
|
let path_len = prefix.len() + path.len();
|
||||||
|
|
||||||
if let Some(memoized) = score_matrix[query_idx * path_len + path_idx] {
|
if let Some(memoized) = self.score_matrix[query_idx * path_len + path_idx] {
|
||||||
return memoized;
|
return memoized;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut score = 0.0;
|
let mut score = 0.0;
|
||||||
let mut best_position = 0;
|
let mut best_position = 0;
|
||||||
|
|
||||||
let query_char = query_cased[query_idx];
|
let query_char = self.lowercase_query[query_idx];
|
||||||
let limit = last_positions[query_idx];
|
let limit = self.last_positions[query_idx];
|
||||||
|
|
||||||
let mut last_slash = 0;
|
let mut last_slash = 0;
|
||||||
for j in path_idx..=limit {
|
for j in path_idx..=limit {
|
||||||
|
@ -429,7 +592,7 @@ fn recursive_score_match(
|
||||||
// Apply a severe penalty if the case doesn't match.
|
// Apply a severe penalty if the case doesn't match.
|
||||||
// This will make the exact matches have higher score than the case-insensitive and the
|
// This will make the exact matches have higher score than the case-insensitive and the
|
||||||
// path insensitive matches.
|
// path insensitive matches.
|
||||||
if (smart_case || curr == '/') && query[query_idx] != curr {
|
if (self.smart_case || curr == '/') && self.query[query_idx] != curr {
|
||||||
char_score *= 0.001;
|
char_score *= 0.001;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,10 +604,10 @@ fn recursive_score_match(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut next_score = 1.0;
|
let mut next_score = 1.0;
|
||||||
if min_score > 0.0 {
|
if self.min_score > 0.0 {
|
||||||
next_score = cur_score * multiplier;
|
next_score = cur_score * multiplier;
|
||||||
// Scores only decrease. If we can't pass the previous best, bail
|
// Scores only decrease. If we can't pass the previous best, bail
|
||||||
if next_score < min_score {
|
if next_score < self.min_score {
|
||||||
// Ensure that score is non-zero so we use it in the memo table.
|
// Ensure that score is non-zero so we use it in the memo table.
|
||||||
if score == 0.0 {
|
if score == 0.0 {
|
||||||
score = 1e-18;
|
score = 1e-18;
|
||||||
|
@ -453,18 +616,11 @@ fn recursive_score_match(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_score = recursive_score_match(
|
let new_score = self.recursive_score_match(
|
||||||
query,
|
|
||||||
query_cased,
|
|
||||||
path,
|
path,
|
||||||
path_cased,
|
path_cased,
|
||||||
prefix,
|
prefix,
|
||||||
lowercase_prefix,
|
lowercase_prefix,
|
||||||
smart_case,
|
|
||||||
last_positions,
|
|
||||||
score_matrix,
|
|
||||||
best_position_matrix,
|
|
||||||
min_score,
|
|
||||||
query_idx + 1,
|
query_idx + 1,
|
||||||
j + 1,
|
j + 1,
|
||||||
next_score,
|
next_score,
|
||||||
|
@ -482,11 +638,12 @@ fn recursive_score_match(
|
||||||
}
|
}
|
||||||
|
|
||||||
if best_position != 0 {
|
if best_position != 0 {
|
||||||
best_position_matrix[query_idx * path_len + path_idx] = best_position;
|
self.best_position_matrix[query_idx * path_len + path_idx] = best_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
score_matrix[query_idx * path_len + path_idx] = Some(score);
|
self.score_matrix[query_idx * path_len + path_idx] = Some(score);
|
||||||
score
|
score
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -496,34 +653,22 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_last_positions() {
|
fn test_get_last_positions() {
|
||||||
let mut last_positions = vec![0; 2];
|
let mut query: &[char] = &['d', 'c'];
|
||||||
let result = find_last_positions(
|
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||||
&mut last_positions,
|
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||||
&['a', 'b', 'c'],
|
|
||||||
&['b', 'd', 'e', 'f'],
|
|
||||||
&['d', 'c'],
|
|
||||||
);
|
|
||||||
assert_eq!(result, false);
|
assert_eq!(result, false);
|
||||||
|
|
||||||
last_positions.resize(2, 0);
|
query = &['c', 'd'];
|
||||||
let result = find_last_positions(
|
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||||
&mut last_positions,
|
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
|
||||||
&['a', 'b', 'c'],
|
|
||||||
&['b', 'd', 'e', 'f'],
|
|
||||||
&['c', 'd'],
|
|
||||||
);
|
|
||||||
assert_eq!(result, true);
|
assert_eq!(result, true);
|
||||||
assert_eq!(last_positions, vec![2, 4]);
|
assert_eq!(matcher.last_positions, vec![2, 4]);
|
||||||
|
|
||||||
last_positions.resize(4, 0);
|
query = &['z', '/', 'z', 'f'];
|
||||||
let result = find_last_positions(
|
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
|
||||||
&mut last_positions,
|
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
|
||||||
&['z', 'e', 'd', '/'],
|
|
||||||
&['z', 'e', 'd', '/', 'f'],
|
|
||||||
&['z', '/', 'z', 'f'],
|
|
||||||
);
|
|
||||||
assert_eq!(result, true);
|
assert_eq!(result, true);
|
||||||
assert_eq!(last_positions, vec![0, 3, 4, 8]);
|
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -604,20 +749,17 @@ mod tests {
|
||||||
for (i, path) in paths.iter().enumerate() {
|
for (i, path) in paths.iter().enumerate() {
|
||||||
let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
|
let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
|
||||||
let char_bag = CharBag::from(lowercase_path.as_slice());
|
let char_bag = CharBag::from(lowercase_path.as_slice());
|
||||||
path_entries.push(MatchCandidate {
|
path_entries.push(PathMatchCandidate {
|
||||||
char_bag,
|
char_bag,
|
||||||
path: path_arcs.get(i).unwrap(),
|
path: path_arcs.get(i).unwrap(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut match_positions = Vec::new();
|
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
|
||||||
let mut last_positions = Vec::new();
|
|
||||||
match_positions.resize(query.len(), 0);
|
|
||||||
last_positions.resize(query.len(), 0);
|
|
||||||
|
|
||||||
let cancel_flag = AtomicBool::new(false);
|
let cancel_flag = AtomicBool::new(false);
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
match_single_tree_paths(
|
matcher.match_paths(
|
||||||
&Snapshot {
|
&Snapshot {
|
||||||
id: 0,
|
id: 0,
|
||||||
scan_id: 0,
|
scan_id: 0,
|
||||||
|
@ -632,17 +774,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
path_entries.into_iter(),
|
path_entries.into_iter(),
|
||||||
&query[..],
|
|
||||||
&lowercase_query[..],
|
|
||||||
query_chars,
|
|
||||||
smart_case,
|
|
||||||
&mut results,
|
&mut results,
|
||||||
100,
|
|
||||||
&mut 0.0,
|
|
||||||
&mut match_positions,
|
|
||||||
&mut last_positions,
|
|
||||||
&mut Vec::new(),
|
|
||||||
&mut Vec::new(),
|
|
||||||
&cancel_flag,
|
&cancel_flag,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue