mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +03:00
Merge branch 'main' into chat-again
This commit is contained in:
commit
d97c9d8dfd
10
README.md
10
README.md
@ -12,16 +12,12 @@ Welcome to Zed, a lightning-fast, collaborative code editor that makes your drea
|
||||
```
|
||||
sudo xcodebuild -license
|
||||
```
|
||||
|
||||
* Install rustup (rust, cargo, etc.)
|
||||
```
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
```
|
||||
|
||||
* Install homebrew and node
|
||||
* Install homebrew, node and rustup-init (rutup, rust, cargo, etc.)
|
||||
```
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
brew install node
|
||||
brew install node rustup-init
|
||||
rustup-init # follow the installation steps
|
||||
```
|
||||
|
||||
* Install postgres and configure the database
|
||||
|
5
assets/icons/select-all.svg
Normal file
5
assets/icons/select-all.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 7V9.5M9.5 12V9.5M12 9.5H9.5M7 9.5H9.5M9.5 9.5L11.1667 7.83333M9.5 9.5L7.83333 11.1667M9.5 9.5L11.1667 11.1667M9.5 9.5L7.83333 7.83333" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2.19366 3.84943C2.19188 4.26418 2.32864 4.59864 2.60673 4.84707C2.88052 5.09166 3.25136 5.26933 3.71609 5.3824C3.71616 5.38242 3.71623 5.38243 3.7163 5.38245L4.30919 5.53134L4.30919 5.53134L4.30965 5.53145C4.50649 5.57891 4.67124 5.63133 4.80447 5.68843L4.80469 5.68852C4.93838 5.74508 5.03564 5.81206 5.10001 5.8877L5.10001 5.8877L5.10041 5.88816C5.16432 5.96142 5.19716 6.05222 5.19716 6.16389C5.19716 6.28412 5.1609 6.38933 5.0882 6.48141C5.01496 6.57418 4.91031 6.64838 4.77141 6.70259L4.77121 6.70266C4.63472 6.75659 4.47185 6.7843 4.28146 6.7843C4.08801 6.7843 3.91607 6.75496 3.76491 6.69726C3.61654 6.6382 3.49924 6.55209 3.41132 6.43942C3.3502 6.35821 3.30747 6.26204 3.28375 6.14992C3.26238 6.04888 3.1772 5.96225 3.06518 5.96225H2.26366C2.14682 5.96225 2.04842 6.05919 2.0592 6.18012C2.08842 6.50802 2.1826 6.79102 2.34331 7.02735L2.34352 7.02767C2.53217 7.30057 2.79377 7.50587 3.12633 7.64399L3.12642 7.64402C3.46009 7.78185 3.84993 7.85 4.29476 7.85C4.74293 7.85 5.12859 7.7828 5.45023 7.64651L5.45036 7.64646C5.77328 7.50857 6.02259 7.31417 6.19551 7.06217C6.37037 6.80817 6.4579 6.50901 6.45972 6.16682L6.45972 6.16616C6.4579 5.9333 6.41513 5.72482 6.33012 5.54178C6.2474 5.35987 6.13061 5.20175 5.98007 5.06773C5.83038 4.93448 5.65389 4.82273 5.4511 4.7322C5.24919 4.64206 5.02795 4.57016 4.78757 4.51632L4.29841 4.39935L4.29841 4.39934L4.29771 4.39919C4.18081 4.37301 4.07116 4.34168 3.9687 4.30523C3.86715 4.26734 3.77847 4.22375 3.70232 4.17471C3.62796 4.12508 3.57037 4.06717 3.52849 4.00124C3.49012 3.93815 3.47157 3.86312 3.47481 3.77407L3.47484 3.77407V3.77225C3.47484 3.66563 3.50527 3.57146 3.56612 3.48808C3.6287 3.40475 3.71977 3.33801 3.84235 3.28931L3.84235 3.28932L3.84289 3.28909C3.96465 3.23906 4.1165 3.21304 4.30008 3.21304C4.57006 3.21304 4.77746 3.27105 4.92754 3.38154C5.04235 3.46608 5.11838 3.57594 5.15673 3.71259C5.18352 3.80802 5.26636 3.89142 5.37611 3.89142H6.17259C6.28852 3.89142 6.38806 3.7953 6.37515 3.67382C6.34686 3.4077 6.26051 3.16831 6.1158 2.95658C5.94159 2.70169 5.6982 2.50368 5.38762 2.36201L5.36687 2.4075M2.19366 3.84943C2.19187 3.51004 2.28242 3.21139 2.46644 2.9556L2.46658 2.9554C2.65148 2.70093 2.90447 2.50326 3.22368 2.36179C3.54316 2.2202 3.90494 2.15 4.30807 2.15C4.71809 2.15 5.07841 2.22014 5.38773 2.36206L5.36687 2.4075M2.19366 3.84943C2.19366 3.84951 2.19366 3.84959 2.19366 3.84967L2.24366 3.8494L2.19366 3.84918C2.19366 3.84926 2.19366 3.84935 2.19366 3.84943ZM5.36687 2.4075C5.06537 2.26917 4.71244 2.2 4.30807 2.2C3.91079 2.2 3.55608 2.26917 3.24394 2.4075C2.93179 2.54584 2.68616 2.73827 2.50703 2.9848L3.82389 3.24285L3.82389 3.24285C3.95336 3.18964 4.11209 3.16304 4.30008 3.16304C4.57676 3.16304 4.79579 3.22245 4.95718 3.34128C5.08094 3.43239 5.1635 3.55166 5.20487 3.69908C5.2271 3.77827 5.29386 3.84142 5.37611 3.84142H6.17259C6.26198 3.84142 6.33488 3.76799 6.32543 3.6791C6.29797 3.4208 6.21433 3.18936 6.07452 2.9848C5.90603 2.73827 5.67015 2.54584 5.36687 2.4075ZM4.78958 6.74917C4.64593 6.80592 4.47655 6.8343 4.28146 6.8343C4.08283 6.8343 3.90458 6.80415 3.74674 6.74384C3.59067 6.68177 3.46563 6.59043 3.37163 6.46983L4.78958 6.74917ZM4.78958 6.74917C4.93502 6.69241 5.04764 6.61349 5.12745 6.5124M4.78958 6.74917L5.12745 6.5124M5.12745 6.5124C5.20726 6.4113 5.24716 6.29514 5.24716 6.16389M5.12745 6.5124L5.24716 6.16389M5.24716 6.16389C5.24716 6.04152 5.2108 5.93865 5.13809 5.85529L5.24716 6.16389Z" fill="#687076" stroke="#687076" stroke-width="0.1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@ -16,7 +16,7 @@ use language::{
|
||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
|
||||
SelectionGoal,
|
||||
};
|
||||
use project::{FormatTrigger, Item as _, Project, ProjectPath};
|
||||
use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath};
|
||||
use rpc::proto::{self, update_view};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@ -26,6 +26,7 @@ use std::{
|
||||
iter,
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use text::Selection;
|
||||
use util::{
|
||||
@ -978,7 +979,26 @@ impl SearchableItem for Editor {
|
||||
}
|
||||
self.change_selections(None, cx, |s| s.select_ranges(ranges));
|
||||
}
|
||||
fn replace(
|
||||
&mut self,
|
||||
identifier: &Self::Match,
|
||||
query: &SearchQuery,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let text = self.buffer.read(cx);
|
||||
let text = text.snapshot(cx);
|
||||
let text = text.text_for_range(identifier.clone()).collect::<Vec<_>>();
|
||||
let text: Cow<_> = if text.len() == 1 {
|
||||
text.first().cloned().unwrap().into()
|
||||
} else {
|
||||
let joined_chunks = text.join("");
|
||||
joined_chunks.into()
|
||||
};
|
||||
|
||||
if let Some(replacement) = query.replacement(&text) {
|
||||
self.edit([(identifier.clone(), Arc::from(&*replacement))], cx);
|
||||
}
|
||||
}
|
||||
fn match_index_for_direction(
|
||||
&mut self,
|
||||
matches: &Vec<Range<Anchor>>,
|
||||
@ -1030,7 +1050,7 @@ impl SearchableItem for Editor {
|
||||
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Range<Anchor>>> {
|
||||
let buffer = self.buffer().read(cx).snapshot(cx);
|
||||
|
@ -13,7 +13,7 @@ use gpui::{
|
||||
use isahc::Request;
|
||||
use language::Buffer;
|
||||
use postage::prelude::Stream;
|
||||
use project::Project;
|
||||
use project::{search::SearchQuery, Project};
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use smallvec::SmallVec;
|
||||
@ -418,10 +418,13 @@ impl SearchableItem for FeedbackEditor {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.select_matches(matches, cx))
|
||||
}
|
||||
|
||||
fn replace(&mut self, matches: &Self::Match, query: &SearchQuery, cx: &mut ViewContext<Self>) {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.replace(matches, query, cx));
|
||||
}
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Self::Match>> {
|
||||
self.editor
|
||||
|
@ -28,24 +28,24 @@ fn generate_methods() -> Vec<TokenStream2> {
|
||||
let mut methods = Vec::new();
|
||||
|
||||
for (prefix, auto_allowed, fields) in box_prefixes() {
|
||||
for (suffix, length_tokens) in box_suffixes() {
|
||||
for (suffix, length_tokens, doc_string) in box_suffixes() {
|
||||
if auto_allowed || suffix != "auto" {
|
||||
let method = generate_method(prefix, suffix, &fields, length_tokens);
|
||||
let method = generate_method(prefix, suffix, &fields, length_tokens, doc_string);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in corner_prefixes() {
|
||||
for (suffix, radius_tokens) in corner_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, radius_tokens);
|
||||
for (suffix, radius_tokens, doc_string) in corner_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, radius_tokens, doc_string);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
|
||||
for (prefix, fields) in border_prefixes() {
|
||||
for (suffix, width_tokens) in border_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, width_tokens);
|
||||
for (suffix, width_tokens, doc_string) in border_suffixes() {
|
||||
let method = generate_method(prefix, suffix, &fields, width_tokens, doc_string);
|
||||
methods.push(method);
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,7 @@ fn generate_method(
|
||||
suffix: &'static str,
|
||||
fields: &Vec<TokenStream2>,
|
||||
length_tokens: TokenStream2,
|
||||
doc_string: &'static str,
|
||||
) -> TokenStream2 {
|
||||
let method_name = if suffix.is_empty() {
|
||||
format_ident!("{}", prefix)
|
||||
@ -75,6 +76,7 @@ fn generate_method(
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let method = quote! {
|
||||
#[doc = #doc_string]
|
||||
fn #method_name(mut self) -> Self where Self: std::marker::Sized {
|
||||
let mut style = self.declared_style();
|
||||
#(#field_assignments)*
|
||||
@ -160,55 +162,52 @@ fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
|
||||
]
|
||||
}
|
||||
|
||||
fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
|
||||
vec![
|
||||
("0", quote! { pixels(0.) }),
|
||||
("0p5", quote! { rems(0.125) }),
|
||||
("1", quote! { rems(0.25) }),
|
||||
("1p5", quote! { rems(0.375) }),
|
||||
("2", quote! { rems(0.5) }),
|
||||
("2p5", quote! { rems(0.625) }),
|
||||
("3", quote! { rems(0.75) }),
|
||||
("3p5", quote! { rems(0.875) }),
|
||||
("4", quote! { rems(1.) }),
|
||||
("5", quote! { rems(1.25) }),
|
||||
("6", quote! { rems(1.5) }),
|
||||
("7", quote! { rems(1.75) }),
|
||||
("8", quote! { rems(2.0) }),
|
||||
("9", quote! { rems(2.25) }),
|
||||
("10", quote! { rems(2.5) }),
|
||||
("11", quote! { rems(2.75) }),
|
||||
("12", quote! { rems(3.) }),
|
||||
("16", quote! { rems(4.) }),
|
||||
("20", quote! { rems(5.) }),
|
||||
("24", quote! { rems(6.) }),
|
||||
("32", quote! { rems(8.) }),
|
||||
("40", quote! { rems(10.) }),
|
||||
("48", quote! { rems(12.) }),
|
||||
("56", quote! { rems(14.) }),
|
||||
("64", quote! { rems(16.) }),
|
||||
("72", quote! { rems(18.) }),
|
||||
("80", quote! { rems(20.) }),
|
||||
("96", quote! { rems(24.) }),
|
||||
("auto", quote! { auto() }),
|
||||
("px", quote! { pixels(1.) }),
|
||||
("full", quote! { relative(1.) }),
|
||||
("1_2", quote! { relative(0.5) }),
|
||||
("1_3", quote! { relative(1./3.) }),
|
||||
("2_3", quote! { relative(2./3.) }),
|
||||
("1_4", quote! { relative(0.25) }),
|
||||
("2_4", quote! { relative(0.5) }),
|
||||
("3_4", quote! { relative(0.75) }),
|
||||
("1_5", quote! { relative(0.2) }),
|
||||
("2_5", quote! { relative(0.4) }),
|
||||
("3_5", quote! { relative(0.6) }),
|
||||
("4_5", quote! { relative(0.8) }),
|
||||
("1_6", quote! { relative(1./6.) }),
|
||||
("5_6", quote! { relative(5./6.) }),
|
||||
("1_12", quote! { relative(1./12.) }),
|
||||
// ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
|
||||
// ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
|
||||
// ("screen", quote! { DefiniteLength::Vh(100.0) }),
|
||||
("0", quote! { pixels(0.) }, "0px"),
|
||||
("0p5", quote! { rems(0.125) }, "2px (0.125rem)"),
|
||||
("1", quote! { rems(0.25) }, "4px (0.25rem)"),
|
||||
("1p5", quote! { rems(0.375) }, "6px (0.375rem)"),
|
||||
("2", quote! { rems(0.5) }, "8px (0.5rem)"),
|
||||
("2p5", quote! { rems(0.625) }, "10px (0.625rem)"),
|
||||
("3", quote! { rems(0.75) }, "12px (0.75rem)"),
|
||||
("3p5", quote! { rems(0.875) }, "14px (0.875rem)"),
|
||||
("4", quote! { rems(1.) }, "16px (1rem)"),
|
||||
("5", quote! { rems(1.25) }, "20px (1.25rem)"),
|
||||
("6", quote! { rems(1.5) }, "24px (1.5rem)"),
|
||||
("7", quote! { rems(1.75) }, "28px (1.75rem)"),
|
||||
("8", quote! { rems(2.0) }, "32px (2rem)"),
|
||||
("9", quote! { rems(2.25) }, "36px (2.25rem)"),
|
||||
("10", quote! { rems(2.5) }, "40px (2.5rem)"),
|
||||
("11", quote! { rems(2.75) }, "44px (2.75rem)"),
|
||||
("12", quote! { rems(3.) }, "48px (3rem)"),
|
||||
("16", quote! { rems(4.) }, "64px (4rem)"),
|
||||
("20", quote! { rems(5.) }, "80px (5rem)"),
|
||||
("24", quote! { rems(6.) }, "96px (6rem)"),
|
||||
("32", quote! { rems(8.) }, "128px (8rem)"),
|
||||
("40", quote! { rems(10.) }, "160px (10rem)"),
|
||||
("48", quote! { rems(12.) }, "192px (12rem)"),
|
||||
("56", quote! { rems(14.) }, "224px (14rem)"),
|
||||
("64", quote! { rems(16.) }, "256px (16rem)"),
|
||||
("72", quote! { rems(18.) }, "288px (18rem)"),
|
||||
("80", quote! { rems(20.) }, "320px (20rem)"),
|
||||
("96", quote! { rems(24.) }, "384px (24rem)"),
|
||||
("auto", quote! { auto() }, "Auto"),
|
||||
("px", quote! { pixels(1.) }, "1px"),
|
||||
("full", quote! { relative(1.) }, "100%"),
|
||||
("1_2", quote! { relative(0.5) }, "50% (1/2)"),
|
||||
("1_3", quote! { relative(1./3.) }, "33% (1/3)"),
|
||||
("2_3", quote! { relative(2./3.) }, "66% (2/3)"),
|
||||
("1_4", quote! { relative(0.25) }, "25% (1/4)"),
|
||||
("2_4", quote! { relative(0.5) }, "50% (2/4)"),
|
||||
("3_4", quote! { relative(0.75) }, "75% (3/4)"),
|
||||
("1_5", quote! { relative(0.2) }, "20% (1/5)"),
|
||||
("2_5", quote! { relative(0.4) }, "40% (2/5)"),
|
||||
("3_5", quote! { relative(0.6) }, "60% (3/5)"),
|
||||
("4_5", quote! { relative(0.8) }, "80% (4/5)"),
|
||||
("1_6", quote! { relative(1./6.) }, "16% (1/6)"),
|
||||
("5_6", quote! { relative(5./6.) }, "80% (5/6)"),
|
||||
("1_12", quote! { relative(1./12.) }, "8% (1/12)"),
|
||||
]
|
||||
}
|
||||
|
||||
@ -258,16 +257,16 @@ fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
]
|
||||
}
|
||||
|
||||
fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
fn corner_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
|
||||
vec![
|
||||
("none", quote! { pixels(0.) }),
|
||||
("sm", quote! { rems(0.125) }),
|
||||
("md", quote! { rems(0.25) }),
|
||||
("lg", quote! { rems(0.5) }),
|
||||
("xl", quote! { rems(0.75) }),
|
||||
("2xl", quote! { rems(1.) }),
|
||||
("3xl", quote! { rems(1.5) }),
|
||||
("full", quote! { pixels(9999.) }),
|
||||
("none", quote! { pixels(0.) }, "0px"),
|
||||
("sm", quote! { rems(0.125) }, "2px (0.125rem)"),
|
||||
("md", quote! { rems(0.25) }, "4px (0.25rem)"),
|
||||
("lg", quote! { rems(0.5) }, "8px (0.5rem)"),
|
||||
("xl", quote! { rems(0.75) }, "12px (0.75rem)"),
|
||||
("2xl", quote! { rems(1.) }, "16px (1rem)"),
|
||||
("3xl", quote! { rems(1.5) }, "24px (1.5rem)"),
|
||||
("full", quote! { pixels(9999.) }, "9999px"),
|
||||
]
|
||||
}
|
||||
|
||||
@ -303,25 +302,25 @@ fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
|
||||
]
|
||||
}
|
||||
|
||||
fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
|
||||
fn border_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
|
||||
vec![
|
||||
("", quote! { pixels(1.) }),
|
||||
("0", quote! { pixels(0.) }),
|
||||
("1", quote! { pixels(1.) }),
|
||||
("2", quote! { pixels(2.) }),
|
||||
("3", quote! { pixels(3.) }),
|
||||
("4", quote! { pixels(4.) }),
|
||||
("5", quote! { pixels(5.) }),
|
||||
("6", quote! { pixels(6.) }),
|
||||
("7", quote! { pixels(7.) }),
|
||||
("8", quote! { pixels(8.) }),
|
||||
("9", quote! { pixels(9.) }),
|
||||
("10", quote! { pixels(10.) }),
|
||||
("11", quote! { pixels(11.) }),
|
||||
("12", quote! { pixels(12.) }),
|
||||
("16", quote! { pixels(16.) }),
|
||||
("20", quote! { pixels(20.) }),
|
||||
("24", quote! { pixels(24.) }),
|
||||
("32", quote! { pixels(32.) }),
|
||||
("", quote! { pixels(1.)}, "1px"),
|
||||
("0", quote! { pixels(0.)}, "0px"),
|
||||
("1", quote! { pixels(1.) }, "1px"),
|
||||
("2", quote! { pixels(2.) }, "2px"),
|
||||
("3", quote! { pixels(3.) }, "3px"),
|
||||
("4", quote! { pixels(4.) }, "4px"),
|
||||
("5", quote! { pixels(5.) }, "5px"),
|
||||
("6", quote! { pixels(6.) }, "6px"),
|
||||
("7", quote! { pixels(7.) }, "7px"),
|
||||
("8", quote! { pixels(8.) }, "8px"),
|
||||
("9", quote! { pixels(9.) }, "9px"),
|
||||
("10", quote! { pixels(10.) }, "10px"),
|
||||
("11", quote! { pixels(11.) }, "11px"),
|
||||
("12", quote! { pixels(12.) }, "12px"),
|
||||
("16", quote! { pixels(16.) }, "16px"),
|
||||
("20", quote! { pixels(20.) }, "20px"),
|
||||
("24", quote! { pixels(24.) }, "24px"),
|
||||
("32", quote! { pixels(32.) }, "32px"),
|
||||
]
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ use gpui::{
|
||||
};
|
||||
use language::{Buffer, LanguageServerId, LanguageServerName};
|
||||
use lsp::IoKind;
|
||||
use project::{Project, Worktree};
|
||||
use project::{search::SearchQuery, Project, Worktree};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use theme::{ui, Theme};
|
||||
use workspace::{
|
||||
@ -524,12 +524,24 @@ impl SearchableItem for LspLogView {
|
||||
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> gpui::Task<Vec<Self::Match>> {
|
||||
self.editor.update(cx, |e, cx| e.find_matches(query, cx))
|
||||
}
|
||||
|
||||
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
|
||||
// Since LSP Log is read-only, it doesn't make sense to support replace operation.
|
||||
}
|
||||
fn supported_options() -> workspace::searchable::SearchOptions {
|
||||
workspace::searchable::SearchOptions {
|
||||
case: true,
|
||||
word: true,
|
||||
regex: true,
|
||||
// LSP log is read-only.
|
||||
replacement: false,
|
||||
}
|
||||
}
|
||||
fn active_match_index(
|
||||
&mut self,
|
||||
matches: Vec<Self::Match>,
|
||||
|
@ -7,6 +7,7 @@ use language::{char_kind, BufferSnapshot};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use smol::future::yield_now;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{BufRead, BufReader, Read},
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
@ -35,6 +36,7 @@ impl SearchInputs {
|
||||
pub enum SearchQuery {
|
||||
Text {
|
||||
search: Arc<AhoCorasick<usize>>,
|
||||
replacement: Option<String>,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
inner: SearchInputs,
|
||||
@ -42,7 +44,7 @@ pub enum SearchQuery {
|
||||
|
||||
Regex {
|
||||
regex: Regex,
|
||||
|
||||
replacement: Option<String>,
|
||||
multiline: bool,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
@ -95,6 +97,7 @@ impl SearchQuery {
|
||||
};
|
||||
Self::Text {
|
||||
search: Arc::new(search),
|
||||
replacement: None,
|
||||
whole_word,
|
||||
case_sensitive,
|
||||
inner,
|
||||
@ -130,6 +133,7 @@ impl SearchQuery {
|
||||
};
|
||||
Ok(Self::Regex {
|
||||
regex,
|
||||
replacement: None,
|
||||
multiline,
|
||||
whole_word,
|
||||
case_sensitive,
|
||||
@ -156,7 +160,21 @@ impl SearchQuery {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_replacement(mut self, new_replacement: Option<String>) -> Self {
|
||||
match self {
|
||||
Self::Text {
|
||||
ref mut replacement,
|
||||
..
|
||||
}
|
||||
| Self::Regex {
|
||||
ref mut replacement,
|
||||
..
|
||||
} => {
|
||||
*replacement = new_replacement;
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn to_proto(&self, project_id: u64) -> proto::SearchProject {
|
||||
proto::SearchProject {
|
||||
project_id,
|
||||
@ -214,7 +232,20 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replacement<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
|
||||
match self {
|
||||
SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from),
|
||||
SearchQuery::Regex {
|
||||
regex, replacement, ..
|
||||
} => {
|
||||
if let Some(replacement) = replacement {
|
||||
Some(regex.replace(text, replacement))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn search(
|
||||
&self,
|
||||
buffer: &BufferSnapshot,
|
||||
|
@ -2,19 +2,16 @@ use crate::{
|
||||
history::SearchHistory,
|
||||
mode::{next_mode, SearchMode, Side},
|
||||
search_bar::{render_nav_button, render_search_mode_button},
|
||||
CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions, SelectAllMatches,
|
||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
|
||||
CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
||||
SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
|
||||
ToggleWholeWord,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::Editor;
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
actions,
|
||||
elements::*,
|
||||
impl_actions,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle,
|
||||
WindowContext,
|
||||
actions, elements::*, impl_actions, Action, AnyViewHandle, AppContext, Entity, Subscription,
|
||||
Task, View, ViewContext, ViewHandle, WindowContext,
|
||||
};
|
||||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
@ -54,6 +51,11 @@ pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(BufferSearchBar::previous_history_query);
|
||||
cx.add_action(BufferSearchBar::cycle_mode);
|
||||
cx.add_action(BufferSearchBar::cycle_mode_on_pane);
|
||||
cx.add_action(BufferSearchBar::replace_all);
|
||||
cx.add_action(BufferSearchBar::replace_next);
|
||||
cx.add_action(BufferSearchBar::replace_all_on_pane);
|
||||
cx.add_action(BufferSearchBar::replace_next_on_pane);
|
||||
cx.add_action(BufferSearchBar::toggle_replace);
|
||||
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
|
||||
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
|
||||
}
|
||||
@ -73,9 +75,11 @@ fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContex
|
||||
|
||||
pub struct BufferSearchBar {
|
||||
query_editor: ViewHandle<Editor>,
|
||||
replacement_editor: ViewHandle<Editor>,
|
||||
active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
|
||||
active_match_index: Option<usize>,
|
||||
active_searchable_item_subscription: Option<Subscription>,
|
||||
active_search: Option<Arc<SearchQuery>>,
|
||||
searchable_items_with_matches:
|
||||
HashMap<Box<dyn WeakSearchableItemHandle>, Vec<Box<dyn Any + Send>>>,
|
||||
pending_search: Option<Task<()>>,
|
||||
@ -85,6 +89,7 @@ pub struct BufferSearchBar {
|
||||
dismissed: bool,
|
||||
search_history: SearchHistory,
|
||||
current_mode: SearchMode,
|
||||
replace_is_active: bool,
|
||||
}
|
||||
|
||||
impl Entity for BufferSearchBar {
|
||||
@ -156,6 +161,9 @@ impl View for BufferSearchBar {
|
||||
self.query_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text(new_placeholder_text, cx);
|
||||
});
|
||||
self.replacement_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text("Replace with...", cx);
|
||||
});
|
||||
let search_button_for_mode = |mode, side, cx: &mut ViewContext<BufferSearchBar>| {
|
||||
let is_active = self.current_mode == mode;
|
||||
|
||||
@ -212,7 +220,6 @@ impl View for BufferSearchBar {
|
||||
cx,
|
||||
)
|
||||
};
|
||||
|
||||
let query_column = Flex::row()
|
||||
.with_child(
|
||||
Svg::for_style(theme.search.editor_icon.clone().icon)
|
||||
@ -243,7 +250,57 @@ impl View for BufferSearchBar {
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false);
|
||||
let should_show_replace_input = self.replace_is_active && supported_options.replacement;
|
||||
|
||||
let replacement = should_show_replace_input.then(|| {
|
||||
Flex::row()
|
||||
.with_child(
|
||||
Svg::for_style(theme.search.replace_icon.clone().icon)
|
||||
.contained()
|
||||
.with_style(theme.search.replace_icon.clone().container),
|
||||
)
|
||||
.with_child(ChildView::new(&self.replacement_editor, cx).flex(1., true))
|
||||
.align_children_center()
|
||||
.flex(1., true)
|
||||
.contained()
|
||||
.with_style(query_container_style)
|
||||
.constrained()
|
||||
.with_min_width(theme.search.editor.min_width)
|
||||
.with_max_width(theme.search.editor.max_width)
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.flex(1., false)
|
||||
});
|
||||
let replace_all = should_show_replace_input.then(|| {
|
||||
super::replace_action(
|
||||
ReplaceAll,
|
||||
"Replace all",
|
||||
"icons/replace_all.svg",
|
||||
theme.tooltip.clone(),
|
||||
theme.search.action_button.clone(),
|
||||
)
|
||||
});
|
||||
let replace_next = should_show_replace_input.then(|| {
|
||||
super::replace_action(
|
||||
ReplaceNext,
|
||||
"Replace next",
|
||||
"icons/replace_next.svg",
|
||||
theme.tooltip.clone(),
|
||||
theme.search.action_button.clone(),
|
||||
)
|
||||
});
|
||||
let switches_column = supported_options.replacement.then(|| {
|
||||
Flex::row()
|
||||
.align_children_center()
|
||||
.with_child(super::toggle_replace_button(
|
||||
self.replace_is_active,
|
||||
theme.tooltip.clone(),
|
||||
theme.search.option_button_component.clone(),
|
||||
))
|
||||
.constrained()
|
||||
.with_height(theme.search.search_bar_row_height)
|
||||
.contained()
|
||||
.with_style(theme.search.option_button_group)
|
||||
});
|
||||
let mode_column = Flex::row()
|
||||
.with_child(search_button_for_mode(
|
||||
SearchMode::Text,
|
||||
@ -261,7 +318,10 @@ impl View for BufferSearchBar {
|
||||
.with_height(theme.search.search_bar_row_height);
|
||||
|
||||
let nav_column = Flex::row()
|
||||
.with_child(self.render_action_button("all", cx))
|
||||
.align_children_center()
|
||||
.with_children(replace_next)
|
||||
.with_children(replace_all)
|
||||
.with_child(self.render_action_button("icons/select-all.svg", cx))
|
||||
.with_child(Flex::row().with_children(match_count))
|
||||
.with_child(nav_button_for_direction("<", Direction::Prev, cx))
|
||||
.with_child(nav_button_for_direction(">", Direction::Next, cx))
|
||||
@ -271,6 +331,8 @@ impl View for BufferSearchBar {
|
||||
|
||||
Flex::row()
|
||||
.with_child(query_column)
|
||||
.with_children(switches_column)
|
||||
.with_children(replacement)
|
||||
.with_child(mode_column)
|
||||
.with_child(nav_column)
|
||||
.contained()
|
||||
@ -345,9 +407,18 @@ impl BufferSearchBar {
|
||||
});
|
||||
cx.subscribe(&query_editor, Self::on_query_editor_event)
|
||||
.detach();
|
||||
|
||||
let replacement_editor = cx.add_view(|cx| {
|
||||
Editor::auto_height(
|
||||
2,
|
||||
Some(Arc::new(|theme| theme.search.editor.input.clone())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
// cx.subscribe(&replacement_editor, Self::on_query_editor_event)
|
||||
// .detach();
|
||||
Self {
|
||||
query_editor,
|
||||
replacement_editor,
|
||||
active_searchable_item: None,
|
||||
active_searchable_item_subscription: None,
|
||||
active_match_index: None,
|
||||
@ -359,6 +430,8 @@ impl BufferSearchBar {
|
||||
dismissed: true,
|
||||
search_history: SearchHistory::default(),
|
||||
current_mode: SearchMode::default(),
|
||||
active_search: None,
|
||||
replace_is_active: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,7 +514,9 @@ impl BufferSearchBar {
|
||||
pub fn query(&self, cx: &WindowContext) -> String {
|
||||
self.query_editor.read(cx).text(cx)
|
||||
}
|
||||
|
||||
pub fn replacement(&self, cx: &WindowContext) -> String {
|
||||
self.replacement_editor.read(cx).text(cx)
|
||||
}
|
||||
pub fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<String> {
|
||||
self.active_searchable_item
|
||||
.as_ref()
|
||||
@ -477,37 +552,16 @@ impl BufferSearchBar {
|
||||
) -> AnyElement<Self> {
|
||||
let tooltip = "Select All Matches";
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
let action_type_id = 0_usize;
|
||||
let has_matches = self.active_match_index.is_some();
|
||||
let cursor_style = if has_matches {
|
||||
CursorStyle::PointingHand
|
||||
} else {
|
||||
CursorStyle::default()
|
||||
};
|
||||
enum ActionButton {}
|
||||
MouseEventHandler::new::<ActionButton, _>(action_type_id, cx, |state, cx| {
|
||||
let theme = theme::current(cx);
|
||||
let style = theme
|
||||
.search
|
||||
.action_button
|
||||
.in_state(has_matches)
|
||||
.style_for(state);
|
||||
Label::new(icon, style.text.clone())
|
||||
.aligned()
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
this.select_all_matches(&SelectAllMatches, cx)
|
||||
})
|
||||
.with_cursor_style(cursor_style)
|
||||
.with_tooltip::<ActionButton>(
|
||||
action_type_id,
|
||||
tooltip.to_string(),
|
||||
Some(Box::new(SelectAllMatches)),
|
||||
tooltip_style,
|
||||
cx,
|
||||
)
|
||||
|
||||
let theme = theme::current(cx);
|
||||
let style = theme.search.action_button.clone();
|
||||
|
||||
gpui::elements::Component::element(SafeStylable::with_style(
|
||||
theme::components::action_button::Button::action(SelectAllMatches)
|
||||
.with_tooltip(tooltip, tooltip_style)
|
||||
.with_contents(theme::components::svg::Svg::new(icon)),
|
||||
style,
|
||||
))
|
||||
.into_any()
|
||||
}
|
||||
|
||||
@ -688,6 +742,7 @@ impl BufferSearchBar {
|
||||
let (done_tx, done_rx) = oneshot::channel();
|
||||
let query = self.query(cx);
|
||||
self.pending_search.take();
|
||||
|
||||
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
|
||||
if query.is_empty() {
|
||||
self.active_match_index.take();
|
||||
@ -695,7 +750,7 @@ impl BufferSearchBar {
|
||||
let _ = done_tx.send(());
|
||||
cx.notify();
|
||||
} else {
|
||||
let query = if self.current_mode == SearchMode::Regex {
|
||||
let query: Arc<_> = if self.current_mode == SearchMode::Regex {
|
||||
match SearchQuery::regex(
|
||||
query,
|
||||
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||
@ -703,7 +758,8 @@ impl BufferSearchBar {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
) {
|
||||
Ok(query) => query,
|
||||
Ok(query) => query
|
||||
.with_replacement(Some(self.replacement(cx)).filter(|s| !s.is_empty())),
|
||||
Err(_) => {
|
||||
self.query_contains_error = true;
|
||||
cx.notify();
|
||||
@ -718,8 +774,10 @@ impl BufferSearchBar {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
)
|
||||
};
|
||||
|
||||
.with_replacement(Some(self.replacement(cx)).filter(|s| !s.is_empty()))
|
||||
}
|
||||
.into();
|
||||
self.active_search = Some(query.clone());
|
||||
let query_text = query.as_str().to_string();
|
||||
let matches = active_searchable_item.find_matches(query, cx);
|
||||
|
||||
@ -810,6 +868,63 @@ impl BufferSearchBar {
|
||||
cx.propagate_action();
|
||||
}
|
||||
}
|
||||
fn toggle_replace(&mut self, _: &ToggleReplace, _: &mut ViewContext<Self>) {
|
||||
if let Some(_) = &self.active_searchable_item {
|
||||
self.replace_is_active = !self.replace_is_active;
|
||||
}
|
||||
}
|
||||
fn replace_next(&mut self, _: &ReplaceNext, cx: &mut ViewContext<Self>) {
|
||||
if !self.dismissed && self.active_search.is_some() {
|
||||
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
|
||||
if let Some(query) = self.active_search.as_ref() {
|
||||
if let Some(matches) = self
|
||||
.searchable_items_with_matches
|
||||
.get(&searchable_item.downgrade())
|
||||
{
|
||||
if let Some(active_index) = self.active_match_index {
|
||||
let query = query.as_ref().clone().with_replacement(
|
||||
Some(self.replacement(cx)).filter(|rep| !rep.is_empty()),
|
||||
);
|
||||
searchable_item.replace(&matches[active_index], &query, cx);
|
||||
}
|
||||
|
||||
self.focus_editor(&FocusEditor, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn replace_all(&mut self, _: &ReplaceAll, cx: &mut ViewContext<Self>) {
|
||||
if !self.dismissed && self.active_search.is_some() {
|
||||
if let Some(searchable_item) = self.active_searchable_item.as_ref() {
|
||||
if let Some(query) = self.active_search.as_ref() {
|
||||
if let Some(matches) = self
|
||||
.searchable_items_with_matches
|
||||
.get(&searchable_item.downgrade())
|
||||
{
|
||||
let query = query.as_ref().clone().with_replacement(
|
||||
Some(self.replacement(cx)).filter(|rep| !rep.is_empty()),
|
||||
);
|
||||
for m in matches {
|
||||
searchable_item.replace(m, &query, cx);
|
||||
}
|
||||
|
||||
self.focus_editor(&FocusEditor, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn replace_next_on_pane(pane: &mut Pane, action: &ReplaceNext, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.replace_next(action, cx));
|
||||
}
|
||||
}
|
||||
fn replace_all_on_pane(pane: &mut Pane, action: &ReplaceAll, cx: &mut ViewContext<Pane>) {
|
||||
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.replace_all(action, cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1539,4 +1654,109 @@ mod tests {
|
||||
assert_eq!(search_bar.search_options, SearchOptions::NONE);
|
||||
});
|
||||
}
|
||||
#[gpui::test]
|
||||
async fn test_replace_simple(cx: &mut TestAppContext) {
|
||||
let (editor, search_bar) = init_test(cx);
|
||||
|
||||
search_bar
|
||||
.update(cx, |search_bar, cx| {
|
||||
search_bar.search("expression", None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.replacement_editor.update(cx, |editor, cx| {
|
||||
// We use $1 here as initially we should be in Text mode, where `$1` should be treated literally.
|
||||
editor.set_text("expr$1", cx);
|
||||
});
|
||||
search_bar.replace_all(&ReplaceAll, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
editor.read_with(cx, |this, cx| { this.text(cx) }),
|
||||
r#"
|
||||
A regular expr$1 (shortened as regex or regexp;[1] also referred to as
|
||||
rational expr$1[2][3]) is a sequence of characters that specifies a search
|
||||
pattern in text. Usually such patterns are used by string-searching algorithms
|
||||
for "find" or "find and replace" operations on strings, or for input validation.
|
||||
"#
|
||||
.unindent()
|
||||
);
|
||||
|
||||
// Search for word boundaries and replace just a single one.
|
||||
search_bar
|
||||
.update(cx, |search_bar, cx| {
|
||||
search_bar.search("or", Some(SearchOptions::WHOLE_WORD), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.replacement_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("banana", cx);
|
||||
});
|
||||
search_bar.replace_next(&ReplaceNext, cx)
|
||||
});
|
||||
// Notice how the first or in the text (shORtened) is not replaced. Neither are the remaining hits of `or` in the text.
|
||||
assert_eq!(
|
||||
editor.read_with(cx, |this, cx| { this.text(cx) }),
|
||||
r#"
|
||||
A regular expr$1 (shortened as regex banana regexp;[1] also referred to as
|
||||
rational expr$1[2][3]) is a sequence of characters that specifies a search
|
||||
pattern in text. Usually such patterns are used by string-searching algorithms
|
||||
for "find" or "find and replace" operations on strings, or for input validation.
|
||||
"#
|
||||
.unindent()
|
||||
);
|
||||
// Let's turn on regex mode.
|
||||
search_bar
|
||||
.update(cx, |search_bar, cx| {
|
||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
||||
search_bar.search("\\[([^\\]]+)\\]", None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.replacement_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("${1}number", cx);
|
||||
});
|
||||
search_bar.replace_all(&ReplaceAll, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
editor.read_with(cx, |this, cx| { this.text(cx) }),
|
||||
r#"
|
||||
A regular expr$1 (shortened as regex banana regexp;1number also referred to as
|
||||
rational expr$12number3number) is a sequence of characters that specifies a search
|
||||
pattern in text. Usually such patterns are used by string-searching algorithms
|
||||
for "find" or "find and replace" operations on strings, or for input validation.
|
||||
"#
|
||||
.unindent()
|
||||
);
|
||||
// Now with a whole-word twist.
|
||||
search_bar
|
||||
.update(cx, |search_bar, cx| {
|
||||
search_bar.activate_search_mode(SearchMode::Regex, cx);
|
||||
search_bar.search("a\\w+s", Some(SearchOptions::WHOLE_WORD), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
search_bar.replacement_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("things", cx);
|
||||
});
|
||||
search_bar.replace_all(&ReplaceAll, cx)
|
||||
});
|
||||
// The only word affected by this edit should be `algorithms`, even though there's a bunch
|
||||
// of words in this text that would match this regex if not for WHOLE_WORD.
|
||||
assert_eq!(
|
||||
editor.read_with(cx, |this, cx| { this.text(cx) }),
|
||||
r#"
|
||||
A regular expr$1 (shortened as regex banana regexp;1number also referred to as
|
||||
rational expr$12number3number) is a sequence of characters that specifies a search
|
||||
pattern in text. Usually such patterns are used by string-searching things
|
||||
for "find" or "find and replace" operations on strings, or for input validation.
|
||||
"#
|
||||
.unindent()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ use gpui::{
|
||||
pub use mode::SearchMode;
|
||||
use project::search::SearchQuery;
|
||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||
use theme::components::{action_button::Button, svg::Svg, ComponentExt, ToggleIconButtonStyle};
|
||||
use theme::components::{
|
||||
action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
|
||||
};
|
||||
|
||||
pub mod buffer_search;
|
||||
mod history;
|
||||
@ -27,6 +29,7 @@ actions!(
|
||||
CycleMode,
|
||||
ToggleWholeWord,
|
||||
ToggleCaseSensitive,
|
||||
ToggleReplace,
|
||||
SelectNextMatch,
|
||||
SelectPrevMatch,
|
||||
SelectAllMatches,
|
||||
@ -34,7 +37,9 @@ actions!(
|
||||
PreviousHistoryQuery,
|
||||
ActivateTextMode,
|
||||
ActivateSemanticMode,
|
||||
ActivateRegexMode
|
||||
ActivateRegexMode,
|
||||
ReplaceAll,
|
||||
ReplaceNext
|
||||
]
|
||||
);
|
||||
|
||||
@ -98,3 +103,32 @@ impl SearchOptions {
|
||||
.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_replace_button<V: View>(
|
||||
active: bool,
|
||||
tooltip_style: TooltipStyle,
|
||||
button_style: ToggleIconButtonStyle,
|
||||
) -> AnyElement<V> {
|
||||
Button::dynamic_action(Box::new(ToggleReplace))
|
||||
.with_tooltip("Toggle replace", tooltip_style)
|
||||
.with_contents(theme::components::svg::Svg::new("icons/replace.svg"))
|
||||
.toggleable(active)
|
||||
.with_style(button_style)
|
||||
.element()
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn replace_action<V: View>(
|
||||
action: impl Action,
|
||||
name: &'static str,
|
||||
icon_path: &'static str,
|
||||
tooltip_style: TooltipStyle,
|
||||
button_style: IconButtonStyle,
|
||||
) -> AnyElement<V> {
|
||||
Button::dynamic_action(Box::new(action))
|
||||
.with_tooltip(name, tooltip_style)
|
||||
.with_contents(theme::components::svg::Svg::new(icon_path))
|
||||
.with_style(button_style)
|
||||
.element()
|
||||
.into_any()
|
||||
}
|
||||
|
@ -4,6 +4,12 @@ use gpui2::{
|
||||
};
|
||||
use std::{marker::PhantomData, rc::Rc};
|
||||
|
||||
mod icon_button;
|
||||
mod tab;
|
||||
|
||||
pub(crate) use icon_button::{icon_button, ButtonVariant};
|
||||
pub(crate) use tab::tab;
|
||||
|
||||
struct ButtonHandlers<V, D> {
|
||||
click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
|
||||
}
|
||||
|
50
crates/storybook/src/components/icon_button.rs
Normal file
50
crates/storybook/src/components/icon_button.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::theme::theme;
|
||||
use gpui2::elements::svg;
|
||||
use gpui2::style::{StyleHelpers, Styleable};
|
||||
use gpui2::{elements::div, IntoElement};
|
||||
use gpui2::{Element, ParentElement, ViewContext};
|
||||
|
||||
#[derive(Element)]
|
||||
pub(crate) struct IconButton {
|
||||
path: &'static str,
|
||||
variant: ButtonVariant,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ButtonVariant {
|
||||
Ghost,
|
||||
Filled,
|
||||
}
|
||||
|
||||
pub fn icon_button<V: 'static>(path: &'static str, variant: ButtonVariant) -> impl Element<V> {
|
||||
IconButton { path, variant }
|
||||
}
|
||||
|
||||
impl IconButton {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
let mut div = div();
|
||||
if self.variant == ButtonVariant::Filled {
|
||||
div = div.fill(theme.highest.on.default.background);
|
||||
}
|
||||
|
||||
div.w_7()
|
||||
.h_6()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.hover()
|
||||
.fill(theme.highest.base.hovered.background)
|
||||
.active()
|
||||
.fill(theme.highest.base.pressed.background)
|
||||
.child(
|
||||
svg()
|
||||
.path(self.path)
|
||||
.w_4()
|
||||
.h_4()
|
||||
.fill(theme.highest.variant.default.foreground),
|
||||
)
|
||||
}
|
||||
}
|
55
crates/storybook/src/components/tab.rs
Normal file
55
crates/storybook/src/components/tab.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::theme::theme;
|
||||
use gpui2::style::{StyleHelpers, Styleable};
|
||||
use gpui2::{elements::div, IntoElement};
|
||||
use gpui2::{Element, ParentElement, ViewContext};
|
||||
|
||||
#[derive(Element)]
|
||||
pub(crate) struct Tab {
|
||||
title: &'static str,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
pub fn tab<V: 'static>(title: &'static str, enabled: bool) -> impl Element<V> {
|
||||
Tab { title, enabled }
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.px_2()
|
||||
.py_0p5()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_lg()
|
||||
.fill(if self.enabled {
|
||||
theme.highest.on.default.background
|
||||
} else {
|
||||
theme.highest.base.default.background
|
||||
})
|
||||
.hover()
|
||||
.fill(if self.enabled {
|
||||
theme.highest.on.hovered.background
|
||||
} else {
|
||||
theme.highest.base.hovered.background
|
||||
})
|
||||
.active()
|
||||
.fill(if self.enabled {
|
||||
theme.highest.on.pressed.background
|
||||
} else {
|
||||
theme.highest.base.pressed.background
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(if self.enabled {
|
||||
theme.highest.base.default.foreground
|
||||
} else {
|
||||
theme.highest.variant.default.foreground
|
||||
})
|
||||
.child(self.title),
|
||||
)
|
||||
}
|
||||
}
|
3
crates/storybook/src/modules.rs
Normal file
3
crates/storybook/src/modules.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod tab_bar;
|
||||
|
||||
pub(crate) use tab_bar::tab_bar;
|
82
crates/storybook/src/modules/tab_bar.rs
Normal file
82
crates/storybook/src/modules/tab_bar.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::components::{icon_button, tab, ButtonVariant};
|
||||
use crate::theme::theme;
|
||||
use gpui2::elements::div::ScrollState;
|
||||
use gpui2::style::StyleHelpers;
|
||||
use gpui2::{elements::div, IntoElement};
|
||||
use gpui2::{Element, ParentElement, ViewContext};
|
||||
|
||||
#[derive(Element)]
|
||||
pub struct TabBar<V: 'static> {
|
||||
view_type: PhantomData<V>,
|
||||
scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
pub fn tab_bar<V: 'static>(scroll_state: ScrollState) -> TabBar<V> {
|
||||
TabBar {
|
||||
view_type: PhantomData,
|
||||
scroll_state,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> TabBar<V> {
|
||||
fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
|
||||
let theme = theme(cx);
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
// Left Side
|
||||
.child(
|
||||
div()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(icon_button("icons/arrow_left.svg", ButtonVariant::Filled))
|
||||
.child(icon_button("icons/arrow_right.svg", ButtonVariant::Ghost)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().w_0().flex_1().h_full().child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_px()
|
||||
.overflow_x_scroll(self.scroll_state.clone())
|
||||
.child(tab("Cargo.toml", false))
|
||||
.child(tab("Channels Panel", true))
|
||||
.child(tab("channels_panel.rs", false))
|
||||
.child(tab("workspace.rs", false))
|
||||
.child(tab("icon_button.rs", false))
|
||||
.child(tab("storybook.rs", false))
|
||||
.child(tab("theme.rs", false))
|
||||
.child(tab("theme_registry.rs", false))
|
||||
.child(tab("styleable_helpers.rs", false)),
|
||||
),
|
||||
)
|
||||
// Right Side
|
||||
.child(
|
||||
div()
|
||||
.px_1()
|
||||
.flex()
|
||||
.flex_none()
|
||||
.gap_2()
|
||||
// Nav Buttons
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_px()
|
||||
.child(icon_button("icons/plus.svg", ButtonVariant::Ghost))
|
||||
.child(icon_button("icons/split.svg", ButtonVariant::Ghost)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ use simplelog::SimpleLogger;
|
||||
mod collab_panel;
|
||||
mod components;
|
||||
mod element_ext;
|
||||
mod modules;
|
||||
mod theme;
|
||||
mod workspace;
|
||||
|
||||
@ -34,13 +35,13 @@ fn main() {
|
||||
|
||||
cx.add_window(
|
||||
gpui2::WindowOptions {
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
|
||||
bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1600., 900.))),
|
||||
center: true,
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
view(|cx| {
|
||||
cx.enable_inspector();
|
||||
// cx.enable_inspector();
|
||||
storybook(&mut ViewContext::new(cx))
|
||||
})
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{collab_panel::collab_panel, theme::theme};
|
||||
use crate::{collab_panel::collab_panel, modules::tab_bar, theme::theme};
|
||||
use gpui2::{
|
||||
elements::{div, div::ScrollState, img, svg},
|
||||
style::{StyleHelpers, Styleable},
|
||||
@ -9,6 +9,7 @@ use gpui2::{
|
||||
struct WorkspaceElement {
|
||||
left_scroll_state: ScrollState,
|
||||
right_scroll_state: ScrollState,
|
||||
tab_bar_scroll_state: ScrollState,
|
||||
}
|
||||
|
||||
pub fn workspace<V: 'static>() -> impl Element<V> {
|
||||
@ -38,7 +39,19 @@ impl WorkspaceElement {
|
||||
.flex_row()
|
||||
.overflow_hidden()
|
||||
.child(collab_panel(self.left_scroll_state.clone()))
|
||||
.child(div().h_full().flex_1())
|
||||
.child(
|
||||
div()
|
||||
.h_full()
|
||||
.flex_1()
|
||||
.fill(theme.highest.base.default.background)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.flex_1()
|
||||
.child(tab_bar(self.tab_bar_scroll_state.clone())),
|
||||
),
|
||||
)
|
||||
.child(collab_panel(self.right_scroll_state.clone())),
|
||||
)
|
||||
.child(statusbar())
|
||||
|
@ -18,7 +18,7 @@ use gpui::{
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::Bias;
|
||||
use project::{LocalWorktree, Project};
|
||||
use project::{search::SearchQuery, LocalWorktree, Project};
|
||||
use serde::Deserialize;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use smol::Timer;
|
||||
@ -26,6 +26,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
ops::RangeInclusive,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use terminal::{
|
||||
@ -380,10 +381,10 @@ impl TerminalView {
|
||||
|
||||
pub fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<RangeInclusive<Point>>> {
|
||||
let searcher = regex_search_for_query(query);
|
||||
let searcher = regex_search_for_query(&query);
|
||||
|
||||
if let Some(searcher) = searcher {
|
||||
self.terminal
|
||||
@ -486,7 +487,7 @@ fn possible_open_targets(
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn regex_search_for_query(query: project::search::SearchQuery) -> Option<RegexSearch> {
|
||||
pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<RegexSearch> {
|
||||
let query = query.as_str();
|
||||
let searcher = RegexSearch::new(&query);
|
||||
searcher.ok()
|
||||
@ -798,6 +799,7 @@ impl SearchableItem for TerminalView {
|
||||
case: false,
|
||||
word: false,
|
||||
regex: false,
|
||||
replacement: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -851,10 +853,10 @@ impl SearchableItem for TerminalView {
|
||||
/// Get all of the matches for this query, should be done on the background
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Self::Match>> {
|
||||
if let Some(searcher) = regex_search_for_query(query) {
|
||||
if let Some(searcher) = regex_search_for_query(&query) {
|
||||
self.terminal()
|
||||
.update(cx, |term, cx| term.find_matches(searcher, cx))
|
||||
} else {
|
||||
@ -898,6 +900,9 @@ impl SearchableItem for TerminalView {
|
||||
|
||||
res
|
||||
}
|
||||
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>) {
|
||||
// Replacement is not supported in terminal view, so this is a no-op.
|
||||
}
|
||||
}
|
||||
|
||||
///Get's the working directory for the given workspace, respecting the user's settings.
|
||||
|
@ -3,7 +3,9 @@ mod theme_registry;
|
||||
mod theme_settings;
|
||||
pub mod ui;
|
||||
|
||||
use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle};
|
||||
use components::{
|
||||
action_button::ButtonStyle, disclosure::DisclosureStyle, IconButtonStyle, ToggleIconButtonStyle,
|
||||
};
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{Border, ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
|
||||
@ -440,9 +442,7 @@ pub struct Search {
|
||||
pub include_exclude_editor: FindEditor,
|
||||
pub invalid_include_exclude_editor: ContainerStyle,
|
||||
pub include_exclude_inputs: ContainedText,
|
||||
pub option_button: Toggleable<Interactive<IconButton>>,
|
||||
pub option_button_component: ToggleIconButtonStyle,
|
||||
pub action_button: Toggleable<Interactive<ContainedText>>,
|
||||
pub match_background: Color,
|
||||
pub match_index: ContainedText,
|
||||
pub major_results_status: TextStyle,
|
||||
@ -454,6 +454,10 @@ pub struct Search {
|
||||
pub search_row_spacing: f32,
|
||||
pub option_button_height: f32,
|
||||
pub modes_container: ContainerStyle,
|
||||
pub replace_icon: IconStyle,
|
||||
// Used for filters and replace
|
||||
pub option_button: Toggleable<Interactive<IconButton>>,
|
||||
pub action_button: IconButtonStyle,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use gpui::{
|
||||
AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
|
||||
@ -25,6 +25,8 @@ pub struct SearchOptions {
|
||||
pub case: bool,
|
||||
pub word: bool,
|
||||
pub regex: bool,
|
||||
/// Specifies whether the item supports search & replace.
|
||||
pub replacement: bool,
|
||||
}
|
||||
|
||||
pub trait SearchableItem: Item {
|
||||
@ -35,6 +37,7 @@ pub trait SearchableItem: Item {
|
||||
case: true,
|
||||
word: true,
|
||||
regex: true,
|
||||
replacement: true,
|
||||
}
|
||||
}
|
||||
fn to_search_event(
|
||||
@ -52,6 +55,7 @@ pub trait SearchableItem: Item {
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
|
||||
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
|
||||
fn match_index_for_direction(
|
||||
&mut self,
|
||||
matches: &Vec<Self::Match>,
|
||||
@ -74,7 +78,7 @@ pub trait SearchableItem: Item {
|
||||
}
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: SearchQuery,
|
||||
query: Arc<SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Self::Match>>;
|
||||
fn active_match_index(
|
||||
@ -103,6 +107,7 @@ pub trait SearchableItemHandle: ItemHandle {
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
|
||||
fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
|
||||
fn match_index_for_direction(
|
||||
&self,
|
||||
matches: &Vec<Box<dyn Any + Send>>,
|
||||
@ -113,7 +118,7 @@ pub trait SearchableItemHandle: ItemHandle {
|
||||
) -> usize;
|
||||
fn find_matches(
|
||||
&self,
|
||||
query: SearchQuery,
|
||||
query: Arc<SearchQuery>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Vec<Box<dyn Any + Send>>>;
|
||||
fn active_match_index(
|
||||
@ -189,7 +194,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
||||
}
|
||||
fn find_matches(
|
||||
&self,
|
||||
query: SearchQuery,
|
||||
query: Arc<SearchQuery>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Vec<Box<dyn Any + Send>>> {
|
||||
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
|
||||
@ -209,6 +214,11 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
|
||||
let matches = downcast_matches(matches);
|
||||
self.update(cx, |this, cx| this.active_match_index(matches, cx))
|
||||
}
|
||||
|
||||
fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) {
|
||||
let matches = matches.downcast_ref().unwrap();
|
||||
self.update(cx, |this, cx| this.replace(matches, query, cx))
|
||||
}
|
||||
}
|
||||
|
||||
fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
|
||||
|
@ -30,9 +30,6 @@ export default function search(): any {
|
||||
selection: theme.players[0],
|
||||
text: text(theme.highest, "mono", "default"),
|
||||
border: border(theme.highest),
|
||||
margin: {
|
||||
right: SEARCH_ROW_SPACING,
|
||||
},
|
||||
padding: {
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
@ -125,7 +122,7 @@ export default function search(): any {
|
||||
|
||||
button_width: 32,
|
||||
background: background(theme.highest, "on"),
|
||||
corner_radius: 2,
|
||||
corner_radius: 6,
|
||||
margin: { right: 2 },
|
||||
border: {
|
||||
width: 1,
|
||||
@ -185,26 +182,6 @@ export default function search(): any {
|
||||
},
|
||||
},
|
||||
}),
|
||||
// Search tool buttons
|
||||
// HACK: This is not how disabled elements should be created
|
||||
// Disabled elements should use a disabled state of an interactive element, not a toggleable element with the inactive state being disabled
|
||||
action_button: toggleable({
|
||||
state: {
|
||||
inactive: text_button({
|
||||
variant: "ghost",
|
||||
layer: theme.highest,
|
||||
disabled: true,
|
||||
margin: { right: SEARCH_ROW_SPACING },
|
||||
text_properties: { size: "sm" },
|
||||
}),
|
||||
active: text_button({
|
||||
variant: "ghost",
|
||||
layer: theme.highest,
|
||||
margin: { right: SEARCH_ROW_SPACING },
|
||||
text_properties: { size: "sm" },
|
||||
}),
|
||||
},
|
||||
}),
|
||||
editor,
|
||||
invalid_editor: {
|
||||
...editor,
|
||||
@ -218,6 +195,7 @@ export default function search(): any {
|
||||
match_index: {
|
||||
...text(theme.highest, "mono", { size: "sm" }),
|
||||
padding: {
|
||||
left: SEARCH_ROW_SPACING,
|
||||
right: SEARCH_ROW_SPACING,
|
||||
},
|
||||
},
|
||||
@ -398,6 +376,59 @@ export default function search(): any {
|
||||
search_row_spacing: 8,
|
||||
option_button_height: 22,
|
||||
modes_container: {},
|
||||
replace_icon: {
|
||||
icon: {
|
||||
color: foreground(theme.highest, "disabled"),
|
||||
asset: "icons/replace.svg",
|
||||
dimensions: {
|
||||
width: 14,
|
||||
height: 14,
|
||||
},
|
||||
},
|
||||
container: {
|
||||
margin: { right: 4 },
|
||||
padding: { left: 1, right: 1 },
|
||||
},
|
||||
},
|
||||
action_button: interactive({
|
||||
base: {
|
||||
icon_size: 14,
|
||||
color: foreground(theme.highest, "variant"),
|
||||
|
||||
button_width: 32,
|
||||
background: background(theme.highest, "on"),
|
||||
corner_radius: 6,
|
||||
margin: { right: 2 },
|
||||
border: {
|
||||
width: 1,
|
||||
color: background(theme.highest, "on"),
|
||||
},
|
||||
padding: {
|
||||
left: 4,
|
||||
right: 4,
|
||||
top: 4,
|
||||
bottom: 4,
|
||||
},
|
||||
},
|
||||
state: {
|
||||
hovered: {
|
||||
...text(theme.highest, "mono", "variant", "hovered"),
|
||||
background: background(theme.highest, "on", "hovered"),
|
||||
border: {
|
||||
width: 1,
|
||||
color: background(theme.highest, "on", "hovered"),
|
||||
},
|
||||
},
|
||||
clicked: {
|
||||
...text(theme.highest, "mono", "variant", "pressed"),
|
||||
background: background(theme.highest, "on", "pressed"),
|
||||
border: {
|
||||
width: 1,
|
||||
color: background(theme.highest, "on", "pressed"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
...search_results(),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user