Compare commits
1 Commits
post-layou
...
wet-paint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e72b162a4 |
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -937,7 +937,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=c4f951a88b345724cb952e920ad30e39851f7760#c4f951a88b345724cb952e920ad30e39851f7760"
|
||||
source = "git+https://github.com/kvark/blade?rev=26bc5e8b9ef67b4f2970eb95888db733eace98f3#26bc5e8b9ef67b4f2970eb95888db733eace98f3"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -967,7 +967,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/kvark/blade?rev=c4f951a88b345724cb952e920ad30e39851f7760#c4f951a88b345724cb952e920ad30e39851f7760"
|
||||
source = "git+https://github.com/kvark/blade?rev=26bc5e8b9ef67b4f2970eb95888db733eace98f3#26bc5e8b9ef67b4f2970eb95888db733eace98f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2681,7 +2681,6 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"parking_lot 0.11.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"theme",
|
||||
@@ -9112,15 +9111,6 @@ dependencies = [
|
||||
"wasmtime-c-api-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-astro"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/virchau13/tree-sitter-astro.git?rev=e924787e12e8a03194f36a113290ac11d6dc10f3#e924787e12e8a03194f36a113290ac11d6dc10f3"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-bash"
|
||||
version = "0.20.4"
|
||||
@@ -9158,15 +9148,6 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-clojure"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/prcastro/tree-sitter-clojure?branch=update-ts#38b4f8d264248b2fd09575fbce66f7c22e8929d5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-cpp"
|
||||
version = "0.20.0"
|
||||
@@ -9389,15 +9370,6 @@ dependencies = [
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-prisma-io"
|
||||
version = "1.4.0"
|
||||
source = "git+https://github.com/victorhqc/tree-sitter-prisma#eca2596a355b1a9952b4f80f8f9caed300a272b5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-proto"
|
||||
version = "0.0.2"
|
||||
@@ -10855,12 +10827,10 @@ dependencies = [
|
||||
"tiny_http",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
"tree-sitter-astro",
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-beancount",
|
||||
"tree-sitter-c",
|
||||
"tree-sitter-c-sharp",
|
||||
"tree-sitter-clojure",
|
||||
"tree-sitter-cpp",
|
||||
"tree-sitter-css",
|
||||
"tree-sitter-elixir",
|
||||
@@ -10884,7 +10854,6 @@ dependencies = [
|
||||
"tree-sitter-nu",
|
||||
"tree-sitter-ocaml",
|
||||
"tree-sitter-php",
|
||||
"tree-sitter-prisma-io",
|
||||
"tree-sitter-proto",
|
||||
"tree-sitter-purescript",
|
||||
"tree-sitter-python",
|
||||
|
||||
@@ -223,11 +223,9 @@ tiktoken-rs = "0.5.7"
|
||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||
toml = "0.5"
|
||||
tree-sitter = { version = "0.20", features = ["wasm"] }
|
||||
tree-sitter-astro = { git = "https://github.com/virchau13/tree-sitter-astro.git", rev = "e924787e12e8a03194f36a113290ac11d6dc10f3" }
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" }
|
||||
tree-sitter-beancount = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "da1bf8c6eb0ae7a97588affde7227630bcd678b6" }
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-clojure = { git = "https://github.com/prcastro/tree-sitter-clojure", branch = "update-ts"}
|
||||
tree-sitter-c-sharp = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "dd5e59721a5f8dae34604060833902b882023aaf" }
|
||||
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
|
||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||
@@ -252,7 +250,6 @@ tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", re
|
||||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa" }
|
||||
tree-sitter-ocaml = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "4abfdc1c7af2c6c77a370aee974627be1c285b3b" }
|
||||
tree-sitter-php = "0.21.1"
|
||||
tree-sitter-prisma-io = { git = "https://github.com/victorhqc/tree-sitter-prisma" }
|
||||
tree-sitter-proto = { git = "https://github.com/rewinfrey/tree-sitter-proto", rev = "36d54f288aee112f13a67b550ad32634d0c2cb52" }
|
||||
tree-sitter-purescript = { git = "https://github.com/ivanmoreau/tree-sitter-purescript", rev = "a37140f0c7034977b90faa73c94fcb8a5e45ed08" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.32668 11.5331C4.78215 10.9852 4.62318 9.834 4.85006 9C5.24343 9.52582 5.78848 9.69239 6.35304 9.78642C7.2246 9.93151 8.08055 9.87724 8.89019 9.43877C8.98281 9.38857 9.06841 9.32182 9.16962 9.25421C9.24559 9.49681 9.26536 9.74172 9.23882 9.99099C9.1743 10.5981 8.89982 11.067 8.46326 11.4225C8.28869 11.5647 8.10397 11.6918 7.92367 11.8259C7.36978 12.2379 7.21992 12.7211 7.42805 13.4239C7.433 13.4411 7.43742 13.4582 7.44861 13.5C7.1658 13.3606 6.95923 13.1578 6.80182 12.8911C6.63558 12.6097 6.55649 12.2983 6.55233 11.9614C6.55025 11.7974 6.55025 11.632 6.53022 11.4704C6.4813 11.0763 6.31323 10.8999 5.99661 10.8897C5.67166 10.8793 5.41462 11.1004 5.34646 11.4486C5.34125 11.4753 5.33371 11.5017 5.32616 11.5328L5.32668 11.5331Z" fill="#17191E" fill-opacity="0.6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.30826 8.62679L6.73279 4.5906C6.82176 4.33854 7.17823 4.33854 7.26719 4.5906L8.69173 8.62679C8.69652 8.64035 8.70626 8.65153 8.7189 8.65814C8.80645 8.6531 8.89466 8.65055 8.98347 8.65055C10.1418 8.65055 11.1986 9.08493 12 9.79967L8.83801 1.36772C8.75507 1.14653 8.54362 1 8.30739 1H5.6926C5.45637 1 5.24492 1.14653 5.16198 1.36772L1.99999 9.79968C2.80137 9.08493 3.85822 8.65055 5.01652 8.65055C5.10533 8.65055 5.19355 8.6531 5.28109 8.65814C5.29373 8.65153 5.30347 8.64035 5.30826 8.62679Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"suffixes": {
|
||||
"astro": "astro",
|
||||
"Emakefile": "erlang",
|
||||
"aac": "audio",
|
||||
"accdb": "storage",
|
||||
@@ -149,9 +148,6 @@
|
||||
"zshrc": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"astro": {
|
||||
"icon": "icons/file_icons/astro.svg"
|
||||
},
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
|
||||
@@ -498,7 +498,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar && !in_replace",
|
||||
"context": "BufferSearchBar && !in_replace > VimEnabled",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
|
||||
@@ -212,8 +212,6 @@
|
||||
"default_width": 640,
|
||||
// Default height when the assistant is docked to the bottom.
|
||||
"default_height": 320,
|
||||
// The default OpenAI API endpoint to use when starting new conversations.
|
||||
"openai_api_url": "https://api.openai.com/v1",
|
||||
// The default OpenAI model to use when starting new conversations. This
|
||||
// setting can take three values:
|
||||
//
|
||||
@@ -445,7 +443,7 @@
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": 15,
|
||||
// "font_size": "15",
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono",
|
||||
|
||||
@@ -103,7 +103,6 @@ pub struct OpenAiResponseStreamEvent {
|
||||
}
|
||||
|
||||
pub async fn stream_completion(
|
||||
api_url: String,
|
||||
credential: ProviderCredential,
|
||||
executor: BackgroundExecutor,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
@@ -118,7 +117,7 @@ pub async fn stream_completion(
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
|
||||
|
||||
let json_data = request.data()?;
|
||||
let mut response = Request::post(format!("{api_url}/chat/completions"))
|
||||
let mut response = Request::post(format!("{OPEN_AI_API_URL}/chat/completions"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.body(json_data)?
|
||||
@@ -196,20 +195,18 @@ pub async fn stream_completion(
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiCompletionProvider {
|
||||
api_url: String,
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl OpenAiCompletionProvider {
|
||||
pub async fn new(api_url: String, model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
pub async fn new(model_name: String, executor: BackgroundExecutor) -> Self {
|
||||
let model = executor
|
||||
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
|
||||
.await;
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
api_url,
|
||||
model,
|
||||
credential,
|
||||
executor,
|
||||
@@ -306,8 +303,7 @@ impl CompletionProvider for OpenAiCompletionProvider {
|
||||
// which is currently model based, due to the language model.
|
||||
// At some point in the future we should rectify this.
|
||||
let credential = self.credential.read().clone();
|
||||
let api_url = self.api_url.clone();
|
||||
let request = stream_completion(api_url, credential, self.executor.clone(), prompt);
|
||||
let request = stream_completion(credential, self.executor.clone(), prompt);
|
||||
async move {
|
||||
let response = request.await?;
|
||||
let stream = response
|
||||
|
||||
@@ -35,7 +35,6 @@ lazy_static! {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiEmbeddingProvider {
|
||||
api_url: String,
|
||||
model: OpenAiLanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
pub client: Arc<dyn HttpClient>,
|
||||
@@ -70,11 +69,7 @@ struct OpenAiEmbeddingUsage {
|
||||
}
|
||||
|
||||
impl OpenAiEmbeddingProvider {
|
||||
pub async fn new(
|
||||
api_url: String,
|
||||
client: Arc<dyn HttpClient>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Self {
|
||||
pub async fn new(client: Arc<dyn HttpClient>, executor: BackgroundExecutor) -> Self {
|
||||
let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
|
||||
let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
|
||||
|
||||
@@ -85,7 +80,6 @@ impl OpenAiEmbeddingProvider {
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
|
||||
OpenAiEmbeddingProvider {
|
||||
api_url,
|
||||
model,
|
||||
credential,
|
||||
client,
|
||||
@@ -136,12 +130,11 @@ impl OpenAiEmbeddingProvider {
|
||||
}
|
||||
async fn send_request(
|
||||
&self,
|
||||
api_url: &str,
|
||||
api_key: &str,
|
||||
spans: Vec<&str>,
|
||||
request_timeout: u64,
|
||||
) -> Result<Response<AsyncBody>> {
|
||||
let request = Request::post(format!("{api_url}/embeddings"))
|
||||
let request = Request::post(format!("{OPEN_AI_API_URL}/embeddings"))
|
||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||
.timeout(Duration::from_secs(request_timeout))
|
||||
.header("Content-Type", "application/json")
|
||||
@@ -253,7 +246,6 @@ impl EmbeddingProvider for OpenAiEmbeddingProvider {
|
||||
const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
|
||||
const MAX_RETRIES: usize = 4;
|
||||
|
||||
let api_url = self.api_url.as_str();
|
||||
let api_key = self.get_api_key()?;
|
||||
|
||||
let mut request_number = 0;
|
||||
@@ -263,7 +255,6 @@ impl EmbeddingProvider for OpenAiEmbeddingProvider {
|
||||
while request_number < MAX_RETRIES {
|
||||
response = self
|
||||
.send_request(
|
||||
&api_url,
|
||||
&api_key,
|
||||
spans.iter().map(|x| &**x).collect(),
|
||||
request_timeout,
|
||||
|
||||
@@ -68,7 +68,6 @@ struct SavedConversation {
|
||||
messages: Vec<SavedMessage>,
|
||||
message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: String,
|
||||
api_url: Option<String>,
|
||||
model: OpenAiModel,
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::{
|
||||
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
|
||||
};
|
||||
use ai::prompts::repository_context::PromptCodeSnippet;
|
||||
use ai::providers::open_ai::OPEN_AI_API_URL;
|
||||
use ai::{
|
||||
auth::ProviderCredential,
|
||||
completion::{CompletionProvider, CompletionRequest},
|
||||
@@ -122,22 +121,10 @@ impl AssistantPanel {
|
||||
.await
|
||||
.log_err()
|
||||
.unwrap_or_default();
|
||||
let (api_url, model_name) = cx
|
||||
.update(|cx| {
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
(
|
||||
settings.openai_api_url.clone(),
|
||||
settings.default_open_ai_model.full_name().to_string(),
|
||||
)
|
||||
})
|
||||
.log_err()
|
||||
.unwrap();
|
||||
let completion_provider = OpenAiCompletionProvider::new(
|
||||
api_url,
|
||||
model_name,
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await;
|
||||
// Defaulting currently to GPT4, allow for this to be set via config.
|
||||
let completion_provider =
|
||||
OpenAiCompletionProvider::new("gpt-4".into(), cx.background_executor().clone())
|
||||
.await;
|
||||
|
||||
// TODO: deserialize state.
|
||||
let workspace_handle = workspace.clone();
|
||||
@@ -1420,7 +1407,6 @@ struct Conversation {
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
model: OpenAiModel,
|
||||
api_url: Option<String>,
|
||||
token_count: Option<usize>,
|
||||
max_token_count: usize,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
@@ -1455,7 +1441,6 @@ impl Conversation {
|
||||
|
||||
let settings = AssistantSettings::get_global(cx);
|
||||
let model = settings.default_open_ai_model.clone();
|
||||
let api_url = settings.openai_api_url.clone();
|
||||
|
||||
let mut this = Self {
|
||||
id: Some(Uuid::new_v4().to_string()),
|
||||
@@ -1469,7 +1454,6 @@ impl Conversation {
|
||||
token_count: None,
|
||||
max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
|
||||
pending_token_count: Task::ready(None),
|
||||
api_url: Some(api_url),
|
||||
model: model.clone(),
|
||||
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
||||
pending_save: Task::ready(Ok(())),
|
||||
@@ -1515,7 +1499,6 @@ impl Conversation {
|
||||
.map(|summary| summary.text.clone())
|
||||
.unwrap_or_default(),
|
||||
model: self.model.clone(),
|
||||
api_url: self.api_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1530,12 +1513,8 @@ impl Conversation {
|
||||
None => Some(Uuid::new_v4().to_string()),
|
||||
};
|
||||
let model = saved_conversation.model;
|
||||
let api_url = saved_conversation.api_url;
|
||||
let completion_provider: Arc<dyn CompletionProvider> = Arc::new(
|
||||
OpenAiCompletionProvider::new(
|
||||
api_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| OPEN_AI_API_URL.to_string()),
|
||||
model.full_name().into(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
@@ -1588,7 +1567,6 @@ impl Conversation {
|
||||
token_count: None,
|
||||
max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()),
|
||||
pending_token_count: Task::ready(None),
|
||||
api_url,
|
||||
model,
|
||||
_subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
|
||||
pending_save: Task::ready(Ok(())),
|
||||
|
||||
@@ -55,7 +55,6 @@ pub struct AssistantSettings {
|
||||
pub default_width: Pixels,
|
||||
pub default_height: Pixels,
|
||||
pub default_open_ai_model: OpenAiModel,
|
||||
pub openai_api_url: String,
|
||||
}
|
||||
|
||||
/// Assistant panel settings
|
||||
@@ -81,10 +80,6 @@ pub struct AssistantSettingsContent {
|
||||
///
|
||||
/// Default: gpt-4-1106-preview
|
||||
pub default_open_ai_model: Option<OpenAiModel>,
|
||||
/// OpenAI API base URL to use when starting new conversations.
|
||||
///
|
||||
/// Default: https://api.openai.com/v1
|
||||
pub openai_api_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for AssistantSettings {
|
||||
|
||||
@@ -84,7 +84,6 @@ pub struct ActiveCall {
|
||||
),
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
pending_channel_id: Option<u64>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
}
|
||||
|
||||
@@ -98,7 +97,6 @@ impl ActiveCall {
|
||||
location: None,
|
||||
pending_invites: Default::default(),
|
||||
incoming_call: watch::channel(),
|
||||
pending_channel_id: None,
|
||||
_join_debouncer: OneAtATime { cancel: None },
|
||||
_subscriptions: vec![
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_incoming_call),
|
||||
@@ -113,10 +111,6 @@ impl ActiveCall {
|
||||
self.room()?.read(cx).channel_id()
|
||||
}
|
||||
|
||||
pub fn pending_channel_id(&self) -> Option<u64> {
|
||||
self.pending_channel_id
|
||||
}
|
||||
|
||||
async fn handle_incoming_call(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::IncomingCall>,
|
||||
@@ -345,13 +339,11 @@ impl ActiveCall {
|
||||
channel_id: u64,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Option<Model<Room>>>> {
|
||||
let mut leave = None;
|
||||
if let Some(room) = self.room().cloned() {
|
||||
if room.read(cx).channel_id() == Some(channel_id) {
|
||||
return Task::ready(Ok(Some(room)));
|
||||
} else {
|
||||
let (room, _) = self.room.take().unwrap();
|
||||
leave = room.update(cx, |room, cx| Some(room.leave(cx)));
|
||||
room.update(cx, |room, cx| room.clear_state(cx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,21 +353,14 @@ impl ActiveCall {
|
||||
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
self.pending_channel_id = Some(channel_id);
|
||||
let join = self._join_debouncer.spawn(cx, move |cx| async move {
|
||||
if let Some(task) = leave {
|
||||
task.await?
|
||||
}
|
||||
Room::join_channel(channel_id, client, user_store, cx).await
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let room = join.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_channel_id.take();
|
||||
this.set_room(room.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| this.set_room(room.clone(), cx))?
|
||||
.await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.report_call_event("join channel", cx)
|
||||
})?;
|
||||
|
||||
@@ -1131,10 +1131,9 @@ impl ChannelState {
|
||||
if let Some(latest_version) = &self.latest_notes_versions {
|
||||
if let Some(observed_version) = &self.observed_notes_versions {
|
||||
latest_version.epoch > observed_version.epoch
|
||||
|| (latest_version.epoch == observed_version.epoch
|
||||
&& latest_version
|
||||
.version
|
||||
.changed_since(&observed_version.version))
|
||||
|| latest_version
|
||||
.version
|
||||
.changed_since(&observed_version.version)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -587,9 +587,6 @@ pub struct ChannelsForUser {
|
||||
pub channels: Vec<Channel>,
|
||||
pub channel_memberships: Vec<channel_member::Model>,
|
||||
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
|
||||
|
||||
pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub observed_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
|
||||
pub latest_channel_messages: Vec<proto::ChannelMessageId>,
|
||||
}
|
||||
|
||||
@@ -561,6 +561,7 @@ impl Database {
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<()> {
|
||||
use observed_buffer_edits::Column;
|
||||
|
||||
observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
buffer_id: ActiveValue::Set(buffer_id),
|
||||
@@ -670,7 +671,7 @@ impl Database {
|
||||
buffer_id: row.buffer_id,
|
||||
epoch: row.epoch,
|
||||
lamport_timestamp: row.lamport_timestamp,
|
||||
replica_id: row.replica_id,
|
||||
replica_id: row.lamport_timestamp,
|
||||
value: Default::default(),
|
||||
});
|
||||
operations.push(proto::Operation {
|
||||
@@ -749,9 +750,20 @@ impl Database {
|
||||
|
||||
pub async fn latest_channel_buffer_changes(
|
||||
&self,
|
||||
channel_ids_by_buffer_id: &HashMap<BufferId, ChannelId>,
|
||||
channel_ids: &[ChannelId],
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||
let mut rows = buffer::Entity::find()
|
||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
channel_ids_by_buffer_id.insert(row.id, row.channel_id);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let latest_operations = self
|
||||
.get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx)
|
||||
.await?;
|
||||
@@ -771,36 +783,6 @@ impl Database {
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn observed_channel_buffer_changes(
|
||||
&self,
|
||||
channel_ids_by_buffer_id: &HashMap<BufferId, ChannelId>,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelBufferVersion>> {
|
||||
let observed_operations = observed_buffer_edits::Entity::find()
|
||||
.filter(observed_buffer_edits::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
observed_buffer_edits::Column::BufferId
|
||||
.is_in(channel_ids_by_buffer_id.keys().copied()),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(observed_operations
|
||||
.iter()
|
||||
.flat_map(|op| {
|
||||
Some(proto::ChannelBufferVersion {
|
||||
channel_id: channel_ids_by_buffer_id.get(&op.buffer_id)?.to_proto(),
|
||||
epoch: op.epoch as u64,
|
||||
version: vec![proto::VectorClockEntry {
|
||||
replica_id: op.replica_id as u32,
|
||||
timestamp: op.lamport_timestamp as u32,
|
||||
}],
|
||||
})
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the latest operations for the buffers with the specified IDs.
|
||||
pub async fn get_latest_operations_for_buffers(
|
||||
&self,
|
||||
|
||||
@@ -673,40 +673,18 @@ impl Database {
|
||||
}
|
||||
|
||||
let channel_ids = channels.iter().map(|c| c.id).collect::<Vec<_>>();
|
||||
|
||||
let mut channel_ids_by_buffer_id = HashMap::default();
|
||||
let mut rows = buffer::Entity::find()
|
||||
.filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied()))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
channel_ids_by_buffer_id.insert(row.id, row.channel_id);
|
||||
}
|
||||
drop(rows);
|
||||
|
||||
let latest_buffer_versions = self
|
||||
.latest_channel_buffer_changes(&channel_ids_by_buffer_id, &*tx)
|
||||
.latest_channel_buffer_changes(&channel_ids, &*tx)
|
||||
.await?;
|
||||
|
||||
let latest_channel_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||
|
||||
let observed_buffer_versions = self
|
||||
.observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, &*tx)
|
||||
.await?;
|
||||
|
||||
let observed_channel_messages = self
|
||||
.observed_channel_messages(&channel_ids, user_id, &*tx)
|
||||
.await?;
|
||||
let latest_messages = self.latest_channel_messages(&channel_ids, &*tx).await?;
|
||||
|
||||
Ok(ChannelsForUser {
|
||||
channel_memberships,
|
||||
channels,
|
||||
channel_participants,
|
||||
latest_buffer_versions,
|
||||
latest_channel_messages,
|
||||
observed_buffer_versions,
|
||||
observed_channel_messages,
|
||||
latest_channel_messages: latest_messages,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -388,30 +388,6 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn observed_channel_messages(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::ChannelMessageId>> {
|
||||
let rows = observed_channel_messages::Entity::find()
|
||||
.filter(observed_channel_messages::Column::UserId.eq(user_id))
|
||||
.filter(
|
||||
observed_channel_messages::Column::ChannelId
|
||||
.is_in(channel_ids.iter().map(|id| id.0)),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(|message| proto::ChannelMessageId {
|
||||
channel_id: message.channel_id.to_proto(),
|
||||
message_id: message.channel_message_id.to_proto(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn latest_channel_messages(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
|
||||
@@ -337,12 +337,17 @@ async fn test_channel_buffers_last_operations(db: &Database) {
|
||||
let buffer_changes = db
|
||||
.transaction(|tx| {
|
||||
let buffers = &buffers;
|
||||
let mut hash = HashMap::default();
|
||||
hash.insert(buffers[0].id, buffers[0].channel_id);
|
||||
hash.insert(buffers[1].id, buffers[1].channel_id);
|
||||
hash.insert(buffers[2].id, buffers[2].channel_id);
|
||||
|
||||
async move { db.latest_channel_buffer_changes(&hash, &*tx).await }
|
||||
async move {
|
||||
db.latest_channel_buffer_changes(
|
||||
&[
|
||||
buffers[0].channel_id,
|
||||
buffers[1].channel_id,
|
||||
buffers[2].channel_id,
|
||||
],
|
||||
&*tx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -620,7 +620,7 @@ impl Server {
|
||||
let mut pool = this.connection_pool.lock();
|
||||
pool.add_connection(connection_id, user_id, user.admin);
|
||||
this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user))?;
|
||||
this.peer.send(connection_id, build_update_user_channels(&channels_for_user.channel_memberships))?;
|
||||
this.peer.send(connection_id, build_channels_update(
|
||||
channels_for_user,
|
||||
channel_invites
|
||||
@@ -3440,18 +3440,17 @@ fn notify_membership_updated(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_update_user_channels(channels: &ChannelsForUser) -> proto::UpdateUserChannels {
|
||||
fn build_update_user_channels(
|
||||
memberships: &Vec<db::channel_member::Model>,
|
||||
) -> proto::UpdateUserChannels {
|
||||
proto::UpdateUserChannels {
|
||||
channel_memberships: channels
|
||||
.channel_memberships
|
||||
channel_memberships: memberships
|
||||
.iter()
|
||||
.map(|m| proto::ChannelMembership {
|
||||
channel_id: m.channel_id.to_proto(),
|
||||
role: m.role.into(),
|
||||
})
|
||||
.collect(),
|
||||
observed_channel_buffer_version: channels.observed_buffer_versions.clone(),
|
||||
observed_channel_message_id: channels.observed_channel_messages.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
tests::{test_server::open_channel_notes, TestServer},
|
||||
tests::TestServer,
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::ACKNOWLEDGE_DEBOUNCE_INTERVAL;
|
||||
@@ -605,75 +605,113 @@ async fn test_channel_buffer_changes(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let (server, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
|
||||
let (_, cx_a) = client_a.build_test_workspace(cx_a).await;
|
||||
let (workspace_b, cx_b) = client_b.build_test_workspace(cx_b).await;
|
||||
let channel_store_b = client_b.channel_store().clone();
|
||||
let mut server = TestServer::start(deterministic.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
||||
// Editing the channel notes should set them to dirty
|
||||
open_channel_notes(channel_id, cx_a).await.unwrap();
|
||||
cx_a.simulate_keystrokes("1");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(channel_store.has_channel_buffer_changed(channel_id))
|
||||
let channel_id = server
|
||||
.make_channel(
|
||||
"the-channel",
|
||||
None,
|
||||
(&client_a, cx_a),
|
||||
&mut [(&client_b, cx_b)],
|
||||
)
|
||||
.await;
|
||||
|
||||
let channel_buffer_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A makes an edit, and client B should see that the note has changed.
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "1")], None, cx);
|
||||
})
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.update(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
|
||||
// Opening the buffer should clear the changed flag.
|
||||
open_channel_notes(channel_id, cx_b).await.unwrap();
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
let project_b = client_b.build_empty_local_project(cx_b);
|
||||
let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
|
||||
let channel_view_b = cx_b
|
||||
.update(|cx| ChannelView::open(channel_id, None, workspace_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.update(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
// Editing the channel while the buffer is open should not show that the buffer has changed.
|
||||
cx_a.simulate_keystrokes("2");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "2")], None, cx);
|
||||
})
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
|
||||
|
||||
// Test that the server is tracking things correctly, and we retain our 'not changed'
|
||||
// state across a disconnect
|
||||
deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL);
|
||||
server
|
||||
.simulate_long_connection_interruption(client_b.peer_id().unwrap(), deterministic.clone());
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(!has_buffer_changed);
|
||||
|
||||
// Closing the buffer should re-enable change tracking
|
||||
cx_b.update(|cx| {
|
||||
workspace_b.update(cx, |workspace, cx| {
|
||||
workspace.close_all_items_and_panes(&Default::default(), cx)
|
||||
});
|
||||
|
||||
drop(channel_view_b)
|
||||
});
|
||||
|
||||
deterministic.run_until_parked();
|
||||
|
||||
channel_buffer_a.update(cx_a, |buffer, cx| {
|
||||
buffer.buffer().update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "3")], None, cx);
|
||||
})
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
cx_a.simulate_keystrokes("3");
|
||||
channel_store_b.read_with(cx_b, |channel_store, _| {
|
||||
assert!(channel_store.has_channel_buffer_changed(channel_id))
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_channel_buffer_changes_persist(
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
cx_b2: &mut TestAppContext,
|
||||
) {
|
||||
let (mut server, client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await;
|
||||
let (_, cx_a) = client_a.build_test_workspace(cx_a).await;
|
||||
let (_, cx_b) = client_b.build_test_workspace(cx_b).await;
|
||||
|
||||
// a) edits the notes
|
||||
open_channel_notes(channel_id, cx_a).await.unwrap();
|
||||
cx_a.simulate_keystrokes("1");
|
||||
// b) opens them to observe the current version
|
||||
open_channel_notes(channel_id, cx_b).await.unwrap();
|
||||
|
||||
// On boot the client should get the correct state.
|
||||
let client_b2 = server.create_client(cx_b2, "user_b").await;
|
||||
let channel_store_b2 = client_b2.channel_store().clone();
|
||||
channel_store_b2.read_with(cx_b2, |channel_store, _| {
|
||||
assert!(!channel_store.has_channel_buffer_changed(channel_id))
|
||||
let has_buffer_changed = cx_b.read(|cx| {
|
||||
client_b
|
||||
.channel_store()
|
||||
.read(cx)
|
||||
.has_channel_buffer_changed(channel_id)
|
||||
});
|
||||
assert!(has_buffer_changed);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@@ -10,7 +10,6 @@ use channel::{ChannelBuffer, ChannelStore};
|
||||
use client::{
|
||||
self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore,
|
||||
};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::FakeFs;
|
||||
use futures::{channel::oneshot, StreamExt as _};
|
||||
@@ -767,16 +766,6 @@ pub fn join_channel_call(cx: &mut TestAppContext) -> Task<anyhow::Result<()>> {
|
||||
room.unwrap().update(cx, |room, cx| room.join_call(cx))
|
||||
}
|
||||
|
||||
pub fn open_channel_notes(
|
||||
channel_id: u64,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Task<anyhow::Result<View<ChannelView>>> {
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
let view = window.root_view(cx).unwrap();
|
||||
|
||||
cx.update(|cx| ChannelView::open(channel_id, None, view.clone(), cx))
|
||||
}
|
||||
|
||||
impl Drop for TestClient {
|
||||
fn drop(&mut self) {
|
||||
self.app_state.client.teardown();
|
||||
|
||||
@@ -504,20 +504,6 @@ impl CollabPanel {
|
||||
role: proto::ChannelRole::Member,
|
||||
}));
|
||||
}
|
||||
} else if let Some(channel_id) = ActiveCall::global(cx).read(cx).pending_channel_id() {
|
||||
self.entries.push(ListEntry::Header(Section::ActiveCall));
|
||||
if !old_entries
|
||||
.iter()
|
||||
.any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
|
||||
{
|
||||
scroll_to_top = true;
|
||||
}
|
||||
|
||||
if query.is_empty() {
|
||||
self.entries.push(ListEntry::ChannelCall { channel_id });
|
||||
self.entries.push(ListEntry::ChannelNotes { channel_id });
|
||||
self.entries.push(ListEntry::ChannelChat { channel_id });
|
||||
}
|
||||
}
|
||||
|
||||
let mut request_entries = Vec::new();
|
||||
@@ -2209,10 +2195,7 @@ impl CollabPanel {
|
||||
let text = match section {
|
||||
Section::ActiveCall => {
|
||||
let channel_name = maybe!({
|
||||
let channel_id = ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.channel_id(cx)
|
||||
.or_else(|| ActiveCall::global(cx).read(cx).pending_channel_id())?;
|
||||
let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
|
||||
|
||||
let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
|
||||
|
||||
|
||||
@@ -444,6 +444,7 @@ impl Copilot {
|
||||
|_, _| { /* Silence the notification */ },
|
||||
)
|
||||
.detach();
|
||||
|
||||
let server = cx.update(|cx| server.initialize(None, cx))?.await?;
|
||||
|
||||
let status = server
|
||||
|
||||
@@ -773,11 +773,11 @@ impl CompletionsMenu {
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
return cx.spawn(move |this, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
this.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn attempt_resolve_selected_completion_documentation(
|
||||
|
||||
@@ -374,7 +374,7 @@ impl EditorElement {
|
||||
) {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if !text_bounds.contains(&mouse_position)
|
||||
|| !cx.is_top_layer(&mouse_position, stacking_order)
|
||||
|| !cx.was_top_layer(&mouse_position, stacking_order)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -406,7 +406,7 @@ impl EditorElement {
|
||||
} else if !text_bounds.contains(&event.position) {
|
||||
return;
|
||||
}
|
||||
if !cx.is_top_layer(&event.position, stacking_order) {
|
||||
if !cx.was_top_layer(&event.position, stacking_order) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -478,11 +478,11 @@ impl EditorElement {
|
||||
editor.select(SelectPhase::End, cx);
|
||||
}
|
||||
|
||||
if interactive_bounds.did_visibly_contains(&event.position, cx)
|
||||
if interactive_bounds.visibly_contains(&event.position, cx)
|
||||
&& !pending_nonempty_selections
|
||||
&& event.modifiers.command
|
||||
&& text_bounds.contains(&event.position)
|
||||
&& cx.is_top_layer(&event.position, stacking_order)
|
||||
&& cx.was_top_layer(&event.position, stacking_order)
|
||||
{
|
||||
let point = position_map.point_for_position(text_bounds, event.position);
|
||||
editor.handle_click_hovered_link(point, event.modifiers, cx);
|
||||
@@ -550,7 +550,7 @@ impl EditorElement {
|
||||
let modifiers = event.modifiers;
|
||||
let text_hovered = text_bounds.contains(&event.position);
|
||||
let gutter_hovered = gutter_bounds.contains(&event.position);
|
||||
let was_top = cx.is_top_layer(&event.position, stacking_order);
|
||||
let was_top = cx.was_top_layer(&event.position, stacking_order);
|
||||
|
||||
editor.set_gutter_hovered(gutter_hovered, cx);
|
||||
|
||||
@@ -903,7 +903,7 @@ impl EditorElement {
|
||||
bounds: text_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_text_bounds.did_visibly_contains(&cx.mouse_position(), cx) {
|
||||
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
if self
|
||||
.editor
|
||||
.read(cx)
|
||||
@@ -1015,13 +1015,13 @@ impl EditorElement {
|
||||
let corner_radius = 0.15 * layout.position_map.line_height;
|
||||
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
|
||||
|
||||
for (participant_ix, (player_color, selections)) in
|
||||
for (participant_ix, (selection_style, selections)) in
|
||||
layout.selections.iter().enumerate()
|
||||
{
|
||||
for selection in selections.into_iter() {
|
||||
self.paint_highlighted_range(
|
||||
selection.range.clone(),
|
||||
player_color.selection,
|
||||
selection_style.selection,
|
||||
corner_radius,
|
||||
corner_radius * 2.,
|
||||
layout,
|
||||
@@ -1102,7 +1102,7 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
cursors.push(Cursor {
|
||||
color: player_color.cursor,
|
||||
color: selection_style.cursor,
|
||||
block_width,
|
||||
origin: point(x, y),
|
||||
line_height: layout.position_map.line_height,
|
||||
@@ -1242,7 +1242,7 @@ impl EditorElement {
|
||||
popover_origin.x = popover_origin.x + x_out_of_bounds;
|
||||
}
|
||||
|
||||
if cx.is_top_layer(&popover_origin, cx.stacking_order()) {
|
||||
if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
|
||||
cx.break_content_mask(|cx| {
|
||||
hover_popover.draw(popover_origin, available_space, cx)
|
||||
});
|
||||
@@ -1537,7 +1537,7 @@ impl EditorElement {
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
let mut mouse_position = cx.mouse_position();
|
||||
if interactive_track_bounds.did_visibly_contains(&mouse_position, cx) {
|
||||
if interactive_track_bounds.visibly_contains(&mouse_position, cx) {
|
||||
cx.set_cursor_style(CursorStyle::Arrow);
|
||||
}
|
||||
|
||||
@@ -1567,7 +1567,7 @@ impl EditorElement {
|
||||
cx.stop_propagation();
|
||||
} else {
|
||||
editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
|
||||
if interactive_track_bounds.did_visibly_contains(&event.position, cx) {
|
||||
if interactive_track_bounds.visibly_contains(&event.position, cx) {
|
||||
editor.scroll_manager.show_scrollbar(cx);
|
||||
}
|
||||
}
|
||||
@@ -2652,14 +2652,14 @@ impl EditorElement {
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
delta = delta.coalesce(event.delta);
|
||||
editor.update(cx, |editor, cx| {
|
||||
let position = event.position;
|
||||
let position_map: &PositionMap = &position_map;
|
||||
let bounds = &interactive_bounds;
|
||||
if !bounds.did_visibly_contains(&position, cx) {
|
||||
if !bounds.visibly_contains(&position, cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2719,7 +2719,7 @@ impl EditorElement {
|
||||
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
match event.button {
|
||||
MouseButton::Left => editor.update(cx, |editor, cx| {
|
||||
@@ -2786,7 +2786,7 @@ impl EditorElement {
|
||||
)
|
||||
}
|
||||
|
||||
if interactive_bounds.did_visibly_contains(&event.position, cx) {
|
||||
if interactive_bounds.visibly_contains(&event.position, cx) {
|
||||
Self::mouse_moved(
|
||||
editor,
|
||||
event,
|
||||
@@ -3067,7 +3067,7 @@ impl Element for EditorElement {
|
||||
) {
|
||||
let editor = self.editor.clone();
|
||||
|
||||
cx.with_view(self.editor.entity_id(), |cx| {
|
||||
cx.paint_view(self.editor.entity_id(), |cx| {
|
||||
cx.with_text_style(
|
||||
Some(gpui::TextStyleRefinement {
|
||||
font_size: Some(self.style.text.font_size),
|
||||
|
||||
@@ -8,10 +8,6 @@ license = "GPL-3.0-or-later"
|
||||
[lib]
|
||||
path = "src/extension_store.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "extension_json_schemas"
|
||||
path = "src/extension_json_schemas.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
collections.workspace = true
|
||||
@@ -20,7 +16,6 @@ futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
parking_lot.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
use language::LanguageConfig;
|
||||
use schemars::schema_for;
|
||||
use theme::ThemeFamilyContent;
|
||||
|
||||
fn main() {
|
||||
let theme_family_schema = schema_for!(ThemeFamilyContent);
|
||||
let language_config_schema = schema_for!(LanguageConfig);
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&theme_family_schema).unwrap()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&language_config_schema).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -99,7 +99,7 @@ flume = "0.11"
|
||||
xcb = { version = "1.3", features = ["as-raw-xcb-connection"] }
|
||||
as-raw-xcb-connection = "1"
|
||||
#TODO: use these on all platforms
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "c4f951a88b345724cb952e920ad30e39851f7760" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "c4f951a88b345724cb952e920ad30e39851f7760" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "26bc5e8b9ef67b4f2970eb95888db733eace98f3" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "26bc5e8b9ef67b4f2970eb95888db733eace98f3" }
|
||||
bytemuck = "1"
|
||||
cosmic-text = "0.10.0"
|
||||
@@ -40,7 +40,7 @@ use crate::{
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::{type_name_of_val, Any}, fmt::Debug, ops::DerefMut};
|
||||
use std::{any::Any, fmt::Debug, ops::DerefMut};
|
||||
|
||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||
@@ -59,22 +59,20 @@ pub trait Element: 'static + IntoElement {
|
||||
cx: &mut ElementContext,
|
||||
) -> (LayoutId, Self::State);
|
||||
|
||||
/// After layout is performed, we assign each element its bounds. Some elements may
|
||||
/// need to use these bounds to render and layout the appropriate children. This is
|
||||
/// also each element's opportunity to add opaque layers before painting [`crate::window::element_cx::add_opaque_layer`].
|
||||
fn post_layout(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::State,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
dbg!(type_name_of_val(self));
|
||||
// TODO: Delete this default implementation once we've implemented it everywhere.
|
||||
}
|
||||
|
||||
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
||||
/// If `wet` is false, this is a dry run, which is used to determine which geometry will be closest
|
||||
/// to the user in the painted scene for purposes of hover state calculations. Avoid unnecessary
|
||||
/// computation in the dry phase, but be sure to push opaque layers at a Z-index that matches what
|
||||
/// you'll draw in the final scene.
|
||||
///
|
||||
/// The state argument is the same state that was returned from [`Element::request_layout()`].
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
);
|
||||
|
||||
/// Convert this element into a dynamically-typed [`AnyElement`].
|
||||
fn into_any(self) -> AnyElement {
|
||||
@@ -121,10 +119,9 @@ pub trait IntoElement: Sized {
|
||||
phase: ElementDrawPhase::Start,
|
||||
};
|
||||
|
||||
let frame_state =
|
||||
DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
|
||||
element.draw(origin, available_space.map(Into::into), cx);
|
||||
|
||||
if let Some(mut frame_state) = frame_state {
|
||||
if let Some(mut frame_state) = element.into_frame_state() {
|
||||
f(&mut frame_state, cx)
|
||||
} else {
|
||||
cx.with_element_state(element_id.unwrap(), |element_state, cx| {
|
||||
@@ -216,8 +213,14 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||
(layout_id, element)
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
element.paint(cx)
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
element: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
element.paint(wet, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,9 +245,7 @@ trait ElementObject {
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||
|
||||
fn post_layout(&mut self, cx: &mut ElementContext);
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext);
|
||||
fn paint(&mut self, wet: bool, cx: &mut ElementContext);
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
@@ -316,45 +317,7 @@ impl<E: Element> DrawableElement<E> {
|
||||
layout_id
|
||||
}
|
||||
|
||||
fn post_layout(&mut self, cx: &mut ElementContext) {
|
||||
println!("DrawableElement post_layout");
|
||||
match &mut self.phase {
|
||||
ElementDrawPhase::LayoutRequested {
|
||||
layout_id,
|
||||
frame_state,
|
||||
..
|
||||
}
|
||||
| ElementDrawPhase::LayoutComputed {
|
||||
layout_id,
|
||||
frame_state,
|
||||
..
|
||||
} => {
|
||||
let bounds = cx.layout_bounds(*layout_id);
|
||||
let element = self.element.as_mut().unwrap();
|
||||
|
||||
if let Some(frame_state) = frame_state.as_mut() {
|
||||
println!("Some");
|
||||
element.post_layout(bounds, frame_state, cx);
|
||||
} else {
|
||||
println!("Else");
|
||||
let element_id = element
|
||||
.element_id()
|
||||
.expect("if we don't have frame state, we should have element state");
|
||||
|
||||
cx.with_element_state(element_id, |element_state, cx| {
|
||||
println!("with_element_state");
|
||||
let mut element_state = element_state.unwrap();
|
||||
element.post_layout(bounds, &mut element_state, cx);
|
||||
((), element_state)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_ => panic!("must call layout before post_layout"),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
|
||||
fn paint(&mut self, wet: bool, cx: &mut ElementContext) {
|
||||
match self.phase {
|
||||
ElementDrawPhase::LayoutRequested {
|
||||
layout_id,
|
||||
@@ -368,11 +331,8 @@ impl<E: Element> DrawableElement<E> {
|
||||
let bounds = cx.layout_bounds(layout_id);
|
||||
|
||||
if let Some(mut frame_state) = frame_state {
|
||||
self.element
|
||||
.take()
|
||||
.unwrap()
|
||||
.paint(bounds, &mut frame_state, cx);
|
||||
Some(frame_state)
|
||||
let mut element = self.element.as_mut().unwrap();
|
||||
element.paint(bounds, wet, &mut frame_state, cx);
|
||||
} else {
|
||||
let element_id = self
|
||||
.element
|
||||
@@ -382,13 +342,10 @@ impl<E: Element> DrawableElement<E> {
|
||||
.expect("if we don't have frame state, we should have element state");
|
||||
cx.with_element_state(element_id, |element_state, cx| {
|
||||
let mut element_state = element_state.unwrap();
|
||||
self.element
|
||||
.take()
|
||||
.unwrap()
|
||||
.paint(bounds, &mut element_state, cx);
|
||||
let mut element = self.element.as_mut().unwrap();
|
||||
element.paint(bounds, wet, &mut element_state, cx);
|
||||
((), element_state)
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,6 +353,14 @@ impl<E: Element> DrawableElement<E> {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_frame_state(self) -> Option<E::State> {
|
||||
match self.phase {
|
||||
ElementDrawPhase::Start => None,
|
||||
ElementDrawPhase::LayoutRequested { frame_state, .. }
|
||||
| ElementDrawPhase::LayoutComputed { frame_state, .. } => frame_state,
|
||||
}
|
||||
}
|
||||
|
||||
fn measure(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
@@ -437,13 +402,16 @@ impl<E: Element> DrawableElement<E> {
|
||||
}
|
||||
|
||||
fn draw(
|
||||
mut self,
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) -> Option<E::State> {
|
||||
) {
|
||||
self.measure(available_space, cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
|
||||
cx.with_absolute_element_offset(origin, |cx| {
|
||||
self.paint(false, cx);
|
||||
self.paint(true, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,13 +428,8 @@ where
|
||||
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
|
||||
}
|
||||
|
||||
fn post_layout(&mut self, cx: &mut ElementContext) {
|
||||
println!("ElementObject post_layout");
|
||||
DrawableElement::post_layout(self.as_mut().unwrap(), cx)
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext) {
|
||||
DrawableElement::paint(self.take().unwrap(), cx);
|
||||
fn paint(&mut self, wet: bool, cx: &mut ElementContext) {
|
||||
DrawableElement::paint(self.as_mut().unwrap(), wet, cx);
|
||||
}
|
||||
|
||||
fn measure(
|
||||
@@ -483,7 +446,7 @@ where
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
|
||||
DrawableElement::draw(self.as_mut().unwrap(), origin, available_space, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,15 +471,9 @@ impl AnyElement {
|
||||
self.0.request_layout(cx)
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub fn post_layout(&mut self, cx: &mut ElementContext) {
|
||||
println!("AnyElement post_layout");
|
||||
self.0.post_layout(cx)
|
||||
}
|
||||
|
||||
/// Paints the element stored in this `AnyElement`.
|
||||
pub fn paint(&mut self, cx: &mut ElementContext) {
|
||||
self.0.paint(cx)
|
||||
pub fn paint(&mut self, wet: bool, cx: &mut ElementContext) {
|
||||
self.0.paint(wet, cx)
|
||||
}
|
||||
|
||||
/// Initializes this element and performs layout within the given available space to determine its size.
|
||||
@@ -555,19 +512,15 @@ impl Element for AnyElement {
|
||||
let layout_id = self.request_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn post_layout(
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::State,
|
||||
_: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
_: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.0.post_layout(cx);
|
||||
}
|
||||
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
|
||||
self.paint(cx)
|
||||
self.paint(wet, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +569,7 @@ impl Element for () {
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_wet: bool,
|
||||
_state: &mut Self::State,
|
||||
_cx: &mut ElementContext,
|
||||
) {
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
|
||||
|
||||
/// Construct a canvas element with the given paint callback.
|
||||
/// Useful for adding short term custom drawing to a view.
|
||||
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementContext)) -> Canvas {
|
||||
pub fn canvas(
|
||||
callback: impl 'static + FnOnce(&Bounds<Pixels>, bool, &mut ElementContext),
|
||||
) -> Canvas {
|
||||
Canvas {
|
||||
paint_callback: Some(Box::new(callback)),
|
||||
style: StyleRefinement::default(),
|
||||
@@ -14,7 +16,7 @@ pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementConte
|
||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||
/// custom element
|
||||
pub struct Canvas {
|
||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
|
||||
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, bool, &mut ElementContext)>>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
@@ -44,9 +46,15 @@ impl Element for Canvas {
|
||||
(layout_id, style)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
style: &mut Style,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
style.paint(bounds, cx, |cx| {
|
||||
(self.paint_callback.take().unwrap())(&bounds, cx)
|
||||
(self.paint_callback.take().unwrap())(&bounds, wet, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Interactivity {
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
&& bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
@@ -105,9 +105,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_down_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
}));
|
||||
@@ -123,9 +121,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_down_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
}));
|
||||
@@ -144,7 +140,7 @@ impl Interactivity {
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == button
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
&& bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
@@ -161,9 +157,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_up_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Capture && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
}));
|
||||
@@ -179,9 +173,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_up_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx)
|
||||
}
|
||||
}));
|
||||
@@ -198,8 +190,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_down_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& !bounds.did_visibly_contains(&event.position, cx)
|
||||
if phase == DispatchPhase::Capture && !bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx)
|
||||
}
|
||||
@@ -220,7 +211,7 @@ impl Interactivity {
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
&& event.button == button
|
||||
&& !bounds.did_visibly_contains(&event.position, cx)
|
||||
&& !bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
(listener)(event, cx);
|
||||
}
|
||||
@@ -237,9 +228,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.mouse_move_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx);
|
||||
}
|
||||
}));
|
||||
@@ -288,9 +277,7 @@ impl Interactivity {
|
||||
) {
|
||||
self.scroll_wheel_listeners
|
||||
.push(Box::new(move |event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.did_visibly_contains(&event.position, cx)
|
||||
{
|
||||
if phase == DispatchPhase::Bubble && bounds.visibly_contains(&event.position, cx) {
|
||||
(listener)(event, cx);
|
||||
}
|
||||
}));
|
||||
@@ -577,7 +564,7 @@ pub trait InteractiveElement: Sized {
|
||||
self
|
||||
}
|
||||
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
/// Set a key that can be used to look up this element's bounds
|
||||
/// in the [`VisualTestContext::debug_bounds`] map
|
||||
/// This is a noop in release builds
|
||||
@@ -586,14 +573,14 @@ pub trait InteractiveElement: Sized {
|
||||
self
|
||||
}
|
||||
|
||||
// #[cfg(not(any(test, feature = "test-support")))]
|
||||
// /// Set a key that can be used to look up this element's bounds
|
||||
// /// in the [`VisualTestContext::debug_bounds`] map
|
||||
// /// This is a noop in release builds
|
||||
// #[inline]
|
||||
// fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
|
||||
// self
|
||||
// }
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
/// Set a key that can be used to look up this element's bounds
|
||||
/// in the [`VisualTestContext::debug_bounds`] map
|
||||
/// This is a noop in release builds
|
||||
#[inline]
|
||||
fn debug_selector(self, _: impl FnOnce() -> String) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
/// Bind the given callback to the mouse down event for any button, during the capture phase
|
||||
/// the fluent API equivalent to [`Interactivity::capture_any_mouse_down`]
|
||||
@@ -1085,7 +1072,6 @@ impl Element for Div {
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
(
|
||||
layout_id,
|
||||
DivState {
|
||||
@@ -1095,33 +1081,10 @@ impl Element for Div {
|
||||
)
|
||||
}
|
||||
|
||||
fn post_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
dbg!(self.children.len());
|
||||
self.interactivity.post_layout(
|
||||
bounds,
|
||||
&mut element_state.interactive_state,
|
||||
cx,
|
||||
|_, scroll_offset, cx| {
|
||||
println!("in continuation");
|
||||
cx.with_element_offset(scroll_offset, |cx| {
|
||||
println!("in with_element_offset");
|
||||
for child in &mut self.children {
|
||||
println!("child");
|
||||
child.post_layout(cx);
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
@@ -1166,7 +1129,7 @@ impl Element for Div {
|
||||
|_style, scroll_offset, cx| {
|
||||
cx.with_element_offset(scroll_offset, |cx| {
|
||||
for child in &mut self.children {
|
||||
child.paint(cx);
|
||||
child.paint(wet, cx);
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -1245,7 +1208,7 @@ pub struct Interactivity {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) location: Option<core::panic::Location<'static>>,
|
||||
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) debug_selector: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1260,7 +1223,7 @@ pub struct InteractiveBounds {
|
||||
|
||||
impl InteractiveBounds {
|
||||
/// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
|
||||
pub fn did_visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
|
||||
}
|
||||
|
||||
@@ -1268,7 +1231,7 @@ impl InteractiveBounds {
|
||||
/// under an active drag
|
||||
pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
|
||||
self.bounds.contains(point)
|
||||
&& cx.is_top_layer_under_active_drag(point, &self.stacking_order)
|
||||
&& cx.was_top_layer_under_active_drag(point, &self.stacking_order)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,29 +1275,6 @@ impl Interactivity {
|
||||
(layout_id, element_state)
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub fn post_layout(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element_state: &mut InteractiveElementState,
|
||||
cx: &mut ElementContext,
|
||||
continuation: impl FnOnce(Bounds<Pixels>, Point<Pixels>, &mut ElementContext),
|
||||
) {
|
||||
let z_index = self.base_style.z_index.unwrap_or(0);
|
||||
|
||||
cx.with_z_index(z_index, |cx| {
|
||||
println!("interactivity post_layout is adding an opaque layer");
|
||||
cx.add_opaque_layer(bounds.intersect(&cx.content_mask().bounds));
|
||||
|
||||
let scroll_offset = element_state
|
||||
.scroll_offset
|
||||
.as_ref()
|
||||
.map(|scroll_offset| *scroll_offset.borrow())
|
||||
.unwrap_or_default();
|
||||
continuation(bounds, scroll_offset, cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Paint this element according to this interactivity state's configured styles
|
||||
/// and bind the element's mouse and keyboard events.
|
||||
///
|
||||
@@ -1349,8 +1289,11 @@ impl Interactivity {
|
||||
content_size: Size<Pixels>,
|
||||
element_state: &mut InteractiveElementState,
|
||||
cx: &mut ElementContext,
|
||||
continuation: impl FnOnce(&Style, Point<Pixels>, &mut ElementContext),
|
||||
f: impl FnOnce(&Style, Point<Pixels>, &mut ElementContext),
|
||||
) {
|
||||
let style = self.compute_style(Some(bounds), element_state, cx);
|
||||
let z_index = style.z_index.unwrap_or(0);
|
||||
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
if let Some(debug_selector) = &self.debug_selector {
|
||||
cx.window
|
||||
@@ -1359,8 +1302,8 @@ impl Interactivity {
|
||||
.insert(debug_selector.clone(), bounds);
|
||||
}
|
||||
|
||||
let paint_hover_group_handler = |this: &Self, cx: &mut ElementContext| {
|
||||
let hover_group_bounds = this
|
||||
let paint_hover_group_handler = |cx: &mut ElementContext| {
|
||||
let hover_group_bounds = self
|
||||
.group_hover_style
|
||||
.as_ref()
|
||||
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
||||
@@ -1377,15 +1320,12 @@ impl Interactivity {
|
||||
}
|
||||
};
|
||||
|
||||
let z_index = self.base_style.z_index.unwrap_or(0);
|
||||
if style.visibility == Visibility::Hidden {
|
||||
cx.with_z_index(z_index, |cx| paint_hover_group_handler(cx));
|
||||
return;
|
||||
}
|
||||
|
||||
cx.with_z_index(z_index, |cx| {
|
||||
let style = self.compute_style(Some(bounds), element_state, cx);
|
||||
|
||||
if style.visibility == Visibility::Hidden {
|
||||
paint_hover_group_handler(self, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
style.paint(bounds, cx, |cx: &mut ElementContext| {
|
||||
cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||
cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
|
||||
@@ -1500,19 +1440,19 @@ impl Interactivity {
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
|
||||
// if self.block_mouse
|
||||
// || style.background.as_ref().is_some_and(|fill| {
|
||||
// fill.color().is_some_and(|color| !color.is_transparent())
|
||||
// })
|
||||
// {
|
||||
// cx.add_opaque_layer(interactive_bounds.bounds);
|
||||
// }
|
||||
if self.block_mouse
|
||||
|| style.background.as_ref().is_some_and(|fill| {
|
||||
fill.color().is_some_and(|color| !color.is_transparent())
|
||||
})
|
||||
{
|
||||
cx.add_opaque_layer(interactive_bounds.bounds);
|
||||
}
|
||||
|
||||
if !cx.has_active_drag() {
|
||||
if let Some(mouse_cursor) = style.mouse_cursor {
|
||||
let mouse_position = &cx.mouse_position();
|
||||
let hovered =
|
||||
interactive_bounds.did_visibly_contains(mouse_position, cx);
|
||||
interactive_bounds.visibly_contains(mouse_position, cx);
|
||||
if hovered {
|
||||
cx.set_cursor_style(mouse_cursor);
|
||||
}
|
||||
@@ -1528,8 +1468,7 @@ impl Interactivity {
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& !cx.default_prevented()
|
||||
&& interactive_bounds
|
||||
.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
cx.focus(&focus_handle);
|
||||
// If there is a parent that is also focusable, prevent it
|
||||
@@ -1568,7 +1507,7 @@ impl Interactivity {
|
||||
})
|
||||
}
|
||||
|
||||
paint_hover_group_handler(self, cx);
|
||||
paint_hover_group_handler(cx);
|
||||
|
||||
if self.hover_style.is_some()
|
||||
|| self.base_style.mouse_cursor.is_some()
|
||||
@@ -1647,8 +1586,7 @@ impl Interactivity {
|
||||
move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Left
|
||||
&& interactive_bounds
|
||||
.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
*pending_mouse_down.borrow_mut() = Some(event.clone());
|
||||
cx.refresh();
|
||||
@@ -1709,7 +1647,7 @@ impl Interactivity {
|
||||
DispatchPhase::Bubble => {
|
||||
if let Some(mouse_down) = captured_mouse_down.take() {
|
||||
if interactive_bounds
|
||||
.did_visibly_contains(&event.position, cx)
|
||||
.visibly_contains(&event.position, cx)
|
||||
{
|
||||
let mouse_click = ClickEvent {
|
||||
down: mouse_down,
|
||||
@@ -1741,7 +1679,7 @@ impl Interactivity {
|
||||
return;
|
||||
}
|
||||
let is_hovered = interactive_bounds
|
||||
.did_visibly_contains(&event.position, cx)
|
||||
.visibly_contains(&event.position, cx)
|
||||
&& has_mouse_down.borrow().is_none()
|
||||
&& !cx.has_active_drag();
|
||||
let mut was_hovered = was_hovered.borrow_mut();
|
||||
@@ -1768,7 +1706,7 @@ impl Interactivity {
|
||||
|
||||
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
|
||||
let is_hovered = interactive_bounds
|
||||
.did_visibly_contains(&event.position, cx)
|
||||
.visibly_contains(&event.position, cx)
|
||||
&& pending_mouse_down.borrow().is_none();
|
||||
if !is_hovered {
|
||||
active_tooltip.borrow_mut().take();
|
||||
@@ -1850,7 +1788,7 @@ impl Interactivity {
|
||||
let group = active_group_bounds
|
||||
.map_or(false, |bounds| bounds.contains(&down.position));
|
||||
let element =
|
||||
interactive_bounds.did_visibly_contains(&down.position, cx);
|
||||
interactive_bounds.visibly_contains(&down.position, cx);
|
||||
if group || element {
|
||||
*active_state.borrow_mut() =
|
||||
ElementClickedState { group, element };
|
||||
@@ -1903,7 +1841,7 @@ impl Interactivity {
|
||||
let interactive_bounds = interactive_bounds.clone();
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
let mut scroll_offset = scroll_offset.borrow_mut();
|
||||
let old_scroll_offset = *scroll_offset;
|
||||
@@ -1973,7 +1911,7 @@ impl Interactivity {
|
||||
cx.on_action(action_type, listener)
|
||||
}
|
||||
|
||||
continuation(&style, scroll_offset.unwrap_or_default(), cx)
|
||||
f(&style, scroll_offset.unwrap_or_default(), cx)
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1996,107 +1934,101 @@ impl Interactivity {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.base_style);
|
||||
|
||||
// cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
|
||||
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
|
||||
if let Some(in_focus_style) = self.in_focus_style.as_ref() {
|
||||
if focus_handle.within_focused(cx) {
|
||||
style.refine(in_focus_style);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(focus_style) = self.focus_style.as_ref() {
|
||||
if focus_handle.is_focused(cx) {
|
||||
style.refine(focus_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bounds) = bounds {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if !cx.has_active_drag() {
|
||||
if let Some(group_hover) = self.group_hover_style.as_ref() {
|
||||
if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx.deref_mut())
|
||||
{
|
||||
if group_bounds.contains(&mouse_position)
|
||||
&& cx.is_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(&group_hover.style);
|
||||
}
|
||||
cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
|
||||
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
|
||||
if let Some(in_focus_style) = self.in_focus_style.as_ref() {
|
||||
if focus_handle.within_focused(cx) {
|
||||
style.refine(in_focus_style);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(hover_style) = self.hover_style.as_ref() {
|
||||
let outer_button = self.debug_selector.as_deref() == Some("outer_button");
|
||||
let inner_button = self.debug_selector.as_deref() == Some("inner_button");
|
||||
if outer_button {
|
||||
cx.is_top_layer_debug(&mouse_position, cx.stacking_order());
|
||||
}
|
||||
println!("\n\n\n");
|
||||
|
||||
if bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains(&mouse_position)
|
||||
&& cx.is_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(hover_style);
|
||||
if let Some(focus_style) = self.focus_style.as_ref() {
|
||||
if focus_handle.is_focused(cx) {
|
||||
style.refine(focus_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(drag) = cx.active_drag.take() {
|
||||
let mut can_drop = true;
|
||||
if let Some(can_drop_predicate) = &self.can_drop_predicate {
|
||||
can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
|
||||
}
|
||||
|
||||
if can_drop {
|
||||
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
||||
if let Some(bounds) = bounds {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if !cx.has_active_drag() {
|
||||
if let Some(group_hover) = self.group_hover_style.as_ref() {
|
||||
if let Some(group_bounds) =
|
||||
GroupBounds::get(&group_drag_style.group, cx.deref_mut())
|
||||
GroupBounds::get(&group_hover.group, cx.deref_mut())
|
||||
{
|
||||
if *state_type == drag.value.as_ref().type_id()
|
||||
&& group_bounds.contains(&mouse_position)
|
||||
if group_bounds.contains(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(&group_drag_style.style);
|
||||
style.refine(&group_hover.style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (state_type, build_drag_over_style) in &self.drag_over_styles {
|
||||
if *state_type == drag.value.as_ref().type_id()
|
||||
&& bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains(&mouse_position)
|
||||
&& cx.is_top_layer_under_active_drag(
|
||||
&mouse_position,
|
||||
cx.stacking_order(),
|
||||
)
|
||||
if let Some(hover_style) = self.hover_style.as_ref() {
|
||||
if bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains(&mouse_position)
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
|
||||
style.refine(hover_style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.active_drag = Some(drag);
|
||||
}
|
||||
}
|
||||
if let Some(drag) = cx.active_drag.take() {
|
||||
let mut can_drop = true;
|
||||
if let Some(can_drop_predicate) = &self.can_drop_predicate {
|
||||
can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
|
||||
}
|
||||
|
||||
let clicked_state = element_state
|
||||
.clicked_state
|
||||
.get_or_insert_with(Default::default)
|
||||
.borrow();
|
||||
if clicked_state.group {
|
||||
if let Some(group) = self.group_active_style.as_ref() {
|
||||
style.refine(&group.style)
|
||||
}
|
||||
}
|
||||
if can_drop {
|
||||
for (state_type, group_drag_style) in &self.group_drag_over_styles {
|
||||
if let Some(group_bounds) =
|
||||
GroupBounds::get(&group_drag_style.group, cx.deref_mut())
|
||||
{
|
||||
if *state_type == drag.value.as_ref().type_id()
|
||||
&& group_bounds.contains(&mouse_position)
|
||||
{
|
||||
style.refine(&group_drag_style.style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_style) = self.active_style.as_ref() {
|
||||
if clicked_state.element {
|
||||
style.refine(active_style)
|
||||
for (state_type, build_drag_over_style) in &self.drag_over_styles {
|
||||
if *state_type == drag.value.as_ref().type_id()
|
||||
&& bounds
|
||||
.intersect(&cx.content_mask().bounds)
|
||||
.contains(&mouse_position)
|
||||
&& cx.was_top_layer_under_active_drag(
|
||||
&mouse_position,
|
||||
cx.stacking_order(),
|
||||
)
|
||||
{
|
||||
style.refine(&build_drag_over_style(drag.value.as_ref(), cx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cx.active_drag = Some(drag);
|
||||
}
|
||||
}
|
||||
}
|
||||
// });
|
||||
|
||||
let clicked_state = element_state
|
||||
.clicked_state
|
||||
.get_or_insert_with(Default::default)
|
||||
.borrow();
|
||||
if clicked_state.group {
|
||||
if let Some(group) = self.group_active_style.as_ref() {
|
||||
style.refine(&group.style)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_style) = self.active_style.as_ref() {
|
||||
if clicked_state.element {
|
||||
style.refine(active_style)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
style
|
||||
}
|
||||
@@ -2205,8 +2137,14 @@ where
|
||||
self.element.request_layout(state, cx)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
self.element.paint(bounds, state, cx)
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.element.paint(bounds, wet, state, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2280,17 +2218,14 @@ where
|
||||
self.element.request_layout(state, cx)
|
||||
}
|
||||
|
||||
fn post_layout(
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
self.element.post_layout(bounds, state, cx);
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
self.element.paint(bounds, state, cx)
|
||||
self.element.paint(bounds, wet, state, cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ impl Element for Img {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
|
||||
@@ -372,6 +372,7 @@ impl Element for List {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
wet: bool,
|
||||
_state: &mut Self::State,
|
||||
cx: &mut crate::ElementContext,
|
||||
) {
|
||||
@@ -520,7 +521,7 @@ impl Element for List {
|
||||
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& bounds.contains(&event.position)
|
||||
&& cx.is_top_layer(&event.position, cx.stacking_order())
|
||||
&& cx.was_top_layer(&event.position, cx.stacking_order())
|
||||
{
|
||||
list_state.0.borrow_mut().scroll(
|
||||
&scroll_top,
|
||||
|
||||
@@ -96,6 +96,7 @@ impl Element for Overlay {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
wet: bool,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
@@ -170,7 +171,7 @@ impl Element for Overlay {
|
||||
cx.with_absolute_element_offset(offset, |cx| {
|
||||
cx.break_content_mask(|cx| {
|
||||
for child in &mut self.children {
|
||||
child.paint(cx);
|
||||
child.paint(wet, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -42,6 +42,7 @@ impl Element for Svg {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) where
|
||||
|
||||
@@ -29,7 +29,13 @@ impl Element for &'static str {
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut TextState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
state.paint(bounds, self, cx)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +77,13 @@ impl Element for SharedString {
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut TextState,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
let text_str: &str = self.as_ref();
|
||||
state.paint(bounds, text_str, cx)
|
||||
}
|
||||
@@ -150,7 +162,13 @@ impl Element for StyledText {
|
||||
(layout_id, state)
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
state.paint(bounds, &self.text, cx)
|
||||
}
|
||||
}
|
||||
@@ -419,7 +437,13 @@ impl Element for InteractiveText {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if let Some(click_listener) = self.click_listener.take() {
|
||||
let mouse_position = cx.mouse_position();
|
||||
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
|
||||
@@ -427,7 +451,7 @@ impl Element for InteractiveText {
|
||||
.clickable_ranges
|
||||
.iter()
|
||||
.any(|range| range.contains(&ix))
|
||||
&& cx.is_top_layer(&mouse_position, cx.stacking_order())
|
||||
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
|
||||
{
|
||||
cx.set_cursor_style(crate::CursorStyle::PointingHand)
|
||||
}
|
||||
@@ -547,7 +571,7 @@ impl Element for InteractiveText {
|
||||
}
|
||||
}
|
||||
|
||||
self.text.paint(bounds, &mut state.text_state, cx)
|
||||
self.text.paint(bounds, wet, &mut state.text_state, cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ impl Element for UniformList {
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
wet: bool,
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
mod blade_atlas;
|
||||
mod blade_belt;
|
||||
mod blade_renderer;
|
||||
mod client;
|
||||
mod client_dispatcher;
|
||||
mod dispatcher;
|
||||
mod display;
|
||||
mod platform;
|
||||
mod text_system;
|
||||
mod x11;
|
||||
mod window;
|
||||
|
||||
pub(crate) use blade_atlas::*;
|
||||
pub(crate) use dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use platform::*;
|
||||
pub(crate) use text_system::*;
|
||||
pub(crate) use x11::display::*;
|
||||
pub(crate) use x11::*;
|
||||
pub(crate) use window::*;
|
||||
|
||||
use blade_belt::*;
|
||||
use blade_renderer::*;
|
||||
|
||||
@@ -237,19 +237,12 @@ pub struct BladeRenderer {
|
||||
}
|
||||
|
||||
impl BladeRenderer {
|
||||
fn make_surface_config(size: gpu::Extent) -> gpu::SurfaceConfig {
|
||||
gpu::SurfaceConfig {
|
||||
pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
|
||||
let surface_format = gpu.resize(gpu::SurfaceConfig {
|
||||
size,
|
||||
usage: gpu::TextureUsage::TARGET,
|
||||
frame_count: SURFACE_FRAME_COUNT,
|
||||
//Note: this matches the original logic of the Metal backend,
|
||||
// but ultimaterly we need to switch to `Linear`.
|
||||
color_space: gpu::ColorSpace::Srgb,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
|
||||
let surface_format = gpu.resize(Self::make_surface_config(size));
|
||||
});
|
||||
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
||||
name: "main",
|
||||
buffer_count: 2,
|
||||
@@ -298,7 +291,11 @@ impl BladeRenderer {
|
||||
|
||||
pub fn resize(&mut self, size: gpu::Extent) {
|
||||
self.wait_for_gpu();
|
||||
self.gpu.resize(Self::make_surface_config(size));
|
||||
self.gpu.resize(gpu::SurfaceConfig {
|
||||
size,
|
||||
usage: gpu::TextureUsage::TARGET,
|
||||
frame_count: SURFACE_FRAME_COUNT,
|
||||
});
|
||||
self.viewport_size = size;
|
||||
}
|
||||
|
||||
@@ -458,7 +455,7 @@ impl BladeRenderer {
|
||||
sprites,
|
||||
} => {
|
||||
let tex_info = self.atlas.get_texture_info(texture_id);
|
||||
let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
|
||||
let instance_buf = self.instance_belt.alloc_data(sprites, &self.gpu);
|
||||
let mut encoder = pass.with(&self.pipelines.mono_sprites);
|
||||
encoder.bind(
|
||||
0,
|
||||
@@ -476,7 +473,7 @@ impl BladeRenderer {
|
||||
sprites,
|
||||
} => {
|
||||
let tex_info = self.atlas.get_texture_info(texture_id);
|
||||
let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
|
||||
let instance_buf = self.instance_belt.alloc_data(sprites, &self.gpu);
|
||||
let mut encoder = pass.with(&self.pipelines.poly_sprites);
|
||||
encoder.bind(
|
||||
0,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions};
|
||||
|
||||
pub trait Client {
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow>;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub trait ClientDispatcher: Send + Sync {
|
||||
fn dispatch_on_main_thread(&self);
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
//todo!(linux): remove
|
||||
#![allow(unused_variables)]
|
||||
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use parking::{Parker, Unparker};
|
||||
@@ -15,9 +14,11 @@ use std::{
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use xcb::x;
|
||||
|
||||
pub(crate) struct LinuxDispatcher {
|
||||
client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_listener_window: x::Window,
|
||||
parker: Mutex<Parker>,
|
||||
timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
@@ -29,16 +30,38 @@ pub(crate) struct LinuxDispatcher {
|
||||
impl LinuxDispatcher {
|
||||
pub fn new(
|
||||
main_sender: flume::Sender<Runnable>,
|
||||
client_dispatcher: &Arc<dyn ClientDispatcher + Send + Sync>,
|
||||
xcb_connection: &Arc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
) -> Self {
|
||||
let x_listener_window = xcb_connection.generate_id();
|
||||
let screen = xcb_connection
|
||||
.get_setup()
|
||||
.roots()
|
||||
.nth(x_root_index as usize)
|
||||
.unwrap();
|
||||
xcb_connection.send_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: x_listener_window,
|
||||
parent: screen.root(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOnly,
|
||||
visual: screen.root_visual(),
|
||||
value_list: &[],
|
||||
});
|
||||
|
||||
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
|
||||
let background_thread = thread::spawn(move || {
|
||||
for runnable in background_receiver {
|
||||
let _ignore_panic = panic::catch_unwind(|| runnable.run());
|
||||
}
|
||||
});
|
||||
Self {
|
||||
client_dispatcher: Arc::clone(client_dispatcher),
|
||||
LinuxDispatcher {
|
||||
xcb_connection: Arc::clone(xcb_connection),
|
||||
x_listener_window,
|
||||
parker: Mutex::new(Parker::new()),
|
||||
timed_tasks: Mutex::new(Vec::new()),
|
||||
main_sender,
|
||||
@@ -49,6 +72,14 @@ impl LinuxDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LinuxDispatcher {
|
||||
fn drop(&mut self) {
|
||||
self.xcb_connection.send_request(&x::DestroyWindow {
|
||||
window: self.x_listener_window,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDispatcher for LinuxDispatcher {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
thread::current().id() == self.main_thread_id
|
||||
@@ -60,7 +91,18 @@ impl PlatformDispatcher for LinuxDispatcher {
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
self.main_sender.send(runnable).unwrap();
|
||||
self.client_dispatcher.dispatch_on_main_thread();
|
||||
// Send a message to the invisible window, forcing
|
||||
// the main loop to wake up and dispatch the runnable.
|
||||
self.xcb_connection.send_request(&x::SendEvent {
|
||||
propagate: false,
|
||||
destination: x::SendEventDest::Window(self.x_listener_window),
|
||||
event_mask: x::EventMask::NO_EVENT,
|
||||
event: &x::VisibilityNotifyEvent::new(
|
||||
self.x_listener_window,
|
||||
x::Visibility::Unobscured,
|
||||
),
|
||||
});
|
||||
self.xcb_connection.flush().unwrap();
|
||||
}
|
||||
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
||||
use anyhow::Result;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct X11Display {
|
||||
pub(crate) struct LinuxDisplay {
|
||||
x_screen_index: i32,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl X11Display {
|
||||
impl LinuxDisplay {
|
||||
pub(crate) fn new(xc: &xcb::Connection, x_screen_index: i32) -> Self {
|
||||
let screen = xc.get_setup().roots().nth(x_screen_index as usize).unwrap();
|
||||
Self {
|
||||
@@ -27,7 +26,7 @@ impl X11Display {
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDisplay for X11Display {
|
||||
impl PlatformDisplay for LinuxDisplay {
|
||||
fn id(&self) -> DisplayId {
|
||||
DisplayId(self.x_screen_index as u32)
|
||||
}
|
||||
@@ -1,36 +1,43 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow,
|
||||
LinuxWindowState, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions,
|
||||
};
|
||||
|
||||
use async_task::Runnable;
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::Mutex;
|
||||
use time::UtcOffset;
|
||||
use xcb::{x, Xid as _};
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
use crate::platform::{X11Client, X11ClientDispatcher, XcbAtoms};
|
||||
use crate::{
|
||||
Action, AnyWindowHandle, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DisplayId,
|
||||
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
|
||||
Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Point, Result,
|
||||
SemanticVersion, Size, Task, WindowAppearance, WindowOptions, X11Display, X11Window,
|
||||
X11WindowState,
|
||||
};
|
||||
xcb::atoms_struct! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct XcbAtoms {
|
||||
pub wm_protocols => b"WM_PROTOCOLS",
|
||||
pub wm_del_window => b"WM_DELETE_WINDOW",
|
||||
wm_state => b"_NET_WM_STATE",
|
||||
wm_state_maxv => b"_NET_WM_STATE_MAXIMIZED_VERT",
|
||||
wm_state_maxh => b"_NET_WM_STATE_MAXIMIZED_HORZ",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Callbacks {
|
||||
struct Callbacks {
|
||||
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
|
||||
become_active: Option<Box<dyn FnMut()>>,
|
||||
resign_active: Option<Box<dyn FnMut()>>,
|
||||
pub(crate) quit: Option<Box<dyn FnMut()>>,
|
||||
quit: Option<Box<dyn FnMut()>>,
|
||||
reopen: Option<Box<dyn FnMut()>>,
|
||||
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
|
||||
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
|
||||
@@ -38,22 +45,21 @@ pub(crate) struct Callbacks {
|
||||
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatformInner {
|
||||
pub(crate) background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
pub(crate) main_receiver: flume::Receiver<Runnable>,
|
||||
pub(crate) text_system: Arc<LinuxTextSystem>,
|
||||
pub(crate) callbacks: Mutex<Callbacks>,
|
||||
pub(crate) state: Mutex<LinuxPlatformState>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatform {
|
||||
client: Arc<dyn Client>,
|
||||
inner: Arc<LinuxPlatformInner>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
atoms: XcbAtoms,
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
text_system: Arc<LinuxTextSystem>,
|
||||
callbacks: Mutex<Callbacks>,
|
||||
state: Mutex<LinuxPlatformState>,
|
||||
}
|
||||
|
||||
pub(crate) struct LinuxPlatformState {
|
||||
pub(crate) quit_requested: bool,
|
||||
quit_requested: bool,
|
||||
windows: HashMap<x::Window, Rc<LinuxWindowState>>,
|
||||
}
|
||||
|
||||
impl Default for LinuxPlatform {
|
||||
@@ -69,12 +75,16 @@ impl LinuxPlatform {
|
||||
|
||||
let xcb_connection = Arc::new(xcb_connection);
|
||||
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
|
||||
let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
|
||||
Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index));
|
||||
let dispatcher = LinuxDispatcher::new(main_sender, &client_dispatcher);
|
||||
let dispatcher = Arc::new(dispatcher);
|
||||
let dispatcher = Arc::new(LinuxDispatcher::new(
|
||||
main_sender,
|
||||
&xcb_connection,
|
||||
x_root_index,
|
||||
));
|
||||
|
||||
let inner = LinuxPlatformInner {
|
||||
Self {
|
||||
xcb_connection,
|
||||
x_root_index,
|
||||
atoms,
|
||||
background_executor: BackgroundExecutor::new(dispatcher.clone()),
|
||||
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
|
||||
main_receiver,
|
||||
@@ -82,39 +92,83 @@ impl LinuxPlatform {
|
||||
callbacks: Mutex::new(Callbacks::default()),
|
||||
state: Mutex::new(LinuxPlatformState {
|
||||
quit_requested: false,
|
||||
windows: HashMap::default(),
|
||||
}),
|
||||
};
|
||||
let inner = Arc::new(inner);
|
||||
|
||||
let x11client = X11Client::new(Arc::clone(&inner), xcb_connection, x_root_index, atoms);
|
||||
let x11client = Arc::new(x11client);
|
||||
|
||||
Self {
|
||||
client: x11client,
|
||||
inner: Arc::clone(&inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Platform for LinuxPlatform {
|
||||
fn background_executor(&self) -> BackgroundExecutor {
|
||||
self.inner.background_executor.clone()
|
||||
self.background_executor.clone()
|
||||
}
|
||||
|
||||
fn foreground_executor(&self) -> ForegroundExecutor {
|
||||
self.inner.foreground_executor.clone()
|
||||
self.foreground_executor.clone()
|
||||
}
|
||||
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
|
||||
self.inner.text_system.clone()
|
||||
self.text_system.clone()
|
||||
}
|
||||
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
self.client.run(on_finish_launching)
|
||||
on_finish_launching();
|
||||
//Note: here and below, don't keep the lock() open when calling
|
||||
// into window functions as they may invoke callbacks that need
|
||||
// to immediately access the platform (self).
|
||||
while !self.state.lock().quit_requested {
|
||||
let event = self.xcb_connection.wait_for_event().unwrap();
|
||||
match event {
|
||||
xcb::Event::X(x::Event::ClientMessage(ev)) => {
|
||||
if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
|
||||
if atom == self.atoms.wm_del_window.resource_id() {
|
||||
// window "x" button clicked by user, we gracefully exit
|
||||
let window = self.state.lock().windows.remove(&ev.window()).unwrap();
|
||||
window.destroy();
|
||||
let mut state = self.state.lock();
|
||||
state.quit_requested |= state.windows.is_empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::Expose(ev)) => {
|
||||
let window = {
|
||||
let state = self.state.lock();
|
||||
Rc::clone(&state.windows[&ev.window()])
|
||||
};
|
||||
window.expose();
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
|
||||
let bounds = Bounds {
|
||||
origin: Point {
|
||||
x: ev.x().into(),
|
||||
y: ev.y().into(),
|
||||
},
|
||||
size: Size {
|
||||
width: ev.width().into(),
|
||||
height: ev.height().into(),
|
||||
},
|
||||
};
|
||||
let window = {
|
||||
let state = self.state.lock();
|
||||
Rc::clone(&state.windows[&ev.window()])
|
||||
};
|
||||
window.configure(bounds)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Ok(runnable) = self.main_receiver.try_recv() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut fun) = self.callbacks.lock().quit {
|
||||
fun();
|
||||
}
|
||||
}
|
||||
|
||||
fn quit(&self) {
|
||||
self.inner.state.lock().quit_requested = true;
|
||||
self.state.lock().quit_requested = true;
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
@@ -133,11 +187,22 @@ impl Platform for LinuxPlatform {
|
||||
fn unhide_other_apps(&self) {}
|
||||
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
self.client.displays()
|
||||
let setup = self.xcb_connection.get_setup();
|
||||
setup
|
||||
.roots()
|
||||
.enumerate()
|
||||
.map(|(root_id, _)| {
|
||||
Rc::new(LinuxDisplay::new(&self.xcb_connection, root_id as i32))
|
||||
as Rc<dyn PlatformDisplay>
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
self.client.display(id)
|
||||
Some(Rc::new(LinuxDisplay::new(
|
||||
&self.xcb_connection,
|
||||
id.0 as i32,
|
||||
)))
|
||||
}
|
||||
|
||||
//todo!(linux)
|
||||
@@ -150,7 +215,21 @@ impl Platform for LinuxPlatform {
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
self.client.open_window(handle, options)
|
||||
let x_window = self.xcb_connection.generate_id();
|
||||
|
||||
let window_ptr = Rc::new(LinuxWindowState::new(
|
||||
options,
|
||||
&self.xcb_connection,
|
||||
self.x_root_index,
|
||||
x_window,
|
||||
&self.atoms,
|
||||
));
|
||||
|
||||
self.state
|
||||
.lock()
|
||||
.windows
|
||||
.insert(x_window, Rc::clone(&window_ptr));
|
||||
Box::new(LinuxWindow(window_ptr))
|
||||
}
|
||||
|
||||
fn open_url(&self, url: &str) {
|
||||
@@ -158,7 +237,7 @@ impl Platform for LinuxPlatform {
|
||||
}
|
||||
|
||||
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
|
||||
self.inner.callbacks.lock().open_urls = Some(callback);
|
||||
self.callbacks.lock().open_urls = Some(callback);
|
||||
}
|
||||
|
||||
fn prompt_for_paths(
|
||||
@@ -177,35 +256,35 @@ impl Platform for LinuxPlatform {
|
||||
}
|
||||
|
||||
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().become_active = Some(callback);
|
||||
self.callbacks.lock().become_active = Some(callback);
|
||||
}
|
||||
|
||||
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().resign_active = Some(callback);
|
||||
self.callbacks.lock().resign_active = Some(callback);
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().quit = Some(callback);
|
||||
self.callbacks.lock().quit = Some(callback);
|
||||
}
|
||||
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().reopen = Some(callback);
|
||||
self.callbacks.lock().reopen = Some(callback);
|
||||
}
|
||||
|
||||
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
|
||||
self.inner.callbacks.lock().event = Some(callback);
|
||||
self.callbacks.lock().event = Some(callback);
|
||||
}
|
||||
|
||||
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
|
||||
self.inner.callbacks.lock().app_menu_action = Some(callback);
|
||||
self.callbacks.lock().app_menu_action = Some(callback);
|
||||
}
|
||||
|
||||
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
|
||||
self.inner.callbacks.lock().will_open_app_menu = Some(callback);
|
||||
self.callbacks.lock().will_open_app_menu = Some(callback);
|
||||
}
|
||||
|
||||
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
|
||||
self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
|
||||
self.callbacks.lock().validate_app_menu_command = Some(callback);
|
||||
}
|
||||
|
||||
fn os_name(&self) -> &'static str {
|
||||
@@ -282,6 +361,8 @@ impl Platform for LinuxPlatform {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ClipboardItem;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn build_platform() -> LinuxPlatform {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
//todo!(linux): remove
|
||||
#![allow(unused)]
|
||||
|
||||
use super::BladeRenderer;
|
||||
use crate::{
|
||||
Bounds, GlobalPixels, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler,
|
||||
PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, XcbAtoms,
|
||||
};
|
||||
use blade_graphics as gpu;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle as rwh;
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
mem,
|
||||
@@ -9,21 +17,11 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::{self, Arc},
|
||||
};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle as rwh;
|
||||
use xcb::{
|
||||
x::{self, StackMode},
|
||||
Xid as _,
|
||||
};
|
||||
|
||||
use crate::platform::linux::blade_renderer::BladeRenderer;
|
||||
use crate::{
|
||||
Bounds, GlobalPixels, Pixels, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Callbacks {
|
||||
request_frame: Option<Box<dyn FnMut()>>,
|
||||
@@ -37,17 +35,6 @@ struct Callbacks {
|
||||
appearance_changed: Option<Box<dyn FnMut()>>,
|
||||
}
|
||||
|
||||
xcb::atoms_struct! {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct XcbAtoms {
|
||||
pub wm_protocols => b"WM_PROTOCOLS",
|
||||
pub wm_del_window => b"WM_DELETE_WINDOW",
|
||||
wm_state => b"_NET_WM_STATE",
|
||||
wm_state_maxv => b"_NET_WM_STATE_MAXIMIZED_VERT",
|
||||
wm_state_maxh => b"_NET_WM_STATE_MAXIMIZED_HORZ",
|
||||
}
|
||||
}
|
||||
|
||||
struct LinuxWindowInner {
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
@@ -83,7 +70,7 @@ struct RawWindow {
|
||||
visual_id: u32,
|
||||
}
|
||||
|
||||
pub(crate) struct X11WindowState {
|
||||
pub(crate) struct LinuxWindowState {
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
raw: RawWindow,
|
||||
@@ -93,7 +80,7 @@ pub(crate) struct X11WindowState {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct X11Window(pub(crate) Rc<X11WindowState>);
|
||||
pub(crate) struct LinuxWindow(pub(crate) Rc<LinuxWindowState>);
|
||||
|
||||
//todo!(linux): Remove other RawWindowHandle implementation
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
@@ -113,7 +100,7 @@ unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for X11Window {
|
||||
impl rwh::HasWindowHandle for LinuxWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let non_zero = NonZeroU32::new(self.0.raw.window_id).unwrap();
|
||||
@@ -122,7 +109,7 @@ impl rwh::HasWindowHandle for X11Window {
|
||||
})
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for X11Window {
|
||||
impl rwh::HasDisplayHandle for LinuxWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let non_zero = NonNull::new(self.0.raw.connection).unwrap();
|
||||
@@ -132,7 +119,7 @@ impl rwh::HasDisplayHandle for X11Window {
|
||||
}
|
||||
}
|
||||
|
||||
impl X11WindowState {
|
||||
impl LinuxWindowState {
|
||||
pub fn new(
|
||||
options: WindowOptions,
|
||||
xcb_connection: &Arc<xcb::Connection>,
|
||||
@@ -232,7 +219,7 @@ impl X11WindowState {
|
||||
|
||||
Self {
|
||||
xcb_connection: Arc::clone(xcb_connection),
|
||||
display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
|
||||
display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)),
|
||||
raw,
|
||||
x_window,
|
||||
callbacks: Mutex::new(Callbacks::default()),
|
||||
@@ -293,7 +280,7 @@ impl X11WindowState {
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindow for X11Window {
|
||||
impl PlatformWindow for LinuxWindow {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32)))
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
mod client;
|
||||
mod client_dispatcher;
|
||||
pub mod display;
|
||||
mod window;
|
||||
|
||||
pub(crate) use client::*;
|
||||
pub(crate) use client_dispatcher::*;
|
||||
pub(crate) use display::*;
|
||||
pub(crate) use window::*;
|
||||
@@ -1,139 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use xcb::{x, Xid};
|
||||
|
||||
use collections::HashMap;
|
||||
|
||||
use crate::platform::linux::client::Client;
|
||||
use crate::platform::{
|
||||
LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
|
||||
};
|
||||
use crate::{AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, Point, Size, WindowOptions};
|
||||
|
||||
pub(crate) struct X11ClientState {
|
||||
pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
|
||||
}
|
||||
|
||||
pub(crate) struct X11Client {
|
||||
platform_inner: Arc<LinuxPlatformInner>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
atoms: XcbAtoms,
|
||||
state: Mutex<X11ClientState>,
|
||||
}
|
||||
|
||||
impl X11Client {
|
||||
pub(crate) fn new(
|
||||
inner: Arc<LinuxPlatformInner>,
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_root_index: i32,
|
||||
atoms: XcbAtoms,
|
||||
) -> Self {
|
||||
Self {
|
||||
platform_inner: inner,
|
||||
xcb_connection,
|
||||
x_root_index,
|
||||
atoms,
|
||||
state: Mutex::new(X11ClientState {
|
||||
windows: HashMap::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client for X11Client {
|
||||
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
|
||||
on_finish_launching();
|
||||
//Note: here and below, don't keep the lock() open when calling
|
||||
// into window functions as they may invoke callbacks that need
|
||||
// to immediately access the platform (self).
|
||||
while !self.platform_inner.state.lock().quit_requested {
|
||||
let event = self.xcb_connection.wait_for_event().unwrap();
|
||||
match event {
|
||||
xcb::Event::X(x::Event::ClientMessage(ev)) => {
|
||||
if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
|
||||
if atom == self.atoms.wm_del_window.resource_id() {
|
||||
// window "x" button clicked by user, we gracefully exit
|
||||
let window = self.state.lock().windows.remove(&ev.window()).unwrap();
|
||||
window.destroy();
|
||||
let mut state = self.state.lock();
|
||||
self.platform_inner.state.lock().quit_requested |=
|
||||
state.windows.is_empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::Expose(ev)) => {
|
||||
let window = {
|
||||
let state = self.state.lock();
|
||||
Rc::clone(&state.windows[&ev.window()])
|
||||
};
|
||||
window.expose();
|
||||
}
|
||||
xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
|
||||
let bounds = Bounds {
|
||||
origin: Point {
|
||||
x: ev.x().into(),
|
||||
y: ev.y().into(),
|
||||
},
|
||||
size: Size {
|
||||
width: ev.width().into(),
|
||||
height: ev.height().into(),
|
||||
},
|
||||
};
|
||||
let window = {
|
||||
let state = self.state.lock();
|
||||
Rc::clone(&state.windows[&ev.window()])
|
||||
};
|
||||
window.configure(bounds)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
|
||||
fun();
|
||||
}
|
||||
}
|
||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
|
||||
let setup = self.xcb_connection.get_setup();
|
||||
setup
|
||||
.roots()
|
||||
.enumerate()
|
||||
.map(|(root_id, _)| {
|
||||
Rc::new(X11Display::new(&self.xcb_connection, root_id as i32))
|
||||
as Rc<dyn PlatformDisplay>
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
|
||||
Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)))
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn PlatformWindow> {
|
||||
let x_window = self.xcb_connection.generate_id();
|
||||
|
||||
let window_ptr = Rc::new(X11WindowState::new(
|
||||
options,
|
||||
&self.xcb_connection,
|
||||
self.x_root_index,
|
||||
x_window,
|
||||
&self.atoms,
|
||||
));
|
||||
|
||||
self.state
|
||||
.lock()
|
||||
.windows
|
||||
.insert(x_window, Rc::clone(&window_ptr));
|
||||
Box::new(X11Window(window_ptr))
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use xcb::x;
|
||||
|
||||
use crate::platform::linux::client_dispatcher::ClientDispatcher;
|
||||
|
||||
pub(crate) struct X11ClientDispatcher {
|
||||
xcb_connection: Arc<xcb::Connection>,
|
||||
x_listener_window: x::Window,
|
||||
}
|
||||
|
||||
impl X11ClientDispatcher {
|
||||
pub fn new(xcb_connection: &Arc<xcb::Connection>, x_root_index: i32) -> Self {
|
||||
let x_listener_window = xcb_connection.generate_id();
|
||||
let screen = xcb_connection
|
||||
.get_setup()
|
||||
.roots()
|
||||
.nth(x_root_index as usize)
|
||||
.unwrap();
|
||||
xcb_connection.send_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: x_listener_window,
|
||||
parent: screen.root(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
border_width: 0,
|
||||
class: x::WindowClass::InputOnly,
|
||||
visual: screen.root_visual(),
|
||||
value_list: &[],
|
||||
});
|
||||
|
||||
Self {
|
||||
xcb_connection: Arc::clone(xcb_connection),
|
||||
x_listener_window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11ClientDispatcher {
|
||||
fn drop(&mut self) {
|
||||
self.xcb_connection.send_request(&x::DestroyWindow {
|
||||
window: self.x_listener_window,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDispatcher for X11ClientDispatcher {
|
||||
fn dispatch_on_main_thread(&self) {
|
||||
// Send a message to the invisible window, forcing
|
||||
// the main loop to wake up and dispatch the runnable.
|
||||
self.xcb_connection.send_request(&x::SendEvent {
|
||||
propagate: false,
|
||||
destination: x::SendEventDest::Window(self.x_listener_window),
|
||||
event_mask: x::EventMask::NO_EVENT,
|
||||
event: &x::VisibilityNotifyEvent::new(
|
||||
self.x_listener_window,
|
||||
x::Visibility::Unobscured,
|
||||
),
|
||||
});
|
||||
self.xcb_connection.flush().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -223,7 +223,6 @@ impl MacTextSystemState {
|
||||
.or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
|
||||
for font in family.fonts() {
|
||||
let mut font = font.load()?;
|
||||
|
||||
open_type::apply_features(&mut font, features);
|
||||
let Some(_) = font.glyph_for_char('m') else {
|
||||
continue;
|
||||
|
||||
@@ -348,12 +348,6 @@ struct MacWindowState {
|
||||
impl MacWindowState {
|
||||
fn move_traffic_light(&self) {
|
||||
if let Some(traffic_light_position) = self.traffic_light_position {
|
||||
if self.is_fullscreen() {
|
||||
// Moving traffic lights while fullscreen doesn't work,
|
||||
// see https://github.com/zed-industries/zed/issues/4712
|
||||
return;
|
||||
}
|
||||
|
||||
let titlebar_height = self.titlebar_height();
|
||||
|
||||
unsafe {
|
||||
|
||||
@@ -104,8 +104,16 @@ impl<V: Render> Element for View<V> {
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.with_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
|
||||
fn paint(
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
element: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
element.as_mut().unwrap().paint(wet, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +212,6 @@ impl<V> Eq for WeakView<V> {}
|
||||
pub struct AnyView {
|
||||
model: AnyModel,
|
||||
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
post_layout: fn(&mut AnyElement, &mut ElementContext),
|
||||
cache: bool,
|
||||
}
|
||||
|
||||
@@ -221,8 +228,7 @@ impl AnyView {
|
||||
pub fn downgrade(&self) -> AnyWeakView {
|
||||
AnyWeakView {
|
||||
model: self.model.downgrade(),
|
||||
request_layout: self.request_layout,
|
||||
post_layout: self.post_layout,
|
||||
layout: self.request_layout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +240,6 @@ impl AnyView {
|
||||
Err(model) => Err(Self {
|
||||
model,
|
||||
request_layout: self.request_layout,
|
||||
post_layout: self.post_layout,
|
||||
cache: self.cache,
|
||||
}),
|
||||
}
|
||||
@@ -250,33 +255,21 @@ impl AnyView {
|
||||
self.model.entity_id()
|
||||
}
|
||||
|
||||
pub(crate) fn layout_and_post_layout(
|
||||
/// Draws this view at the given origin, with its sizing based on the `available_space`.
|
||||
pub(crate) fn draw(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
) -> AnyElement {
|
||||
cx.with_view(self.entity_id(), |cx| {
|
||||
) {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
cx.with_absolute_element_offset(origin, |cx| {
|
||||
let (layout_id, mut rendered_element) = (self.request_layout)(self, cx);
|
||||
cx.compute_layout(layout_id, available_space);
|
||||
(self.post_layout)(&mut rendered_element, cx);
|
||||
rendered_element
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn paint(
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
mut rendered_element: AnyElement,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
cx.with_view(self.entity_id(), |cx| {
|
||||
cx.with_absolute_element_offset(origin, |cx| {
|
||||
rendered_element.paint(cx);
|
||||
rendered_element.paint(false, cx);
|
||||
rendered_element.paint(true, cx);
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +278,6 @@ impl<V: Render> From<View<V>> for AnyView {
|
||||
AnyView {
|
||||
model: value.model.into_any(),
|
||||
request_layout: any_view::request_layout::<V>,
|
||||
post_layout: any_view::post_layout,
|
||||
cache: false,
|
||||
}
|
||||
}
|
||||
@@ -318,21 +310,17 @@ impl Element for AnyView {
|
||||
(layout_id, state)
|
||||
})
|
||||
}
|
||||
|
||||
fn post_layout(
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
wet: bool,
|
||||
state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
// todo!("move parts of caching")
|
||||
state.element.as_mut().unwrap().post_layout(cx);
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
|
||||
cx.with_view(self.entity_id(), |cx| {
|
||||
cx.paint_view(self.entity_id(), |cx| {
|
||||
if !self.cache {
|
||||
state.element.take().unwrap().paint(cx);
|
||||
state.element.as_mut().unwrap().paint(wet, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,15 +337,20 @@ impl Element for AnyView {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut element) = state.element.take() {
|
||||
if let Some(mut element) = state.element.as_mut() {
|
||||
element.paint(cx);
|
||||
} else {
|
||||
let mut element = (self.request_layout)(self, cx).1;
|
||||
element.draw(bounds.origin, bounds.size.into(), cx);
|
||||
}
|
||||
|
||||
state.next_stacking_order_id =
|
||||
cx.window.next_stacking_order_ids.last().copied().unwrap();
|
||||
state.next_stacking_order_id = cx
|
||||
.window
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap();
|
||||
state.cache_key = Some(ViewCacheKey {
|
||||
bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
@@ -395,8 +388,7 @@ impl IntoElement for AnyView {
|
||||
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||
pub struct AnyWeakView {
|
||||
model: AnyWeakModel,
|
||||
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
post_layout: fn(&mut AnyElement, &mut ElementContext),
|
||||
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
|
||||
}
|
||||
|
||||
impl AnyWeakView {
|
||||
@@ -405,8 +397,7 @@ impl AnyWeakView {
|
||||
let model = self.model.upgrade()?;
|
||||
Some(AnyView {
|
||||
model,
|
||||
request_layout: self.request_layout,
|
||||
post_layout: self.post_layout,
|
||||
request_layout: self.layout,
|
||||
cache: false,
|
||||
})
|
||||
}
|
||||
@@ -416,8 +407,7 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
|
||||
fn from(view: WeakView<V>) -> Self {
|
||||
Self {
|
||||
model: view.model.into(),
|
||||
request_layout: any_view::request_layout::<V>,
|
||||
post_layout: any_view::post_layout,
|
||||
layout: any_view::request_layout::<V>,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -448,8 +438,4 @@ mod any_view {
|
||||
let layout_id = element.request_layout(cx);
|
||||
(layout_id, element)
|
||||
}
|
||||
|
||||
pub(crate) fn post_layout(element: &mut AnyElement, cx: &mut ElementContext) {
|
||||
element.post_layout(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,9 +257,6 @@ pub struct Window {
|
||||
pub(crate) element_id_stack: GlobalElementId,
|
||||
pub(crate) rendered_frame: Frame,
|
||||
pub(crate) next_frame: Frame,
|
||||
pub(crate) z_index_stack: StackingOrder,
|
||||
pub(crate) next_stacking_order_ids: Vec<u16>,
|
||||
pub(crate) next_root_z_index: u16,
|
||||
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
|
||||
pub(crate) dirty_views: FxHashSet<EntityId>,
|
||||
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
@@ -444,9 +441,6 @@ impl Window {
|
||||
element_id_stack: GlobalElementId::default(),
|
||||
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
|
||||
z_index_stack: StackingOrder::default(),
|
||||
next_stacking_order_ids: vec![0],
|
||||
next_root_z_index: 0,
|
||||
next_frame_callbacks,
|
||||
dirty_views: FxHashSet::default(),
|
||||
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
|
||||
@@ -472,7 +466,6 @@ impl Window {
|
||||
graphics_profiler_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_focus_listener(
|
||||
&mut self,
|
||||
value: AnyWindowFocusListener,
|
||||
@@ -855,49 +848,6 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.modifiers
|
||||
}
|
||||
|
||||
/// Returns true if there is no opaque layer containing the given point
|
||||
/// on top of the given level. Layers who are extensions of the queried layer
|
||||
/// are not considered to be on top of queried layer.
|
||||
pub fn is_top_layer_debug(&self, point: &Point<Pixels>, layer: &StackingOrder) -> bool {
|
||||
dbg!(point, layer);
|
||||
dbg!(&self.window.next_frame.depth_map);
|
||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
||||
|
||||
for (opaque_layer, _, bounds) in self.window.next_frame.depth_map.iter() {
|
||||
dbg!(opaque_layer);
|
||||
if layer >= opaque_layer {
|
||||
// The queried layer is either above or is the same as the this opaque layer.
|
||||
// Anything after this point is guaranteed to be below the queried layer.
|
||||
println!("is above or equal");
|
||||
return true;
|
||||
}
|
||||
|
||||
if !bounds.contains(point) {
|
||||
// This opaque layer is above the queried layer but it doesn't contain
|
||||
// the given position, so we can ignore it even if it's above.
|
||||
println!("does not contain point");
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
// let is_on_same_layer = opaque_layer
|
||||
// .iter()
|
||||
// .zip(layer.iter())
|
||||
// .all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
// if !is_on_same_layer {
|
||||
// return false;
|
||||
// }
|
||||
println!("returning false");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if there is no opaque layer containing the given point
|
||||
/// on top of the given level. Layers who are extensions of the queried layer
|
||||
/// are not considered to be on top of queried layer.
|
||||
@@ -921,65 +871,27 @@ impl<'a> WindowContext<'a> {
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
// let is_on_same_layer = opaque_layer
|
||||
// .iter()
|
||||
// .zip(layer.iter())
|
||||
// .all(|(a, b)| a.z_index == b.z_index);
|
||||
let is_on_same_layer = opaque_layer
|
||||
.iter()
|
||||
.zip(layer.iter())
|
||||
.all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
// if !is_on_same_layer {
|
||||
// return false;
|
||||
// }
|
||||
return false;
|
||||
if !is_on_same_layer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns true if there is no opaque layer containing the given point
|
||||
/// on top of the given level. Layers who are extensions of the queried layer
|
||||
/// are not considered to be on top of queried layer.
|
||||
pub fn is_top_layer(&self, point: &Point<Pixels>, layer: &StackingOrder) -> bool {
|
||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
||||
|
||||
for (opaque_layer, _, bounds) in self.window.next_frame.depth_map.iter() {
|
||||
if layer >= opaque_layer {
|
||||
// The queried layer is either above or is the same as the this opaque layer.
|
||||
// Anything after this point is guaranteed to be below the queried layer.
|
||||
return true;
|
||||
}
|
||||
|
||||
if !bounds.contains(point) {
|
||||
// This opaque layer is above the queried layer but it doesn't contain
|
||||
// the given position, so we can ignore it even if it's above.
|
||||
continue;
|
||||
}
|
||||
|
||||
// At this point, we've established that this opaque layer is on top of the queried layer
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
// let is_on_same_layer = opaque_layer
|
||||
// .iter()
|
||||
// .zip(layer.iter())
|
||||
// .all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
// if !is_on_same_layer {
|
||||
// return false;
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn is_top_layer_under_active_drag(
|
||||
pub(crate) fn was_top_layer_under_active_drag(
|
||||
&self,
|
||||
point: &Point<Pixels>,
|
||||
layer: &StackingOrder,
|
||||
) -> bool {
|
||||
// Precondition: the depth map is ordered from topmost to bottomost.
|
||||
|
||||
for (opaque_layer, _, bounds) in self.window.next_frame.depth_map.iter() {
|
||||
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
|
||||
if layer >= opaque_layer {
|
||||
// The queried layer is either above or is the same as the this opaque layer.
|
||||
// Anything after this point is guaranteed to be below the queried layer.
|
||||
@@ -1007,15 +919,14 @@ impl<'a> WindowContext<'a> {
|
||||
// and contains the position:
|
||||
// If neither the opaque layer or the queried layer is an extension of the other then
|
||||
// we know they are on different stacking orders, and return false.
|
||||
// let is_on_same_layer = opaque_layer
|
||||
// .iter()
|
||||
// .zip(layer.iter())
|
||||
// .all(|(a, b)| a.z_index == b.z_index);
|
||||
let is_on_same_layer = opaque_layer
|
||||
.iter()
|
||||
.zip(layer.iter())
|
||||
.all(|(a, b)| a.z_index == b.z_index);
|
||||
|
||||
// if !is_on_same_layer {
|
||||
// return false;
|
||||
// }
|
||||
return false;
|
||||
if !is_on_same_layer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
@@ -1023,7 +934,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Called during painting to get the current stacking order.
|
||||
pub fn stacking_order(&self) -> &StackingOrder {
|
||||
&self.window.z_index_stack
|
||||
&self.window.next_frame.z_index_stack
|
||||
}
|
||||
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
@@ -1039,48 +950,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
let root_view = self.window.root_view.take().unwrap();
|
||||
let root_element = self.with_element_context(|cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
let available_space = cx.window.viewport_size.map(Into::into);
|
||||
root_view.layout_and_post_layout(Point::default(), available_space, cx)
|
||||
})
|
||||
});
|
||||
|
||||
let active_drag = self.app.active_drag.take();
|
||||
let tooltip_request = self.window.next_frame.tooltip_request.take();
|
||||
|
||||
let drag_or_tooltip_element = if let Some(active_drag) = &active_drag {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
|
||||
let offset = cx.mouse_position() - active_drag.cursor_offset;
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
Some(
|
||||
active_drag
|
||||
.view
|
||||
.layout_and_post_layout(offset, available_space, cx),
|
||||
)
|
||||
})
|
||||
})
|
||||
} else if let Some(tooltip_request) = &tooltip_request {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
Some(tooltip_request.tooltip.view.layout_and_post_layout(
|
||||
tooltip_request.tooltip.cursor_offset,
|
||||
available_space,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
assert_eq!(self.window.z_index_stack.len(), 0);
|
||||
self.window.next_stacking_order_ids = vec![0];
|
||||
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
|
||||
@@ -1098,35 +967,36 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
root_view.paint(Point::default(), root_element, cx);
|
||||
let available_space = cx.window.viewport_size.map(Into::into);
|
||||
root_view.draw(Point::default(), available_space, cx);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(active_drag) = &active_drag {
|
||||
if let Some(active_drag) = self.app.active_drag.take() {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
|
||||
let offset = cx.mouse_position() - active_drag.cursor_offset;
|
||||
active_drag
|
||||
.view
|
||||
.paint(offset, drag_or_tooltip_element.unwrap(), cx);
|
||||
});
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
active_drag.view.draw(offset, available_space, cx);
|
||||
})
|
||||
});
|
||||
} else if let Some(tooltip_request) = &tooltip_request {
|
||||
self.active_drag = Some(active_drag);
|
||||
} else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() {
|
||||
self.with_element_context(|cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
tooltip_request.tooltip.view.paint(
|
||||
let available_space =
|
||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
||||
tooltip_request.tooltip.view.draw(
|
||||
tooltip_request.tooltip.cursor_offset,
|
||||
drag_or_tooltip_element.unwrap(),
|
||||
available_space,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
self.window.next_frame.tooltip_request = Some(tooltip_request);
|
||||
}
|
||||
|
||||
self.active_drag = active_drag;
|
||||
self.window.next_frame.tooltip_request = tooltip_request;
|
||||
|
||||
self.window.dirty_views.clear();
|
||||
|
||||
self.window
|
||||
@@ -1176,9 +1046,6 @@ impl<'a> WindowContext<'a> {
|
||||
let previous_window_active = self.window.rendered_frame.window_active;
|
||||
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
|
||||
self.window.next_frame.clear();
|
||||
self.window.next_stacking_order_ids = vec![0];
|
||||
self.window.next_root_z_index = 0;
|
||||
self.window.z_index_stack.clear();
|
||||
let current_focus_path = self.window.rendered_frame.focus_path();
|
||||
let current_window_active = self.window.rendered_frame.window_active;
|
||||
|
||||
@@ -1211,8 +1078,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
self.window.refreshing = false;
|
||||
self.window.drawing = false;
|
||||
|
||||
println!("\n\n\n\n");
|
||||
}
|
||||
|
||||
fn present(&self) {
|
||||
|
||||
@@ -59,6 +59,9 @@ pub(crate) struct Frame {
|
||||
pub(crate) dispatch_tree: DispatchTree,
|
||||
pub(crate) scene: Scene,
|
||||
pub(crate) depth_map: Vec<(StackingOrder, EntityId, Bounds<Pixels>)>,
|
||||
pub(crate) z_index_stack: StackingOrder,
|
||||
pub(crate) next_stacking_order_ids: Vec<u16>,
|
||||
pub(crate) next_root_z_index: u16,
|
||||
pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
|
||||
pub(crate) requested_input_handler: Option<RequestedInputHandler>,
|
||||
@@ -82,6 +85,9 @@ impl Frame {
|
||||
dispatch_tree,
|
||||
scene: Scene::default(),
|
||||
depth_map: Vec::new(),
|
||||
z_index_stack: StackingOrder::default(),
|
||||
next_stacking_order_ids: vec![0],
|
||||
next_root_z_index: 0,
|
||||
content_mask_stack: Vec::new(),
|
||||
element_offset_stack: Vec::new(),
|
||||
requested_input_handler: None,
|
||||
@@ -101,6 +107,8 @@ impl Frame {
|
||||
self.mouse_listeners.values_mut().for_each(Vec::clear);
|
||||
self.dispatch_tree.clear();
|
||||
self.depth_map.clear();
|
||||
self.next_stacking_order_ids = vec![0];
|
||||
self.next_root_z_index = 0;
|
||||
self.reused_views.clear();
|
||||
self.scene.clear();
|
||||
self.requested_input_handler.take();
|
||||
@@ -310,7 +318,6 @@ impl<'a> ElementContext<'a> {
|
||||
.next_frame
|
||||
.dispatch_tree
|
||||
.reuse_view(view_id, &mut self.cx.window.rendered_frame.dispatch_tree);
|
||||
|
||||
for view_id in grafted_view_ids {
|
||||
assert!(self.window.next_frame.reused_views.insert(view_id));
|
||||
|
||||
@@ -346,9 +353,21 @@ impl<'a> ElementContext<'a> {
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
next_stacking_order_id >= self.window.next_stacking_order_ids.last().copied().unwrap()
|
||||
next_stacking_order_id
|
||||
>= self
|
||||
.window
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap()
|
||||
);
|
||||
*self.window.next_stacking_order_ids.last_mut().unwrap() = next_stacking_order_id;
|
||||
*self
|
||||
.window
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last_mut()
|
||||
.unwrap() = next_stacking_order_id;
|
||||
}
|
||||
|
||||
/// Push a text style onto the stack, and call a function with that style active.
|
||||
@@ -421,7 +440,6 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
/// Invoke the given function with the content mask reset to that
|
||||
/// of the window.
|
||||
/// TODO: Don't call this during paint, that is very bad and broken
|
||||
pub fn break_content_mask<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let mask = ContentMask {
|
||||
bounds: Bounds {
|
||||
@@ -430,9 +448,10 @@ impl<'a> ElementContext<'a> {
|
||||
},
|
||||
};
|
||||
|
||||
let new_root_z_index = post_inc(&mut self.window_mut().next_root_z_index);
|
||||
let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index);
|
||||
let new_stacking_order_id = post_inc(
|
||||
self.window_mut()
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last_mut()
|
||||
.unwrap(),
|
||||
@@ -442,13 +461,13 @@ impl<'a> ElementContext<'a> {
|
||||
id: new_stacking_order_id,
|
||||
};
|
||||
|
||||
let old_stacking_order = mem::take(&mut self.window_mut().z_index_stack);
|
||||
let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack);
|
||||
|
||||
self.window_mut().z_index_stack.push(new_context);
|
||||
self.window_mut().next_frame.z_index_stack.push(new_context);
|
||||
self.window_mut().next_frame.content_mask_stack.push(mask);
|
||||
let result = f(self);
|
||||
self.window_mut().next_frame.content_mask_stack.pop();
|
||||
self.window_mut().z_index_stack = old_stacking_order;
|
||||
self.window_mut().next_frame.z_index_stack = old_stacking_order;
|
||||
|
||||
result
|
||||
}
|
||||
@@ -458,29 +477,22 @@ impl<'a> ElementContext<'a> {
|
||||
pub fn with_z_index<R>(&mut self, z_index: u16, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let new_stacking_order_id = post_inc(
|
||||
self.window_mut()
|
||||
.next_frame
|
||||
.next_stacking_order_ids
|
||||
.last_mut()
|
||||
.unwrap(),
|
||||
);
|
||||
self.window_mut().next_stacking_order_ids.push(0);
|
||||
self.window_mut().next_frame.next_stacking_order_ids.push(0);
|
||||
let new_context = StackingContext {
|
||||
z_index,
|
||||
id: new_stacking_order_id,
|
||||
};
|
||||
|
||||
self.window_mut().z_index_stack.push(new_context);
|
||||
|
||||
if self.window.z_index_stack.len() == 2 {
|
||||
let last = self.window.z_index_stack.last().unwrap();
|
||||
if last.z_index == 0 && last.id == 5 {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
self.window_mut().next_frame.z_index_stack.push(new_context);
|
||||
let result = f(self);
|
||||
self.window_mut().z_index_stack.pop();
|
||||
self.window_mut().next_frame.z_index_stack.pop();
|
||||
|
||||
self.window_mut().next_stacking_order_ids.pop();
|
||||
self.window_mut().next_frame.next_stacking_order_ids.pop();
|
||||
|
||||
result
|
||||
}
|
||||
@@ -655,7 +667,7 @@ impl<'a> ElementContext<'a> {
|
||||
shadow_bounds.origin += shadow.offset;
|
||||
shadow_bounds.dilate(shadow.spread_radius);
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
Shadow {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -681,7 +693,7 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
Quad {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -709,7 +721,7 @@ impl<'a> ElementContext<'a> {
|
||||
window
|
||||
.next_frame
|
||||
.scene
|
||||
.insert(&window.z_index_stack, path.scale(scale_factor));
|
||||
.insert(&window.next_frame.z_index_stack, path.scale(scale_factor));
|
||||
}
|
||||
|
||||
/// Paint an underline into the scene for the next frame at the current z-index.
|
||||
@@ -734,7 +746,7 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
Underline {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -766,7 +778,7 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
Underline {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -826,7 +838,7 @@ impl<'a> ElementContext<'a> {
|
||||
let view_id = self.parent_view_id();
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
MonochromeSprite {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -884,7 +896,7 @@ impl<'a> ElementContext<'a> {
|
||||
let window = &mut *self.window;
|
||||
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
PolychromeSprite {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -930,7 +942,7 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
MonochromeSprite {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -969,7 +981,7 @@ impl<'a> ElementContext<'a> {
|
||||
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
PolychromeSprite {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -994,7 +1006,7 @@ impl<'a> ElementContext<'a> {
|
||||
let view_id = self.parent_view_id();
|
||||
let window = &mut *self.window;
|
||||
window.next_frame.scene.insert(
|
||||
&window.z_index_stack,
|
||||
&window.next_frame.z_index_stack,
|
||||
crate::Surface {
|
||||
view_id: view_id.into(),
|
||||
layer_id: 0,
|
||||
@@ -1080,10 +1092,9 @@ impl<'a> ElementContext<'a> {
|
||||
.requested_style(layout_id)
|
||||
}
|
||||
|
||||
/// TODO
|
||||
/// Called during painting to track which z-index is on top at each pixel position
|
||||
pub fn add_opaque_layer(&mut self, bounds: Bounds<Pixels>) {
|
||||
assert_ne!(self.window.z_index_stack.last().cloned().unwrap().id, 6);
|
||||
let stacking_order = self.window.z_index_stack.clone();
|
||||
let stacking_order = self.window.next_frame.z_index_stack.clone();
|
||||
let view_id = self.parent_view_id();
|
||||
let depth_map = &mut self.window.next_frame.depth_map;
|
||||
match depth_map.binary_search_by(|(level, _, _)| stacking_order.cmp(level)) {
|
||||
@@ -1130,8 +1141,8 @@ impl<'a> ElementContext<'a> {
|
||||
}
|
||||
|
||||
/// Invoke the given function with the given view id present on the view stack.
|
||||
/// This is a fairly low-level method used to in the post-layout and paint phase of views.
|
||||
pub fn with_view<R>(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
/// This is a fairly low-level method used to paint views.
|
||||
pub fn paint_view<R>(&mut self, view_id: EntityId, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
let text_system = self.text_system().clone();
|
||||
text_system.with_view(view_id, || {
|
||||
if self.window.next_frame.view_stack.last() == Some(&view_id) {
|
||||
@@ -1177,7 +1188,7 @@ impl<'a> ElementContext<'a> {
|
||||
mut handler: impl FnMut(&Event, DispatchPhase, &mut ElementContext) + 'static,
|
||||
) {
|
||||
let view_id = self.parent_view_id();
|
||||
let order = self.window.z_index_stack.clone();
|
||||
let order = self.window.next_frame.z_index_stack.clone();
|
||||
self.window
|
||||
.next_frame
|
||||
.mouse_listeners
|
||||
|
||||
@@ -28,11 +28,6 @@ use lazy_static::lazy_static;
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use schemars::{
|
||||
gen::SchemaGenerator,
|
||||
schema::{InstanceType, Schema, SchemaObject},
|
||||
JsonSchema,
|
||||
};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use std::{
|
||||
@@ -368,7 +363,7 @@ pub struct CodeLabel {
|
||||
pub filter_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, JsonSchema)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct LanguageConfig {
|
||||
/// Human-readable name of the language.
|
||||
pub name: Arc<str>,
|
||||
@@ -379,7 +374,6 @@ pub struct LanguageConfig {
|
||||
pub matcher: LanguageMatcher,
|
||||
/// List of bracket types in a language.
|
||||
#[serde(default)]
|
||||
#[schemars(schema_with = "bracket_pair_config_json_schema")]
|
||||
pub brackets: BracketPairConfig,
|
||||
/// If set to true, auto indentation uses last non empty line to determine
|
||||
/// the indentation level for a new line.
|
||||
@@ -388,12 +382,10 @@ pub struct LanguageConfig {
|
||||
/// A regex that is used to determine whether the indentation level should be
|
||||
/// increased in the following line.
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
#[schemars(schema_with = "regex_json_schema")]
|
||||
pub increase_indent_pattern: Option<Regex>,
|
||||
/// A regex that is used to determine whether the indentation level should be
|
||||
/// decreased in the following line.
|
||||
#[serde(default, deserialize_with = "deserialize_regex")]
|
||||
#[schemars(schema_with = "regex_json_schema")]
|
||||
pub decrease_indent_pattern: Option<Regex>,
|
||||
/// A list of characters that trigger the automatic insertion of a closing
|
||||
/// bracket when they immediately precede the point where an opening
|
||||
@@ -426,7 +418,7 @@ pub struct LanguageConfig {
|
||||
pub prettier_parser_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct LanguageMatcher {
|
||||
/// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
|
||||
#[serde(default)]
|
||||
@@ -437,7 +429,6 @@ pub struct LanguageMatcher {
|
||||
serialize_with = "serialize_regex",
|
||||
deserialize_with = "deserialize_regex"
|
||||
)]
|
||||
#[schemars(schema_with = "regex_json_schema")]
|
||||
pub first_line_pattern: Option<Regex>,
|
||||
}
|
||||
|
||||
@@ -450,14 +441,13 @@ pub struct LanguageScope {
|
||||
override_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default, Debug, JsonSchema)]
|
||||
#[derive(Clone, Deserialize, Default, Debug)]
|
||||
pub struct LanguageConfigOverride {
|
||||
#[serde(default)]
|
||||
pub line_comments: Override<Vec<Arc<str>>>,
|
||||
#[serde(default)]
|
||||
pub block_comment: Override<(Arc<str>, Arc<str>)>,
|
||||
#[serde(skip_deserializing)]
|
||||
#[schemars(skip)]
|
||||
pub disabled_bracket_ixs: Vec<u16>,
|
||||
#[serde(default)]
|
||||
pub word_characters: Override<HashSet<char>>,
|
||||
@@ -465,7 +455,7 @@ pub struct LanguageConfigOverride {
|
||||
pub opt_into_language_servers: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum Override<T> {
|
||||
Remove { remove: bool },
|
||||
@@ -523,13 +513,6 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
|
||||
}
|
||||
}
|
||||
|
||||
fn regex_json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_regex<S>(regex: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@@ -556,34 +539,29 @@ pub struct FakeLspAdapter {
|
||||
///
|
||||
/// This struct includes settings for defining which pairs of characters are considered brackets and
|
||||
/// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes.
|
||||
#[derive(Clone, Debug, Default, JsonSchema)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BracketPairConfig {
|
||||
/// A list of character pairs that should be treated as brackets in the context of a given language.
|
||||
pub pairs: Vec<BracketPair>,
|
||||
/// A list of tree-sitter scopes for which a given bracket should not be active.
|
||||
/// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]`
|
||||
#[schemars(skip)]
|
||||
pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
fn bracket_pair_config_json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
Option::<Vec<BracketPairContent>>::json_schema(gen)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct BracketPairContent {
|
||||
#[serde(flatten)]
|
||||
pub bracket_pair: BracketPair,
|
||||
#[serde(default)]
|
||||
pub not_in: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for BracketPairConfig {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let result = Vec::<BracketPairContent>::deserialize(deserializer)?;
|
||||
#[derive(Deserialize)]
|
||||
pub struct Entry {
|
||||
#[serde(flatten)]
|
||||
pub bracket_pair: BracketPair,
|
||||
#[serde(default)]
|
||||
pub not_in: Vec<String>,
|
||||
}
|
||||
|
||||
let result = Vec::<Entry>::deserialize(deserializer)?;
|
||||
let mut brackets = Vec::with_capacity(result.len());
|
||||
let mut disabled_scopes_by_bracket_ix = Vec::with_capacity(result.len());
|
||||
for entry in result {
|
||||
@@ -600,7 +578,7 @@ impl<'de> Deserialize<'de> for BracketPairConfig {
|
||||
|
||||
/// Describes a single bracket pair and how an editor should react to e.g. inserting
|
||||
/// an opening bracket or to a newline character insertion in between `start` and `end` characters.
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||
pub struct BracketPair {
|
||||
/// Starting substring for a bracket.
|
||||
pub start: String,
|
||||
|
||||
@@ -28,7 +28,7 @@ use futures::{
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use gpui::{
|
||||
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, Context, Entity, EventEmitter,
|
||||
Model, ModelContext, PromptLevel, Task, WeakModel,
|
||||
Model, ModelContext, Task, WeakModel,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
@@ -47,8 +47,7 @@ use language::{
|
||||
use log::error;
|
||||
use lsp::{
|
||||
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId,
|
||||
MessageActionItem, OneOf,
|
||||
DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf,
|
||||
};
|
||||
use lsp_command::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
@@ -214,37 +213,12 @@ enum ProjectClientState {
|
||||
},
|
||||
}
|
||||
|
||||
/// A prompt requested by LSP server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LanguageServerPromptRequest {
|
||||
pub level: PromptLevel,
|
||||
pub message: String,
|
||||
pub actions: Vec<MessageActionItem>,
|
||||
response_channel: Sender<MessageActionItem>,
|
||||
}
|
||||
|
||||
impl LanguageServerPromptRequest {
|
||||
pub async fn respond(self, index: usize) -> Option<()> {
|
||||
if let Some(response) = self.actions.into_iter().nth(index) {
|
||||
self.response_channel.send(response).await.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PartialEq for LanguageServerPromptRequest {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.message == other.message && self.actions == other.actions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Event {
|
||||
LanguageServerAdded(LanguageServerId),
|
||||
LanguageServerRemoved(LanguageServerId),
|
||||
LanguageServerLog(LanguageServerId, String),
|
||||
Notification(String),
|
||||
LanguageServerPrompt(LanguageServerPromptRequest),
|
||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||
ActivateProjectPanel,
|
||||
WorktreeAdded,
|
||||
@@ -3131,42 +3105,6 @@ impl Project {
|
||||
})
|
||||
.detach();
|
||||
|
||||
language_server
|
||||
.on_request::<lsp::request::ShowMessageRequest, _, _>({
|
||||
let this = this.clone();
|
||||
move |params, mut cx| {
|
||||
let this = this.clone();
|
||||
async move {
|
||||
if let Some(actions) = params.actions {
|
||||
let (tx, mut rx) = smol::channel::bounded(1);
|
||||
let request = LanguageServerPromptRequest {
|
||||
level: match params.typ {
|
||||
lsp::MessageType::ERROR => PromptLevel::Critical,
|
||||
lsp::MessageType::WARNING => PromptLevel::Warning,
|
||||
_ => PromptLevel::Info,
|
||||
},
|
||||
message: params.message,
|
||||
actions,
|
||||
response_channel: tx,
|
||||
};
|
||||
|
||||
if let Ok(_) = this.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::LanguageServerPrompt(request));
|
||||
}) {
|
||||
let response = rx.next().await;
|
||||
|
||||
Ok(response)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let disk_based_diagnostics_progress_token =
|
||||
adapter.disk_based_diagnostics_progress_token.clone();
|
||||
|
||||
|
||||
@@ -9,16 +9,13 @@ use crate::{
|
||||
ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use editor::{
|
||||
actions::{Tab, TabPrev},
|
||||
Editor, EditorElement, EditorStyle,
|
||||
};
|
||||
use editor::{actions::Tab, Editor, EditorElement, EditorStyle};
|
||||
use futures::channel::oneshot;
|
||||
use gpui::{
|
||||
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
|
||||
FontStyle, FontWeight, Hsla, InteractiveElement as _, IntoElement, KeyContext,
|
||||
ParentElement as _, Render, Styled, Subscription, Task, TextStyle, View, ViewContext,
|
||||
VisualContext as _, WhiteSpace, WindowContext,
|
||||
FontStyle, FontWeight, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _,
|
||||
Render, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext as _,
|
||||
WhiteSpace, WindowContext,
|
||||
};
|
||||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
@@ -26,7 +23,7 @@ use settings::Settings;
|
||||
use std::{any::Any, sync::Arc};
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use ui::{h_flex, prelude::*, IconButton, IconName, ToggleButton, Tooltip};
|
||||
use ui::{h_flex, prelude::*, Icon, IconButton, IconName, ToggleButton, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
item::ItemHandle,
|
||||
@@ -37,9 +34,6 @@ use workspace::{
|
||||
pub use registrar::DivRegistrar;
|
||||
use registrar::{ForDeployed, ForDismissed, SearchActionsRegistrar, WithResults};
|
||||
|
||||
const MIN_INPUT_WIDTH_REMS: f32 = 15.;
|
||||
const MAX_INPUT_WIDTH_REMS: f32 = 25.;
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize)]
|
||||
pub struct Deploy {
|
||||
pub focus: bool,
|
||||
@@ -60,9 +54,7 @@ pub fn init(cx: &mut AppContext) {
|
||||
|
||||
pub struct BufferSearchBar {
|
||||
query_editor: View<Editor>,
|
||||
query_editor_focused: bool,
|
||||
replacement_editor: View<Editor>,
|
||||
replacement_editor_focused: bool,
|
||||
active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
|
||||
active_match_index: Option<usize>,
|
||||
active_searchable_item_subscription: Option<Subscription>,
|
||||
@@ -80,18 +72,13 @@ pub struct BufferSearchBar {
|
||||
}
|
||||
|
||||
impl BufferSearchBar {
|
||||
fn render_text_input(
|
||||
&self,
|
||||
editor: &View<Editor>,
|
||||
color: Hsla,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
fn render_text_input(&self, editor: &View<Editor>, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if editor.read(cx).read_only(cx) {
|
||||
cx.theme().colors().text_disabled
|
||||
} else {
|
||||
color
|
||||
cx.theme().colors().text
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
@@ -174,8 +161,7 @@ impl Render for BufferSearchBar {
|
||||
editor.set_placeholder_text("Replace with...", cx);
|
||||
});
|
||||
|
||||
let mut match_color = Color::Default;
|
||||
let match_text = self
|
||||
let match_count = self
|
||||
.active_searchable_item
|
||||
.as_ref()
|
||||
.and_then(|searchable_item| {
|
||||
@@ -185,15 +171,14 @@ impl Render for BufferSearchBar {
|
||||
let matches = self
|
||||
.searchable_items_with_matches
|
||||
.get(&searchable_item.downgrade())?;
|
||||
if let Some(match_ix) = self.active_match_index {
|
||||
Some(format!("{}/{}", match_ix + 1, matches.len()))
|
||||
let message = if let Some(match_ix) = self.active_match_index {
|
||||
format!("{}/{}", match_ix + 1, matches.len())
|
||||
} else {
|
||||
match_color = Color::Error; // No matches found
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| "No matches".to_string());
|
||||
let match_count = Label::new(match_text).color(match_color);
|
||||
"No matches".to_string()
|
||||
};
|
||||
|
||||
Some(ui::Label::new(message))
|
||||
});
|
||||
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
||||
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(cx);
|
||||
|
||||
@@ -207,9 +192,35 @@ impl Render for BufferSearchBar {
|
||||
} else {
|
||||
cx.theme().colors().border
|
||||
};
|
||||
|
||||
let search_line = h_flex()
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_2()
|
||||
.key_context(key_context)
|
||||
.capture_action(cx.listener(Self::tab))
|
||||
.on_action(cx.listener(Self::previous_history_query))
|
||||
.on_action(cx.listener(Self::next_history_query))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::select_next_match))
|
||||
.on_action(cx.listener(Self::select_prev_match))
|
||||
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Regex, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Text, cx);
|
||||
}))
|
||||
.when(self.supported_options().replacement, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_replace))
|
||||
.when(in_replace, |this| {
|
||||
this.on_action(cx.listener(Self::replace_next))
|
||||
.on_action(cx.listener(Self::replace_all))
|
||||
})
|
||||
})
|
||||
.when(self.supported_options().case, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_case_sensitive))
|
||||
})
|
||||
.when(self.supported_options().word, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_whole_word))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
@@ -218,10 +229,10 @@ impl Render for BufferSearchBar {
|
||||
.gap_2()
|
||||
.border_1()
|
||||
.border_color(editor_border)
|
||||
.min_w(rems(MIN_INPUT_WIDTH_REMS))
|
||||
.max_w(rems(MAX_INPUT_WIDTH_REMS))
|
||||
.min_w(rems(384. / 16.))
|
||||
.rounded_lg()
|
||||
.child(self.render_text_input(&self.query_editor, match_color.color(cx), cx))
|
||||
.child(Icon::new(IconName::MagnifyingGlass))
|
||||
.child(self.render_text_input(&self.query_editor, cx))
|
||||
.children(supported_options.case.then(|| {
|
||||
self.render_search_option_button(
|
||||
SearchOptions::CASE_SENSITIVE,
|
||||
@@ -297,6 +308,49 @@ impl Render for BufferSearchBar {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.flex_1()
|
||||
.when(self.replace_enabled, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
// We're giving this a fixed height to match the height of the search input,
|
||||
// which has an icon inside that is increasing its height.
|
||||
.h_8()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.gap_2()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_lg()
|
||||
.child(self.render_text_input(&self.replacement_editor, cx)),
|
||||
)
|
||||
.when(should_show_replace_input, |this| {
|
||||
this.child(
|
||||
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Replace next", &ReplaceNext, cx)
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.replace_next(&ReplaceNext, cx)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Replace all", &ReplaceAll, cx)
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
this.replace_all(&ReplaceAll, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
@@ -308,7 +362,7 @@ impl Render for BufferSearchBar {
|
||||
Tooltip::for_action("Select all matches", &SelectAllMatches, cx)
|
||||
}),
|
||||
)
|
||||
.child(div().min_w(rems(6.)).child(match_count))
|
||||
.children(match_count)
|
||||
.child(render_nav_button(
|
||||
ui::IconName::ChevronLeft,
|
||||
self.active_match_index.is_some(),
|
||||
@@ -321,89 +375,7 @@ impl Render for BufferSearchBar {
|
||||
"Select next match",
|
||||
&SelectNextMatch,
|
||||
)),
|
||||
);
|
||||
|
||||
let replace_line = self.replace_enabled.then(|| {
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.flex_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.flex_1()
|
||||
// We're giving this a fixed height to match the height of the search input,
|
||||
// which has an icon inside that is increasing its height.
|
||||
// .h_8()
|
||||
.px_2()
|
||||
.py_1()
|
||||
.gap_2()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_lg()
|
||||
.min_w(rems(MIN_INPUT_WIDTH_REMS))
|
||||
.max_w(rems(MAX_INPUT_WIDTH_REMS))
|
||||
.child(self.render_text_input(
|
||||
&self.replacement_editor,
|
||||
cx.theme().colors().text,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.when(should_show_replace_input, |this| {
|
||||
this.child(
|
||||
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action("Replace next", &ReplaceNext, cx)
|
||||
})
|
||||
.on_click(
|
||||
cx.listener(|this, _, cx| this.replace_next(&ReplaceNext, cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
|
||||
.tooltip(move |cx| Tooltip::for_action("Replace all", &ReplaceAll, cx))
|
||||
.on_click(cx.listener(|this, _, cx| this.replace_all(&ReplaceAll, cx))),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.key_context(key_context)
|
||||
.capture_action(cx.listener(Self::tab))
|
||||
.capture_action(cx.listener(Self::tab_prev))
|
||||
.on_action(cx.listener(Self::previous_history_query))
|
||||
.on_action(cx.listener(Self::next_history_query))
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::select_next_match))
|
||||
.on_action(cx.listener(Self::select_prev_match))
|
||||
.on_action(cx.listener(|this, _: &ActivateRegexMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Regex, cx);
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &ActivateTextMode, cx| {
|
||||
this.activate_search_mode(SearchMode::Text, cx);
|
||||
}))
|
||||
.when(self.supported_options().replacement, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_replace))
|
||||
.when(in_replace, |this| {
|
||||
this.on_action(cx.listener(Self::replace_next))
|
||||
.on_action(cx.listener(Self::replace_all))
|
||||
})
|
||||
})
|
||||
.when(self.supported_options().case, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_case_sensitive))
|
||||
})
|
||||
.when(self.supported_options().word, |this| {
|
||||
this.on_action(cx.listener(Self::toggle_whole_word))
|
||||
})
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex().child(search_line.w_full()).child(
|
||||
IconButton::new(SharedString::from("Close"), IconName::Close)
|
||||
.tooltip(move |cx| Tooltip::for_action("Close search bar", &Dismiss, cx))
|
||||
.on_click(
|
||||
cx.listener(|this, _: &ClickEvent, cx| this.dismiss(&Dismiss, cx)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.children(replace_line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,13 +488,11 @@ impl BufferSearchBar {
|
||||
cx.subscribe(&query_editor, Self::on_query_editor_event)
|
||||
.detach();
|
||||
let replacement_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
cx.subscribe(&replacement_editor, Self::on_replacement_editor_event)
|
||||
cx.subscribe(&replacement_editor, Self::on_query_editor_event)
|
||||
.detach();
|
||||
Self {
|
||||
query_editor,
|
||||
query_editor_focused: false,
|
||||
replacement_editor,
|
||||
replacement_editor_focused: false,
|
||||
active_searchable_item: None,
|
||||
active_searchable_item_subscription: None,
|
||||
active_match_index: None,
|
||||
@@ -795,33 +765,15 @@ impl BufferSearchBar {
|
||||
event: &editor::EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::EditorEvent::Focused => self.query_editor_focused = true,
|
||||
editor::EditorEvent::Blurred => self.query_editor_focused = false,
|
||||
editor::EditorEvent::Edited => {
|
||||
self.query_contains_error = false;
|
||||
self.clear_matches(cx);
|
||||
let search = self.update_matches(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
search.await?;
|
||||
this.update(&mut cx, |this, cx| this.activate_current_match(cx))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_replacement_editor_event(
|
||||
&mut self,
|
||||
_: View<Editor>,
|
||||
event: &editor::EditorEvent,
|
||||
_: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
editor::EditorEvent::Focused => self.replacement_editor_focused = true,
|
||||
editor::EditorEvent::Blurred => self.replacement_editor_focused = false,
|
||||
_ => {}
|
||||
if let editor::EditorEvent::Edited = event {
|
||||
self.query_contains_error = false;
|
||||
self.clear_matches(cx);
|
||||
let search = self.update_matches(cx);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
search.await?;
|
||||
this.update(&mut cx, |this, cx| this.activate_current_match(cx))
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,29 +911,11 @@ impl BufferSearchBar {
|
||||
}
|
||||
|
||||
fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
// Search -> Replace -> Editor
|
||||
let focus_handle = if self.replace_enabled && self.query_editor_focused {
|
||||
self.replacement_editor.focus_handle(cx)
|
||||
} else if let Some(item) = self.active_searchable_item.as_ref() {
|
||||
item.focus_handle(cx)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
cx.focus(&focus_handle);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
|
||||
fn tab_prev(&mut self, _: &TabPrev, cx: &mut ViewContext<Self>) {
|
||||
// Search -> Replace -> Search
|
||||
let focus_handle = if self.replace_enabled && self.query_editor_focused {
|
||||
self.replacement_editor.focus_handle(cx)
|
||||
} else if self.replacement_editor_focused {
|
||||
self.query_editor.focus_handle(cx)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
cx.focus(&focus_handle);
|
||||
cx.stop_propagation();
|
||||
if let Some(item) = self.active_searchable_item.as_ref() {
|
||||
let focus_handle = item.focus_handle(cx);
|
||||
cx.focus(&focus_handle);
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
|
||||
@@ -1011,12 +945,10 @@ impl BufferSearchBar {
|
||||
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
|
||||
if let Some(_) = &self.active_searchable_item {
|
||||
self.replace_enabled = !self.replace_enabled;
|
||||
let handle = if self.replace_enabled {
|
||||
self.replacement_editor.focus_handle(cx)
|
||||
} else {
|
||||
self.query_editor.focus_handle(cx)
|
||||
};
|
||||
cx.focus(&handle);
|
||||
if !self.replace_enabled {
|
||||
let handle = self.query_editor.focus_handle(cx);
|
||||
cx.focus(&handle);
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ mod semantic_index_tests;
|
||||
|
||||
use crate::semantic_index_settings::SemanticIndexSettings;
|
||||
use ai::embedding::{Embedding, EmbeddingProvider};
|
||||
use ai::providers::open_ai::{OpenAiEmbeddingProvider, OPEN_AI_API_URL};
|
||||
use ai::providers::open_ai::OpenAiEmbeddingProvider;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use db::VectorDatabase;
|
||||
@@ -91,13 +91,8 @@ pub fn init(
|
||||
.detach();
|
||||
|
||||
cx.spawn(move |cx| async move {
|
||||
let embedding_provider = OpenAiEmbeddingProvider::new(
|
||||
// TODO: We should read it from config, but I'm not sure whether to reuse `openai_api_url` in assistant settings or not
|
||||
OPEN_AI_API_URL.to_string(),
|
||||
http_client,
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await;
|
||||
let embedding_provider =
|
||||
OpenAiEmbeddingProvider::new(http_client, cx.background_executor().clone()).await;
|
||||
let semantic_index = SemanticIndex::new(
|
||||
fs,
|
||||
db_file_path,
|
||||
|
||||
@@ -10,49 +10,37 @@ impl Render for ZIndexStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container().child(Story::title("z-index")).child(
|
||||
div()
|
||||
.debug_selector(|| "outer_button".into())
|
||||
.size_20()
|
||||
.bg(gpui::red())
|
||||
.hover(|s| s.bg(gpui::green()))
|
||||
.flex()
|
||||
.child(
|
||||
div()
|
||||
.debug_selector(|| "inner_button".into())
|
||||
.z_index(0)
|
||||
.size_8()
|
||||
.bg(gpui::yellow())
|
||||
.hover(|s| s.bg(gpui::blue())),
|
||||
), // div()
|
||||
// .flex()
|
||||
// .child(
|
||||
// div()
|
||||
// .w(px(250.))
|
||||
// .child(Story::label("z-index: auto"))
|
||||
// .child(ZIndexExample::new(0)),
|
||||
// )
|
||||
// .child(
|
||||
// div()
|
||||
// .w(px(250.))
|
||||
// .child(Story::label("z-index: 1"))
|
||||
// .child(ZIndexExample::new(1)),
|
||||
// )
|
||||
// .child(
|
||||
// div()
|
||||
// .w(px(250.))
|
||||
// .child(Story::label("z-index: 3"))
|
||||
// .child(ZIndexExample::new(3)),
|
||||
// )
|
||||
// .child(
|
||||
// div()
|
||||
// .w(px(250.))
|
||||
// .child(Story::label("z-index: 5"))
|
||||
// .child(ZIndexExample::new(5)),
|
||||
// )
|
||||
// .child(
|
||||
// div()
|
||||
// .w(px(250.))
|
||||
// .child(Story::label("z-index: 7"))
|
||||
// .child(ZIndexExample::new(7)),
|
||||
// ),
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: auto"))
|
||||
.child(ZIndexExample::new(0)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 1"))
|
||||
.child(ZIndexExample::new(1)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 3"))
|
||||
.child(ZIndexExample::new(3)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 5"))
|
||||
.child(ZIndexExample::new(5)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 7"))
|
||||
.child(ZIndexExample::new(7)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,27 +37,26 @@ fn main() {
|
||||
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
// let args = Args::parse();
|
||||
let args = Args::parse();
|
||||
|
||||
// let story_selector = args.story.clone().unwrap_or_else(|| {
|
||||
// let stories = ComponentStory::iter().collect::<Vec<_>>();
|
||||
let story_selector = args.story.clone().unwrap_or_else(|| {
|
||||
let stories = ComponentStory::iter().collect::<Vec<_>>();
|
||||
|
||||
// ctrlc::set_handler(move || {}).unwrap();
|
||||
ctrlc::set_handler(move || {}).unwrap();
|
||||
|
||||
// let result = FuzzySelect::new()
|
||||
// .with_prompt("Choose a story to run:")
|
||||
// .items(&stories)
|
||||
// .interact();
|
||||
let result = FuzzySelect::new()
|
||||
.with_prompt("Choose a story to run:")
|
||||
.items(&stories)
|
||||
.interact();
|
||||
|
||||
// let Ok(selection) = result else {
|
||||
// dialoguer::console::Term::stderr().show_cursor().unwrap();
|
||||
// std::process::exit(0);
|
||||
// };
|
||||
let Ok(selection) = result else {
|
||||
dialoguer::console::Term::stderr().show_cursor().unwrap();
|
||||
std::process::exit(0);
|
||||
};
|
||||
|
||||
// StorySelector::Component(stories[selection])
|
||||
// });
|
||||
// let theme_name = args.theme.unwrap_or("One Dark".to_string());
|
||||
let theme_name = "One Dark".to_string();
|
||||
StorySelector::Component(stories[selection])
|
||||
});
|
||||
let theme_name = args.theme.unwrap_or("One Dark".to_string());
|
||||
|
||||
gpui::App::new().with_assets(Assets).run(move |cx| {
|
||||
load_embedded_fonts(cx).unwrap();
|
||||
@@ -70,7 +69,7 @@ fn main() {
|
||||
|
||||
theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
|
||||
|
||||
// let selector = story_selector;
|
||||
let selector = story_selector;
|
||||
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
@@ -92,8 +91,7 @@ fn main() {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
cx.set_rem_size(ui_font_size);
|
||||
|
||||
// cx.new_view(|cx| StoryWrapper::new(selector.story(cx)))
|
||||
cx.new_view(|cx| stories::ZIndexStory)
|
||||
cx.new_view(|cx| StoryWrapper::new(selector.story(cx)))
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -447,7 +447,7 @@ impl TerminalElement {
|
||||
};
|
||||
|
||||
let text_system = cx.text_system();
|
||||
let player_color = theme.players().local();
|
||||
let selection_color = theme.players().local();
|
||||
let match_color = theme.colors().search_match_background;
|
||||
let gutter;
|
||||
let dimensions = {
|
||||
@@ -493,7 +493,7 @@ impl TerminalElement {
|
||||
bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_text_bounds.did_visibly_contains(&cx.mouse_position(), cx) {
|
||||
if interactive_text_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
if self.can_navigate_to_selected_word && last_hovered_word.is_some() {
|
||||
cx.set_cursor_style(gpui::CursorStyle::PointingHand)
|
||||
} else {
|
||||
@@ -526,7 +526,7 @@ impl TerminalElement {
|
||||
}
|
||||
if let Some(selection) = selection {
|
||||
relative_highlighted_ranges
|
||||
.push((selection.start..=selection.end, player_color.selection));
|
||||
.push((selection.start..=selection.end, selection_color.cursor));
|
||||
}
|
||||
|
||||
// then have that representation be converted to the appropriate highlight data structure
|
||||
@@ -658,7 +658,7 @@ impl TerminalElement {
|
||||
}
|
||||
|
||||
if e.pressed_button.is_some() && !cx.has_active_drag() {
|
||||
let visibly_contains = interactive_bounds.did_visibly_contains(&e.position, cx);
|
||||
let visibly_contains = interactive_bounds.visibly_contains(&e.position, cx);
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
if !terminal.selection_started() {
|
||||
if visibly_contains {
|
||||
@@ -672,7 +672,7 @@ impl TerminalElement {
|
||||
})
|
||||
}
|
||||
|
||||
if interactive_bounds.did_visibly_contains(&e.position, cx) {
|
||||
if interactive_bounds.visibly_contains(&e.position, cx) {
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
terminal.mouse_move(&e, origin);
|
||||
cx.notify();
|
||||
|
||||
@@ -46,10 +46,6 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
const REGEX_SPECIAL_CHARS: &[char] = &[
|
||||
'\\', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '^', '$',
|
||||
];
|
||||
|
||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
///Event to transmit the scroll from the element to the view
|
||||
@@ -440,6 +436,21 @@ impl TerminalView {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn find_matches(
|
||||
&mut self,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<RangeInclusive<Point>>> {
|
||||
let searcher = regex_search_for_query(&query);
|
||||
|
||||
if let Some(searcher) = searcher {
|
||||
self.terminal
|
||||
.update(cx, |term, cx| term.find_matches(searcher, cx))
|
||||
} else {
|
||||
cx.background_executor().spawn(async { Vec::new() })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn terminal(&self) -> &Model<Terminal> {
|
||||
&self.terminal
|
||||
}
|
||||
@@ -645,19 +656,6 @@ fn possible_open_targets(
|
||||
possible_open_paths_metadata(fs, row, column, potential_abs_paths, cx)
|
||||
}
|
||||
|
||||
fn regex_to_literal(regex: &str) -> String {
|
||||
regex
|
||||
.chars()
|
||||
.flat_map(|c| {
|
||||
if REGEX_SPECIAL_CHARS.contains(&c) {
|
||||
vec!['\\', c]
|
||||
} else {
|
||||
vec![c]
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn regex_search_for_query(query: &project::search::SearchQuery) -> Option<RegexSearch> {
|
||||
let query = query.as_str();
|
||||
if query == "." {
|
||||
@@ -876,7 +874,7 @@ impl SearchableItem for TerminalView {
|
||||
SearchOptions {
|
||||
case: false,
|
||||
word: false,
|
||||
regex: true,
|
||||
regex: false,
|
||||
replacement: false,
|
||||
}
|
||||
}
|
||||
@@ -918,27 +916,12 @@ impl SearchableItem for TerminalView {
|
||||
/// Get all of the matches for this query, should be done on the background
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: Arc<SearchQuery>,
|
||||
query: Arc<project::search::SearchQuery>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Vec<Self::Match>> {
|
||||
let searcher = match &*query {
|
||||
SearchQuery::Text { .. } => regex_search_for_query(
|
||||
&(SearchQuery::text(
|
||||
regex_to_literal(&query.as_str()),
|
||||
query.whole_word(),
|
||||
query.case_sensitive(),
|
||||
query.include_ignored(),
|
||||
query.files_to_include().to_vec(),
|
||||
query.files_to_exclude().to_vec(),
|
||||
)
|
||||
.unwrap()),
|
||||
),
|
||||
SearchQuery::Regex { .. } => regex_search_for_query(&query),
|
||||
};
|
||||
|
||||
if let Some(s) = searcher {
|
||||
if let Some(searcher) = regex_search_for_query(&query) {
|
||||
self.terminal()
|
||||
.update(cx, |term, cx| term.find_matches(s, cx))
|
||||
.update(cx, |term, cx| term.find_matches(searcher, cx))
|
||||
} else {
|
||||
Task::ready(vec![])
|
||||
}
|
||||
@@ -1229,14 +1212,4 @@ mod tests {
|
||||
project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escapes_only_special_characters() {
|
||||
assert_eq!(regex_to_literal(r"test(\w)"), r"test\(\\w\)".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_string_stays_empty() {
|
||||
assert_eq!(regex_to_literal(""), "".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if let Some(mut child) = element_state.child_element.take() {
|
||||
if let Some(mut child) = element_state.child_element.as_mut() {
|
||||
child.paint(cx);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
element_state.child_bounds = Some(cx.layout_bounds(child_layout_id));
|
||||
}
|
||||
|
||||
if let Some(mut menu) = element_state.menu_element.take() {
|
||||
if let Some(mut menu) = element_state.menu_element.as_mut() {
|
||||
menu.paint(cx);
|
||||
|
||||
if let Some(child_bounds) = element_state.child_bounds {
|
||||
@@ -212,7 +212,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
// want a click on the toggle to re-open it.
|
||||
cx.on_mouse_event(move |e: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& interactive_bounds.did_visibly_contains(&e.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&e.position, cx)
|
||||
{
|
||||
cx.stop_propagation()
|
||||
}
|
||||
|
||||
@@ -118,11 +118,11 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
element_state: &mut Self::State,
|
||||
cx: &mut ElementContext,
|
||||
) {
|
||||
if let Some(mut child) = element_state.child_element.take() {
|
||||
if let Some(mut child) = element_state.child_element.as_mut() {
|
||||
child.paint(cx);
|
||||
}
|
||||
|
||||
if let Some(mut menu) = element_state.menu_element.take() {
|
||||
if let Some(mut menu) = element_state.menu_element.as_mut() {
|
||||
menu.paint(cx);
|
||||
return;
|
||||
}
|
||||
@@ -143,7 +143,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Right
|
||||
&& interactive_bounds.did_visibly_contains(&event.position, cx)
|
||||
&& interactive_bounds.visibly_contains(&event.position, cx)
|
||||
{
|
||||
cx.stop_propagation();
|
||||
cx.prevent_default();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::{insert::NormalBefore, Vim, VimModeSetting};
|
||||
use crate::{insert::NormalBefore, Vim};
|
||||
use editor::{Editor, EditorEvent};
|
||||
use gpui::{Action, AppContext, Entity, EntityId, View, ViewContext, WindowContext};
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|_, cx: &mut ViewContext<Editor>| {
|
||||
@@ -13,17 +12,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
})
|
||||
.detach();
|
||||
|
||||
let mut enabled = VimModeSetting::get_global(cx).0;
|
||||
cx.observe_global::<SettingsStore>(move |editor, cx| {
|
||||
if VimModeSetting::get_global(cx).0 != enabled {
|
||||
enabled = VimModeSetting::get_global(cx).0;
|
||||
if !enabled {
|
||||
Vim::unhook_vim_settings(editor, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let id = cx.view().entity_id();
|
||||
cx.on_release(move |_, _, cx| released(id, cx)).detach();
|
||||
})
|
||||
@@ -31,25 +19,34 @@ pub fn init(cx: &mut AppContext) {
|
||||
}
|
||||
|
||||
fn focused(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
if Vim::read(cx).active_editor.clone().is_some() {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.update_active_editor(cx, |previously_active_editor, cx| {
|
||||
vim.unhook_vim_settings(previously_active_editor, cx)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Vim::update(cx, |vim, cx| {
|
||||
if !vim.enabled {
|
||||
return;
|
||||
}
|
||||
vim.activate_editor(editor.clone(), cx);
|
||||
vim.set_active_editor(editor.clone(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
vim.stop_recording_immediately(NormalBefore.boxed_clone());
|
||||
if let Some(previous_editor) = vim.active_editor.clone() {
|
||||
vim.stop_recording_immediately(NormalBefore.boxed_clone());
|
||||
if previous_editor
|
||||
.upgrade()
|
||||
.is_some_and(|previous| previous == editor.clone())
|
||||
{
|
||||
vim.clear_operator(cx);
|
||||
vim.active_editor = None;
|
||||
vim.editor_subscription = None;
|
||||
}
|
||||
}
|
||||
|
||||
editor.update(cx, |editor, cx| vim.unhook_vim_settings(editor, cx))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use gpui::{div, Element, Render, Subscription, ViewContext};
|
||||
use settings::SettingsStore;
|
||||
use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
|
||||
|
||||
use crate::{state::Mode, Vim};
|
||||
@@ -6,16 +7,20 @@ use crate::{state::Mode, Vim};
|
||||
/// The ModeIndicator displays the current mode in the status bar.
|
||||
pub struct ModeIndicator {
|
||||
pub(crate) mode: Option<Mode>,
|
||||
_subscription: Subscription,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl ModeIndicator {
|
||||
/// Construct a new mode indicator in this window.
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
let _subscription = cx.observe_global::<Vim>(|this, cx| this.update_mode(cx));
|
||||
let _subscriptions = vec![
|
||||
cx.observe_global::<Vim>(|this, cx| this.update_mode(cx)),
|
||||
cx.observe_global::<SettingsStore>(|this, cx| this.update_mode(cx)),
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
mode: None,
|
||||
_subscription,
|
||||
_subscriptions,
|
||||
};
|
||||
this.update_mode(cx);
|
||||
this
|
||||
|
||||
@@ -617,9 +617,6 @@ fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Display
|
||||
fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
for _ in 0..times {
|
||||
point = movement::left(map, point);
|
||||
if point.is_zero() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
point
|
||||
}
|
||||
@@ -627,9 +624,6 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di
|
||||
fn space(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint {
|
||||
for _ in 0..times {
|
||||
point = wrapping_right(map, point);
|
||||
if point == map.max_point() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
point
|
||||
}
|
||||
@@ -774,7 +768,7 @@ pub(crate) fn next_word_start(
|
||||
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||
for _ in 0..times {
|
||||
let mut crossed_newline = false;
|
||||
let new_point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
|
||||
point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
let at_newline = right == '\n';
|
||||
@@ -785,11 +779,7 @@ pub(crate) fn next_word_start(
|
||||
|
||||
crossed_newline |= at_newline;
|
||||
found
|
||||
});
|
||||
if point == new_point {
|
||||
break;
|
||||
}
|
||||
point = new_point;
|
||||
})
|
||||
}
|
||||
point
|
||||
}
|
||||
@@ -802,30 +792,21 @@ fn next_word_end(
|
||||
) -> DisplayPoint {
|
||||
let scope = map.buffer_snapshot.language_scope_at(point.to_point(map));
|
||||
for _ in 0..times {
|
||||
let mut new_point = point;
|
||||
if new_point.column() < map.line_len(new_point.row()) {
|
||||
*new_point.column_mut() += 1;
|
||||
} else if new_point.row() < map.max_buffer_row() {
|
||||
*new_point.row_mut() += 1;
|
||||
*new_point.column_mut() = 0;
|
||||
if point.column() < map.line_len(point.row()) {
|
||||
*point.column_mut() += 1;
|
||||
} else if point.row() < map.max_buffer_row() {
|
||||
*point.row_mut() += 1;
|
||||
*point.column_mut() = 0;
|
||||
}
|
||||
|
||||
let new_point = movement::find_boundary_exclusive(
|
||||
map,
|
||||
new_point,
|
||||
FindRange::MultiLine,
|
||||
|left, right| {
|
||||
point =
|
||||
movement::find_boundary_exclusive(map, point, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
|
||||
left_kind != right_kind && left_kind != CharKind::Whitespace
|
||||
},
|
||||
);
|
||||
let new_point = map.clip_point(new_point, Bias::Left);
|
||||
if point == new_point {
|
||||
break;
|
||||
}
|
||||
point = new_point;
|
||||
});
|
||||
point = map.clip_point(point, Bias::Left);
|
||||
}
|
||||
point
|
||||
}
|
||||
@@ -840,17 +821,13 @@ fn previous_word_start(
|
||||
for _ in 0..times {
|
||||
// This works even though find_preceding_boundary is called for every character in the line containing
|
||||
// cursor because the newline is checked only once.
|
||||
let new_point =
|
||||
point =
|
||||
movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
|
||||
let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
|
||||
let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
|
||||
|
||||
(left_kind != right_kind && !right.is_whitespace()) || left == '\n'
|
||||
});
|
||||
if point == new_point {
|
||||
break;
|
||||
}
|
||||
point = new_point;
|
||||
}
|
||||
point
|
||||
}
|
||||
@@ -990,14 +967,10 @@ fn find_forward(
|
||||
|
||||
for _ in 0..times {
|
||||
found = false;
|
||||
let new_to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
|
||||
to = find_boundary(map, to, FindRange::SingleLine, |_, right| {
|
||||
found = right == target;
|
||||
found
|
||||
});
|
||||
if to == new_to {
|
||||
break;
|
||||
}
|
||||
to = new_to;
|
||||
}
|
||||
|
||||
if found {
|
||||
@@ -1022,12 +995,7 @@ fn find_backward(
|
||||
let mut to = from;
|
||||
|
||||
for _ in 0..times {
|
||||
let new_to =
|
||||
find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
|
||||
if to == new_to {
|
||||
break;
|
||||
}
|
||||
to = new_to;
|
||||
to = find_preceding_boundary(map, to, FindRange::SingleLine, |_, right| right == target);
|
||||
}
|
||||
|
||||
if map.buffer_snapshot.chars_at(to.to_point(map)).next() == Some(target) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{motion::Motion, object::Object, utils::copy_and_flash_selections_content, Vim};
|
||||
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
||||
use collections::HashMap;
|
||||
use gpui::WindowContext;
|
||||
|
||||
@@ -15,7 +15,7 @@ pub fn yank_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut
|
||||
motion.expand_selection(map, selection, times, true, &text_layout_details);
|
||||
});
|
||||
});
|
||||
copy_and_flash_selections_content(editor, motion.linewise(), cx);
|
||||
copy_selections_content(editor, motion.linewise(), cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
||||
@@ -38,7 +38,7 @@ pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowC
|
||||
original_positions.insert(selection.id, original_position);
|
||||
});
|
||||
});
|
||||
copy_and_flash_selections_content(editor, false, cx);
|
||||
copy_selections_content(editor, false, cx);
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.move_with(|_, selection| {
|
||||
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
||||
|
||||
@@ -169,6 +169,7 @@ impl EditorState {
|
||||
|
||||
pub fn keymap_context_layer(&self) -> KeyContext {
|
||||
let mut context = KeyContext::default();
|
||||
context.add("VimEnabled");
|
||||
context.set(
|
||||
"vim_mode",
|
||||
match self.mode {
|
||||
|
||||
@@ -49,9 +49,7 @@ impl VimTestContext {
|
||||
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
|
||||
});
|
||||
settings::KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
|
||||
if enabled {
|
||||
settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
|
||||
}
|
||||
settings::KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
|
||||
});
|
||||
|
||||
// Setup search toolbars and keypress hook
|
||||
|
||||
@@ -1,34 +1,12 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use editor::{ClipboardSelection, Editor};
|
||||
use gpui::{ClipboardItem, ViewContext};
|
||||
use gpui::{AppContext, ClipboardItem};
|
||||
use language::{CharKind, Point};
|
||||
|
||||
pub struct HighlightOnYank;
|
||||
|
||||
pub fn copy_and_flash_selections_content(
|
||||
editor: &mut Editor,
|
||||
linewise: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
copy_selections_content_internal(editor, linewise, true, cx);
|
||||
}
|
||||
|
||||
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut ViewContext<Editor>) {
|
||||
copy_selections_content_internal(editor, linewise, false, cx);
|
||||
}
|
||||
|
||||
fn copy_selections_content_internal(
|
||||
editor: &mut Editor,
|
||||
linewise: bool,
|
||||
highlight: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let mut text = String::new();
|
||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||
let mut ranges_to_highlight = Vec::new();
|
||||
{
|
||||
let mut is_first = true;
|
||||
for selection in selections.iter() {
|
||||
@@ -54,11 +32,6 @@ fn copy_selections_content_internal(
|
||||
if is_last_line {
|
||||
start = Point::new(start.row + 1, 0);
|
||||
}
|
||||
|
||||
let start_anchor = buffer.anchor_after(start);
|
||||
let end_anchor = buffer.anchor_before(end);
|
||||
ranges_to_highlight.push(start_anchor..end_anchor);
|
||||
|
||||
for chunk in buffer.text_for_range(start..end) {
|
||||
text.push_str(chunk);
|
||||
}
|
||||
@@ -74,25 +47,6 @@ fn copy_selections_content_internal(
|
||||
}
|
||||
|
||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
if !highlight {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.highlight_background::<HighlightOnYank>(
|
||||
ranges_to_highlight,
|
||||
|colors| colors.editor_document_highlight_read_background,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await;
|
||||
this.update(&mut cx, |editor, cx| {
|
||||
editor.clear_background_highlights::<HighlightOnYank>(cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> CharKind {
|
||||
|
||||
@@ -20,8 +20,8 @@ use command_palette::CommandPaletteInterceptor;
|
||||
use copilot::CommandPaletteFilter;
|
||||
use editor::{movement, Editor, EditorEvent, EditorMode};
|
||||
use gpui::{
|
||||
actions, impl_actions, Action, AppContext, EntityId, Global, Subscription, View, ViewContext,
|
||||
WeakView, WindowContext,
|
||||
actions, impl_actions, Action, AppContext, EntityId, Global, KeyContext, Subscription, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{CursorShape, Point, Selection, SelectionGoal};
|
||||
pub use mode_indicator::ModeIndicator;
|
||||
@@ -197,11 +197,7 @@ impl Vim {
|
||||
cx.update_global(update)
|
||||
}
|
||||
|
||||
fn activate_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
|
||||
if editor.read(cx).mode() != EditorMode::Full {
|
||||
return;
|
||||
}
|
||||
|
||||
fn set_active_editor(&mut self, editor: View<Editor>, cx: &mut WindowContext) {
|
||||
self.active_editor = Some(editor.clone().downgrade());
|
||||
self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
|
||||
EditorEvent::SelectionsChanged { local: true } => {
|
||||
@@ -223,17 +219,19 @@ impl Vim {
|
||||
_ => {}
|
||||
}));
|
||||
|
||||
let editor = editor.read(cx);
|
||||
let editor_mode = editor.mode();
|
||||
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||
if self.enabled {
|
||||
let editor = editor.read(cx);
|
||||
let editor_mode = editor.mode();
|
||||
let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
|
||||
|
||||
if editor_mode == EditorMode::Full
|
||||
if editor_mode == EditorMode::Full
|
||||
&& !newest_selection_empty
|
||||
&& self.state().mode == Mode::Normal
|
||||
// When following someone, don't switch vim mode.
|
||||
&& editor.leader_peer_id().is_none()
|
||||
{
|
||||
self.switch_mode(Mode::Visual, true, cx);
|
||||
{
|
||||
self.switch_mode(Mode::Visual, true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
self.sync_vim_settings(cx);
|
||||
@@ -506,39 +504,43 @@ impl Vim {
|
||||
}
|
||||
|
||||
fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
|
||||
if self.enabled == enabled {
|
||||
return;
|
||||
}
|
||||
if !enabled {
|
||||
let _ = cx.remove_global::<CommandPaletteInterceptor>();
|
||||
if self.enabled != enabled {
|
||||
self.enabled = enabled;
|
||||
|
||||
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
|
||||
filter.hidden_namespaces.insert("vim");
|
||||
if self.enabled {
|
||||
filter.hidden_namespaces.remove("vim");
|
||||
} else {
|
||||
filter.hidden_namespaces.insert("vim");
|
||||
}
|
||||
});
|
||||
*self = Default::default();
|
||||
return;
|
||||
}
|
||||
|
||||
self.enabled = true;
|
||||
cx.update_global::<CommandPaletteFilter, _>(|filter, _| {
|
||||
filter.hidden_namespaces.remove("vim");
|
||||
});
|
||||
cx.set_global::<CommandPaletteInterceptor>(CommandPaletteInterceptor(Box::new(
|
||||
command::command_interceptor,
|
||||
)));
|
||||
if self.enabled {
|
||||
cx.set_global::<CommandPaletteInterceptor>(CommandPaletteInterceptor(Box::new(
|
||||
command::command_interceptor,
|
||||
)));
|
||||
} else if cx.has_global::<CommandPaletteInterceptor>() {
|
||||
let _ = cx.remove_global::<CommandPaletteInterceptor>();
|
||||
}
|
||||
|
||||
if let Some(active_window) = cx
|
||||
.active_window()
|
||||
.and_then(|window| window.downcast::<Workspace>())
|
||||
{
|
||||
active_window
|
||||
.update(cx, |workspace, cx| {
|
||||
let active_editor = workspace.active_item_as::<Editor>(cx);
|
||||
if let Some(active_editor) = active_editor {
|
||||
self.activate_editor(active_editor, cx);
|
||||
self.switch_mode(Mode::Normal, false, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
if let Some(active_window) = cx.active_window() {
|
||||
active_window
|
||||
.update(cx, |root_view, cx| {
|
||||
if self.enabled {
|
||||
let active_editor = root_view
|
||||
.downcast::<Workspace>()
|
||||
.ok()
|
||||
.and_then(|workspace| workspace.read(cx).active_item(cx))
|
||||
.and_then(|item| item.downcast::<Editor>());
|
||||
if let Some(active_editor) = active_editor {
|
||||
self.set_active_editor(active_editor, cx);
|
||||
}
|
||||
self.switch_mode(Mode::Normal, false, cx);
|
||||
}
|
||||
self.sync_vim_settings(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,29 +569,45 @@ impl Vim {
|
||||
|
||||
fn sync_vim_settings(&self, cx: &mut WindowContext) {
|
||||
let state = self.state();
|
||||
let cursor_shape = state.cursor_shape();
|
||||
|
||||
self.update_active_editor(cx, |editor, cx| {
|
||||
editor.set_cursor_shape(state.cursor_shape(), cx);
|
||||
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
|
||||
editor.set_collapse_matches(true);
|
||||
editor.set_input_enabled(!state.vim_controlled());
|
||||
editor.set_autoindent(state.should_autoindent());
|
||||
editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
|
||||
let context_layer = state.keymap_context_layer();
|
||||
editor.set_keymap_context_layer::<Self>(context_layer, cx);
|
||||
if self.enabled && editor.mode() == EditorMode::Full {
|
||||
editor.set_cursor_shape(cursor_shape, cx);
|
||||
editor.set_clip_at_line_ends(state.clip_at_line_ends(), cx);
|
||||
editor.set_collapse_matches(true);
|
||||
editor.set_input_enabled(!state.vim_controlled());
|
||||
editor.set_autoindent(state.should_autoindent());
|
||||
editor.selections.line_mode = matches!(state.mode, Mode::VisualLine);
|
||||
let context_layer = state.keymap_context_layer();
|
||||
editor.set_keymap_context_layer::<Self>(context_layer, cx);
|
||||
} else {
|
||||
// Note: set_collapse_matches is not in unhook_vim_settings, as that method is called on blur,
|
||||
// but we need collapse_matches to persist when the search bar is focused.
|
||||
editor.set_collapse_matches(false);
|
||||
self.unhook_vim_settings(editor, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
if editor.mode() == EditorMode::Full {
|
||||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.set_collapse_matches(false);
|
||||
editor.set_input_enabled(true);
|
||||
editor.set_autoindent(true);
|
||||
editor.selections.line_mode = false;
|
||||
fn unhook_vim_settings(&self, editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
editor.set_cursor_shape(CursorShape::Bar, cx);
|
||||
editor.set_clip_at_line_ends(false, cx);
|
||||
editor.set_input_enabled(true);
|
||||
editor.set_autoindent(true);
|
||||
editor.selections.line_mode = false;
|
||||
|
||||
// we set the VimEnabled context on all editors so that we
|
||||
// can distinguish between vim mode and non-vim mode in the BufferSearchBar.
|
||||
// This is a bit of a hack, but currently the search crate does not depend on vim,
|
||||
// and it seems nice to keep it that way.
|
||||
if self.enabled {
|
||||
let mut context = KeyContext::default();
|
||||
context.add("VimEnabled");
|
||||
editor.set_keymap_context_layer::<Self>(context, cx)
|
||||
} else {
|
||||
editor.remove_keymap_context_layer::<Self>(cx);
|
||||
}
|
||||
editor.remove_keymap_context_layer::<Self>(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,17 +633,19 @@ fn local_selections_changed(
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
if vim.state().mode == Mode::Normal && !newest.is_empty() {
|
||||
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
|
||||
vim.switch_mode(Mode::VisualBlock, false, cx);
|
||||
} else {
|
||||
vim.switch_mode(Mode::Visual, false, cx)
|
||||
if vim.enabled {
|
||||
if vim.state().mode == Mode::Normal && !newest.is_empty() {
|
||||
if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) {
|
||||
vim.switch_mode(Mode::VisualBlock, false, cx);
|
||||
} else {
|
||||
vim.switch_mode(Mode::Visual, false, cx)
|
||||
}
|
||||
} else if newest.is_empty()
|
||||
&& !is_multicursor
|
||||
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode)
|
||||
{
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
} else if newest.is_empty()
|
||||
&& !is_multicursor
|
||||
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&vim.state().mode)
|
||||
{
|
||||
vim.switch_mode(Mode::Normal, true, cx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ mod element {
|
||||
bounds: handle_bounds,
|
||||
stacking_order: cx.stacking_order().clone(),
|
||||
};
|
||||
if interactive_handle_bounds.did_visibly_contains(&cx.mouse_position(), cx) {
|
||||
if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
|
||||
cx.set_cursor_style(match axis {
|
||||
Axis::Vertical => CursorStyle::ResizeUpDown,
|
||||
Axis::Horizontal => CursorStyle::ResizeLeftRight,
|
||||
|
||||
@@ -571,27 +571,6 @@ impl Workspace {
|
||||
cx.new_view(|_| MessageNotification::new(message.clone()))
|
||||
}),
|
||||
|
||||
project::Event::LanguageServerPrompt(request) => {
|
||||
let request = request.clone();
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let messages = request
|
||||
.actions
|
||||
.iter()
|
||||
.map(|action| action.title.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let index = cx
|
||||
.update(|cx| {
|
||||
cx.prompt(request.level, "", Some(&request.message), &messages)
|
||||
})?
|
||||
.await?;
|
||||
request.respond(index).await;
|
||||
|
||||
Result::<(), anyhow::Error>::Ok(())
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
cx.notify()
|
||||
|
||||
@@ -108,12 +108,10 @@ theme_selector.workspace = true
|
||||
thiserror.workspace = true
|
||||
tiny_http = "0.8"
|
||||
toml.workspace = true
|
||||
tree-sitter-astro.workspace = true
|
||||
tree-sitter-bash.workspace = true
|
||||
tree-sitter-beancount.workspace = true
|
||||
tree-sitter-c-sharp.workspace = true
|
||||
tree-sitter-c.workspace = true
|
||||
tree-sitter-clojure.workspace = true
|
||||
tree-sitter-cpp.workspace = true
|
||||
tree-sitter-css.workspace = true
|
||||
tree-sitter-elixir.workspace = true
|
||||
@@ -137,7 +135,6 @@ tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-ocaml.workspace = true
|
||||
tree-sitter-php.workspace = true
|
||||
tree-sitter-prisma-io.workspace = true
|
||||
tree-sitter-proto.workspace = true
|
||||
tree-sitter-purescript.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
|
||||
@@ -9,9 +9,7 @@ use util::asset_str;
|
||||
|
||||
use self::{deno::DenoSettings, elixir::ElixirSettings};
|
||||
|
||||
mod astro;
|
||||
mod c;
|
||||
mod clojure;
|
||||
mod csharp;
|
||||
mod css;
|
||||
mod deno;
|
||||
@@ -29,7 +27,6 @@ mod lua;
|
||||
mod nu;
|
||||
mod ocaml;
|
||||
mod php;
|
||||
mod prisma;
|
||||
mod purescript;
|
||||
mod python;
|
||||
mod ruby;
|
||||
@@ -66,12 +63,10 @@ pub fn init(
|
||||
DenoSettings::register(cx);
|
||||
|
||||
languages.register_native_grammars([
|
||||
("astro", tree_sitter_astro::language()),
|
||||
("bash", tree_sitter_bash::language()),
|
||||
("beancount", tree_sitter_beancount::language()),
|
||||
("c", tree_sitter_c::language()),
|
||||
("c_sharp", tree_sitter_c_sharp::language()),
|
||||
("clojure", tree_sitter_clojure::language()),
|
||||
("cpp", tree_sitter_cpp::language()),
|
||||
("css", tree_sitter_css::language()),
|
||||
("elixir", tree_sitter_elixir::language()),
|
||||
@@ -102,7 +97,6 @@ pub fn init(
|
||||
tree_sitter_ocaml::language_ocaml_interface(),
|
||||
),
|
||||
("php", tree_sitter_php::language_php()),
|
||||
("prisma", tree_sitter_prisma_io::language()),
|
||||
("proto", tree_sitter_proto::language()),
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
("purescript", tree_sitter_purescript::language()),
|
||||
@@ -132,17 +126,9 @@ pub fn init(
|
||||
)
|
||||
};
|
||||
|
||||
language(
|
||||
"astro",
|
||||
vec![
|
||||
Arc::new(astro::AstroLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
);
|
||||
language("bash", vec![]);
|
||||
language("beancount", vec![]);
|
||||
language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
|
||||
language("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]);
|
||||
language("cpp", vec![Arc::new(c::CLspAdapter)]);
|
||||
language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
|
||||
language(
|
||||
@@ -304,21 +290,12 @@ pub fn init(
|
||||
language("nu", vec![Arc::new(nu::NuLanguageServer {})]);
|
||||
language("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]);
|
||||
language("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]);
|
||||
language(
|
||||
"vue",
|
||||
vec![Arc::new(vue::VueLspAdapter::new(node_runtime.clone()))],
|
||||
);
|
||||
language("vue", vec![Arc::new(vue::VueLspAdapter::new(node_runtime))]);
|
||||
language("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]);
|
||||
language("proto", vec![]);
|
||||
language("terraform", vec![]);
|
||||
language("terraform-vars", vec![]);
|
||||
language("hcl", vec![]);
|
||||
language(
|
||||
"prisma",
|
||||
vec![Arc::new(prisma::PrismaLspAdapter::new(
|
||||
node_runtime.clone(),
|
||||
))],
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use serde_json::json;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const SERVER_PATH: &'static str = "node_modules/@astrojs/language-server/bin/nodeServer.js";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct AstroLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl AstroLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
AstroLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for AstroLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("astro-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"astro"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("@astrojs/language-server")
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[("@astrojs/language-server", version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
"provideFormatter": true,
|
||||
"typescript": {
|
||||
"tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["prettier-plugin-astro"]
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
(|| async move {
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
@@ -1,22 +0,0 @@
|
||||
name = "Astro"
|
||||
grammar = "astro"
|
||||
path_suffixes = ["astro"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "<", end = ">", close = false, newline = true, not_in = ["string", "comment"] },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "`", end = "`", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
word_characters = ["#", "$", "-"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "astro"
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
opt_into_language_servers = ["tailwindcss-language-server"]
|
||||
@@ -1,25 +0,0 @@
|
||||
(tag_name) @tag
|
||||
(erroneous_end_tag_name) @keyword
|
||||
(doctype) @constant
|
||||
(attribute_name) @property
|
||||
(attribute_value) @string
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(attribute_value)
|
||||
(quoted_attribute_value)
|
||||
] @string
|
||||
|
||||
"=" @operator
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"<"
|
||||
">"
|
||||
"</"
|
||||
"/>"
|
||||
] @tag.delimiter
|
||||
@@ -1,16 +0,0 @@
|
||||
; inherits: html_tags
|
||||
(frontmatter
|
||||
(raw_text) @content
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
(interpolation
|
||||
(raw_text) @content
|
||||
(#set! "language" "tsx"))
|
||||
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
(style_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "css"))
|
||||
@@ -1,136 +0,0 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
pub use language::*;
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, env::consts, path::PathBuf};
|
||||
use util::{
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ClojureLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl super::LspAdapter for ClojureLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("clojure-lsp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"clojure"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release(
|
||||
"clojure-lsp/clojure-lsp",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let platform = match consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "aarch64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let asset_name = format!("clojure-lsp-native-macos-{platform}.zip");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.tag_name.clone(),
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
let zip_path = container_dir.join(format!("clojure-lsp_{}.zip", version.name));
|
||||
let folder_path = container_dir.join("bin");
|
||||
let binary_path = folder_path.join("clojure-lsp");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.context("error downloading release")?;
|
||||
let mut file = File::create(&zip_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
|
||||
if !response.status().is_success() {
|
||||
return Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
|
||||
fs::create_dir_all(&folder_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
|
||||
|
||||
let unzip_status = smol::process::Command::new("unzip")
|
||||
.arg(&zip_path)
|
||||
.arg("-d")
|
||||
.arg(&folder_path)
|
||||
.output()
|
||||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
return Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != folder_path).await;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||
if binary_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||
if binary_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
arguments: vec!["--version".into()],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
@@ -1,12 +0,0 @@
|
||||
name = "Clojure"
|
||||
grammar = "clojure"
|
||||
path_suffixes = ["clj", "cljs"]
|
||||
line_comments = [";; "]
|
||||
autoclose_before = "}])"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
word_characters = ["-"]
|
||||
@@ -1,41 +0,0 @@
|
||||
;; Literals
|
||||
|
||||
(num_lit) @number
|
||||
|
||||
[
|
||||
(char_lit)
|
||||
(str_lit)
|
||||
] @string
|
||||
|
||||
[
|
||||
(bool_lit)
|
||||
(nil_lit)
|
||||
] @constant.builtin
|
||||
|
||||
(kwd_lit) @constant
|
||||
|
||||
;; Comments
|
||||
|
||||
(comment) @comment
|
||||
|
||||
;; Treat quasiquotation as operators for the purpose of highlighting.
|
||||
|
||||
[
|
||||
"'"
|
||||
"`"
|
||||
"~"
|
||||
"@"
|
||||
"~@"
|
||||
] @operator
|
||||
|
||||
|
||||
(list_lit
|
||||
.
|
||||
(sym_lit) @function)
|
||||
|
||||
(list_lit
|
||||
.
|
||||
(sym_lit) @keyword
|
||||
(#match? @keyword
|
||||
"^(do|if|let|var|fn|fn*|loop*|recur|throw|try|catch|finally|set!|new|quote|->|->>)$"
|
||||
))
|
||||
@@ -1,3 +0,0 @@
|
||||
(_ "[" "]") @indent
|
||||
(_ "{" "}") @indent
|
||||
(_ "(" ")") @indent
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -214,14 +214,4 @@
|
||||
"type"
|
||||
"readonly"
|
||||
"override"
|
||||
] @keyword
|
||||
|
||||
; JSX elements
|
||||
(jsx_opening_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
(jsx_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
(jsx_self_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
|
||||
(jsx_attribute (property_identifier) @attribute)
|
||||
(jsx_opening_element (["<" ">"]) @punctuation.bracket)
|
||||
(jsx_closing_element (["<" "/" ">"]) @punctuation.bracket)
|
||||
(jsx_self_closing_element (["<" "/" ">"]) @punctuation.bracket)
|
||||
] @keyword
|
||||
@@ -1,126 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &'static str = "node_modules/.bin/prisma-language-server";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct PrismaLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl PrismaLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
PrismaLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for PrismaLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("prisma-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"prisma-language-server"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("@prisma/language-server")
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
if fs::metadata(&server_path).await.is_err() {
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[("@prisma/language-server", version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last_version_dir = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_dir() {
|
||||
last_version_dir = Some(entry.path());
|
||||
}
|
||||
}
|
||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
||||
let server_path = last_version_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Ok(LanguageServerBinary {
|
||||
path: node.binary_path().await?,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
name = "Prisma"
|
||||
grammar = "prisma"
|
||||
path_suffixes = ["prisma"]
|
||||
line_comments = ["// "]
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true }
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
[
|
||||
"datasource"
|
||||
"enum"
|
||||
"generator"
|
||||
"model"
|
||||
] @keyword
|
||||
|
||||
(comment) @comment
|
||||
(developer_comment) @comment
|
||||
|
||||
(arguments) @property
|
||||
(attribute) @function
|
||||
(call_expression) @function
|
||||
(column_type) @type
|
||||
(enumeral) @constant
|
||||
(identifier) @variable
|
||||
(string) @string
|
||||
|
||||
"(" @punctuation.bracket
|
||||
")" @punctuation.bracket
|
||||
"[" @punctuation.bracket
|
||||
"]" @punctuation.bracket
|
||||
"{" @punctuation.bracket
|
||||
"}" @punctuation.bracket
|
||||
"=" @operator
|
||||
"@" @operator
|
||||
@@ -114,7 +114,6 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
HashMap::from_iter([
|
||||
("Astro".to_string(), "astro".to_string()),
|
||||
("HTML".to_string(), "html".to_string()),
|
||||
("CSS".to_string(), "css".to_string()),
|
||||
("JavaScript".to_string(), "javascript".to_string()),
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
; Variables
|
||||
|
||||
(identifier) @variable
|
||||
|
||||
; Properties
|
||||
|
||||
(property_identifier) @property
|
||||
|
||||
; Function and method calls
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @function)
|
||||
|
||||
(call_expression
|
||||
function: (member_expression
|
||||
property: (property_identifier) @function.method))
|
||||
|
||||
; Function and method definitions
|
||||
|
||||
(function
|
||||
name: (identifier) @function)
|
||||
(function_declaration
|
||||
name: (identifier) @function)
|
||||
(method_definition
|
||||
name: (property_identifier) @function.method)
|
||||
|
||||
(pair
|
||||
key: (property_identifier) @function.method
|
||||
value: [(function) (arrow_function)])
|
||||
|
||||
(assignment_expression
|
||||
left: (member_expression
|
||||
property: (property_identifier) @function.method)
|
||||
right: [(function) (arrow_function)])
|
||||
|
||||
(variable_declarator
|
||||
name: (identifier) @function
|
||||
value: [(function) (arrow_function)])
|
||||
|
||||
(assignment_expression
|
||||
left: (identifier) @function
|
||||
right: [(function) (arrow_function)])
|
||||
|
||||
; Special identifiers
|
||||
|
||||
((identifier) @constructor
|
||||
(#match? @constructor "^[A-Z]"))
|
||||
|
||||
((identifier) @type
|
||||
(#match? @type "^[A-Z]"))
|
||||
(type_identifier) @type
|
||||
(predefined_type) @type.builtin
|
||||
|
||||
([
|
||||
(identifier)
|
||||
(shorthand_property_identifier)
|
||||
(shorthand_property_identifier_pattern)
|
||||
] @constant
|
||||
(#match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
|
||||
|
||||
; Literals
|
||||
|
||||
(this) @variable.special
|
||||
(super) @variable.special
|
||||
|
||||
[
|
||||
(null)
|
||||
(undefined)
|
||||
] @constant.builtin
|
||||
|
||||
[
|
||||
(true)
|
||||
(false)
|
||||
] @boolean
|
||||
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(string)
|
||||
(template_string)
|
||||
] @string
|
||||
|
||||
(regex) @string.regex
|
||||
(number) @number
|
||||
|
||||
; Tokens
|
||||
|
||||
[
|
||||
";"
|
||||
"?."
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"-"
|
||||
"--"
|
||||
"-="
|
||||
"+"
|
||||
"++"
|
||||
"+="
|
||||
"*"
|
||||
"*="
|
||||
"**"
|
||||
"**="
|
||||
"/"
|
||||
"/="
|
||||
"%"
|
||||
"%="
|
||||
"<"
|
||||
"<="
|
||||
"<<"
|
||||
"<<="
|
||||
"="
|
||||
"=="
|
||||
"==="
|
||||
"!"
|
||||
"!="
|
||||
"!=="
|
||||
"=>"
|
||||
">"
|
||||
">="
|
||||
">>"
|
||||
">>="
|
||||
">>>"
|
||||
">>>="
|
||||
"~"
|
||||
"^"
|
||||
"&"
|
||||
"|"
|
||||
"^="
|
||||
"&="
|
||||
"|="
|
||||
"&&"
|
||||
"||"
|
||||
"??"
|
||||
"&&="
|
||||
"||="
|
||||
"??="
|
||||
] @operator
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"as"
|
||||
"async"
|
||||
"await"
|
||||
"break"
|
||||
"case"
|
||||
"catch"
|
||||
"class"
|
||||
"const"
|
||||
"continue"
|
||||
"debugger"
|
||||
"default"
|
||||
"delete"
|
||||
"do"
|
||||
"else"
|
||||
"export"
|
||||
"extends"
|
||||
"finally"
|
||||
"for"
|
||||
"from"
|
||||
"function"
|
||||
"get"
|
||||
"if"
|
||||
"import"
|
||||
"in"
|
||||
"instanceof"
|
||||
"let"
|
||||
"new"
|
||||
"of"
|
||||
"return"
|
||||
"satisfies"
|
||||
"set"
|
||||
"static"
|
||||
"switch"
|
||||
"target"
|
||||
"throw"
|
||||
"try"
|
||||
"typeof"
|
||||
"var"
|
||||
"void"
|
||||
"while"
|
||||
"with"
|
||||
"yield"
|
||||
] @keyword
|
||||
|
||||
(template_substitution
|
||||
"${" @punctuation.special
|
||||
"}" @punctuation.special) @embedded
|
||||
|
||||
(type_arguments
|
||||
"<" @punctuation.bracket
|
||||
">" @punctuation.bracket)
|
||||
|
||||
; Keywords
|
||||
|
||||
[ "abstract"
|
||||
"declare"
|
||||
"enum"
|
||||
"export"
|
||||
"implements"
|
||||
"interface"
|
||||
"keyof"
|
||||
"namespace"
|
||||
"private"
|
||||
"protected"
|
||||
"public"
|
||||
"type"
|
||||
"readonly"
|
||||
"override"
|
||||
] @keyword
|
||||
|
||||
; JSX elements
|
||||
(jsx_opening_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
(jsx_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
(jsx_self_closing_element (identifier) @tag (#match? @tag "^[a-z][^.]*$"))
|
||||
|
||||
(jsx_attribute (property_identifier) @attribute)
|
||||
(jsx_opening_element (["<" ">"]) @punctuation.bracket)
|
||||
(jsx_closing_element (["<" "/" ">"]) @punctuation.bracket)
|
||||
(jsx_self_closing_element (["<" "/" ">"]) @punctuation.bracket)
|
||||
1
crates/zed/src/languages/tsx/highlights.scm
Symbolic link
1
crates/zed/src/languages/tsx/highlights.scm
Symbolic link
@@ -0,0 +1 @@
|
||||
../typescript/highlights.scm
|
||||
@@ -11,13 +11,15 @@ use gpui::{AppContext, AsyncAppContext, Global};
|
||||
use itertools::Itertools;
|
||||
use language::{Bias, Point};
|
||||
use release_channel::parse_zed_link;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::prelude::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{path::PathBuf, sync::atomic::AtomicBool};
|
||||
use util::paths::{PathExt, PathLikeWithPosition};
|
||||
use util::paths::PathLikeWithPosition;
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
@@ -127,9 +129,9 @@ impl OpenListener {
|
||||
let paths: Vec<_> = urls
|
||||
.iter()
|
||||
.flat_map(|url| url.strip_prefix("file://"))
|
||||
.flat_map(|url| {
|
||||
.map(|url| {
|
||||
let decoded = urlencoding::decode_binary(url.as_bytes());
|
||||
PathBuf::try_from_bytes(decoded.as_ref()).log_err()
|
||||
PathBuf::from(OsStr::from_bytes(decoded.as_ref()))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ use util::{
|
||||
ResultExt,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use vim::VimModeSetting;
|
||||
use welcome::BaseKeymap;
|
||||
use workspace::Pane;
|
||||
use workspace::{
|
||||
@@ -496,17 +495,13 @@ pub fn handle_keymap_file_changes(
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
BaseKeymap::register(cx);
|
||||
VimModeSetting::register(cx);
|
||||
|
||||
let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded();
|
||||
let mut old_base_keymap = *BaseKeymap::get_global(cx);
|
||||
let mut old_vim_enabled = VimModeSetting::get_global(cx).0;
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let new_base_keymap = *BaseKeymap::get_global(cx);
|
||||
let new_vim_enabled = VimModeSetting::get_global(cx).0;
|
||||
if new_base_keymap != old_base_keymap || new_vim_enabled != old_vim_enabled {
|
||||
if new_base_keymap != old_base_keymap {
|
||||
old_base_keymap = new_base_keymap.clone();
|
||||
old_vim_enabled = new_vim_enabled;
|
||||
base_keymap_tx.unbounded_send(()).unwrap();
|
||||
}
|
||||
})
|
||||
@@ -543,9 +538,8 @@ fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
|
||||
}
|
||||
|
||||
pub fn load_default_keymap(cx: &mut AppContext) {
|
||||
KeymapFile::load_asset("keymaps/default.json", cx).unwrap();
|
||||
if VimModeSetting::get_global(cx).0 {
|
||||
KeymapFile::load_asset("keymaps/vim.json", cx).unwrap();
|
||||
for path in ["keymaps/default.json", "keymaps/vim.json"] {
|
||||
KeymapFile::load_asset(path, cx).unwrap();
|
||||
}
|
||||
|
||||
if let Some(asset_path) = BaseKeymap::get_global(cx).asset_path() {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Astro
|
||||
|
||||
- Tree Sitter: [tree-sitter-astro](https://github.com/virchau13/tree-sitter-astro)
|
||||
- Language Server: [astro](https://github.com/withastro/language-tools/tree/main/packages/language-server)
|
||||
@@ -1,4 +0,0 @@
|
||||
# Clojure
|
||||
|
||||
- Tree Sitter: [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure)
|
||||
- Language Server: [clojure-lsp](https://github.com/clojure-lsp/clojure-lsp)
|
||||
@@ -1,4 +0,0 @@
|
||||
# Prisma
|
||||
|
||||
- Tree Sitter: [tree-sitter-prisma](https://github.com/victorhqc/tree-sitter-prisma)
|
||||
- Language Server: [prisma-language-server](https://github.com/prisma/language-tools/tree/main/packages/language-server)
|
||||
@@ -12,7 +12,7 @@ bundle_name=""
|
||||
zed_crate="zed"
|
||||
binary_name="Zed"
|
||||
|
||||
# This must match the team in the provisioning profile.
|
||||
# This must match the team in the provsiioning profile.
|
||||
APPLE_NOTORIZATION_TEAM="MQ55VZLNZQ"
|
||||
|
||||
# Function for displaying help info
|
||||
|
||||
Reference in New Issue
Block a user