Detailed changes
@@ -1,149 +1,148 @@
name: CI
on:
- push:
- branches:
- - main
- - "v[0-9]+.[0-9]+.x"
- tags:
- - "v*"
- pull_request:
- branches:
- - "**"
+ push:
+ branches:
+ - main
+ - "v[0-9]+.[0-9]+.x"
+ tags:
+ - "v*"
+ pull_request:
+ branches:
+ - "**"
concurrency:
- # Allow only one workflow per any non-`main` branch.
- group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
- cancel-in-progress: true
+ # Allow only one workflow per any non-`main` branch.
+ group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.ref_name == 'main' && github.sha || 'anysha' }}
+ cancel-in-progress: true
env:
- CARGO_TERM_COLOR: always
- CARGO_INCREMENTAL: 0
- RUST_BACKTRACE: 1
+ CARGO_TERM_COLOR: always
+ CARGO_INCREMENTAL: 0
+ RUST_BACKTRACE: 1
jobs:
- style:
- name: Check formatting, Clippy lints, and spelling
- runs-on:
- - self-hosted
- - test
- steps:
- - name: Checkout repo
- uses: actions/checkout@v3
- with:
- clean: false
- submodules: "recursive"
- fetch-depth: 0
-
- - name: Set up default .cargo/config.toml
- run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
-
- - name: Check spelling
- run: |
- if ! which typos > /dev/null; then
- cargo install typos-cli
- fi
- typos
-
- - name: Run style checks
- uses: ./.github/actions/check_style
-
- tests:
- name: Run tests
- runs-on:
- - self-hosted
- - test
- needs: style
- steps:
- - name: Checkout repo
- uses: actions/checkout@v3
- with:
- clean: false
- submodules: "recursive"
-
- - name: Run tests
- uses: ./.github/actions/run_tests
-
- - name: Build collab
- run: cargo build -p collab
-
- - name: Build other binaries
- run: cargo build --workspace --bins --all-features
-
- bundle:
- name: Bundle app
- runs-on:
- - self-hosted
- - bundle
- if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
- needs: tests
+ style:
+ name: Check formatting, Clippy lints, and spelling
+ runs-on:
+ - self-hosted
+ - test
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ with:
+ clean: false
+ submodules: "recursive"
+ fetch-depth: 0
+
+ - name: Set up default .cargo/config.toml
+ run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml
+
+ - name: Check spelling
+ run: |
+ if ! which typos > /dev/null; then
+ cargo install typos-cli
+ fi
+ typos
+
+ - name: Run style checks
+ uses: ./.github/actions/check_style
+
+ tests:
+ name: Run tests
+ runs-on:
+ - self-hosted
+ - test
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ with:
+ clean: false
+ submodules: "recursive"
+
+ - name: Run tests
+ uses: ./.github/actions/run_tests
+
+ - name: Build collab
+ run: cargo build -p collab
+
+ - name: Build other binaries
+ run: cargo build --workspace --bins --all-features
+
+ bundle:
+ name: Bundle app
+ runs-on:
+ - self-hosted
+ - bundle
+ if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
+ needs: tests
+ env:
+ MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+ MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+ APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+ APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+ steps:
+ - name: Install Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: "18"
+
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ with:
+ clean: false
+ submodules: "recursive"
+
+ - name: Limit target directory size
+ run: script/clear-target-dir-if-larger-than 100
+
+ - name: Determine version and release channel
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ run: |
+ set -eu
+
+ version=$(script/get-crate-version zed)
+ channel=$(cat crates/zed/RELEASE_CHANNEL)
+ echo "Publishing version: ${version} on release channel ${channel}"
+ echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
+
+ expected_tag_name=""
+ case ${channel} in
+ stable)
+ expected_tag_name="v${version}";;
+ preview)
+ expected_tag_name="v${version}-pre";;
+ nightly)
+ expected_tag_name="v${version}-nightly";;
+ *)
+ echo "can't publish a release on channel ${channel}"
+ exit 1;;
+ esac
+ if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
+ echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
+ exit 1
+ fi
+
+ - name: Generate license file
+ run: script/generate-licenses
+
+ - name: Create app bundle
+ run: script/bundle
+
+ - name: Upload app bundle to workflow run if main branch or specific label
+ uses: actions/upload-artifact@v3
+ if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
+ with:
+ name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
+ path: target/release/Zed.dmg
+
+ - uses: softprops/action-gh-release@v1
+ name: Upload app bundle to release
+ if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
+ with:
+ draft: true
+ prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
+ files: target/release/Zed.dmg
+ body: ""
env:
- MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
- MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
- APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
- APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
- steps:
- - name: Install Node
- uses: actions/setup-node@v3
- with:
- node-version: "18"
-
- - name: Checkout repo
- uses: actions/checkout@v3
- with:
- clean: false
- submodules: "recursive"
-
- - name: Limit target directory size
- run: script/clear-target-dir-if-larger-than 100
-
- - name: Determine version and release channel
- if: ${{ startsWith(github.ref, 'refs/tags/v') }}
- run: |
- set -eu
-
- version=$(script/get-crate-version zed)
- channel=$(cat crates/zed/RELEASE_CHANNEL)
- echo "Publishing version: ${version} on release channel ${channel}"
- echo "RELEASE_CHANNEL=${channel}" >> $GITHUB_ENV
-
- expected_tag_name=""
- case ${channel} in
- stable)
- expected_tag_name="v${version}";;
- preview)
- expected_tag_name="v${version}-pre";;
- nightly)
- expected_tag_name="v${version}-nightly";;
- *)
- echo "can't publish a release on channel ${channel}"
- exit 1;;
- esac
- if [[ $GITHUB_REF_NAME != $expected_tag_name ]]; then
- echo "invalid release tag ${GITHUB_REF_NAME}. expected ${expected_tag_name}"
- exit 1
- fi
-
- - name: Generate license file
- run: script/generate-licenses
-
- - name: Create app bundle
- run: script/bundle
-
- - name: Upload app bundle to workflow run if main branch or specific label
- uses: actions/upload-artifact@v3
- if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-build-dmg') }}
- with:
- name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
- path: target/release/Zed.dmg
-
- - uses: softprops/action-gh-release@v1
- name: Upload app bundle to release
- if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
- with:
- draft: true
- prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
- files: target/release/Zed.dmg
- body: ""
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,69 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "lldb",
- "request": "launch",
- "name": "Debug executable 'Zed'",
- "env": {
- "ZED_SERVER_URL": "http://localhost:8080"
- },
- "cargo": {
- "args": [
- "build",
- "--bin=Zed",
- "--package=zed"
- ],
- "filter": {
- "name": "Zed",
- "kind": "bin"
- }
- },
- "args": [
- "${workspaceFolder}"
- ],
- "cwd": "${workspaceFolder}"
- },
- {
- "type": "lldb",
- "request": "launch",
- "name": "Debug unit tests in executable 'zed'",
- "cargo": {
- "args": [
- "test",
- "--no-run",
- "--bin=zed",
- "--package=zed"
- ],
- "filter": {
- "name": "zed",
- "kind": "bin"
- }
- },
- "args": [],
- "cwd": "${workspaceFolder}"
- },
- {
- "type": "lldb",
- "request": "launch",
- "name": "Debug unit tests in library 'gpui'",
- "cargo": {
- "args": [
- "test",
- "--no-run",
- "--lib",
- "--package=gpui"
- ],
- "filter": {
- "name": "gpui",
- "kind": "lib"
- }
- },
- "args": [],
- "cwd": "${workspaceFolder}"
- }
- ]
-}
@@ -1452,7 +1452,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.37.0"
+version = "0.38.0"
dependencies = [
"anyhow",
"async-trait",
@@ -1548,6 +1548,7 @@ dependencies = [
"log",
"menu",
"notifications",
+ "parking_lot 0.11.2",
"picker",
"postage",
"pretty_assertions",
@@ -6144,6 +6145,7 @@ dependencies = [
"smol",
"sum_tree",
"theme",
+ "ui",
"util",
]
@@ -9079,6 +9081,7 @@ dependencies = [
"nvim-rs",
"parking_lot 0.11.2",
"project",
+ "regex",
"search",
"serde",
"serde_derive",
@@ -183,6 +183,7 @@
"context": "Editor && mode == auto_height",
"bindings": {
"ctrl-enter": "editor::Newline",
+ "shift-enter": "editor::Newline",
"ctrl-shift-enter": "editor::NewlineBelow"
}
},
@@ -349,7 +350,8 @@
"alt-cmd-]": "editor::UnfoldLines",
"ctrl-space": "editor::ShowCompletions",
"cmd-.": "editor::ToggleCodeActions",
- "alt-cmd-r": "editor::RevealInFinder"
+ "alt-cmd-r": "editor::RevealInFinder",
+ "ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
{
@@ -104,8 +104,6 @@
"shift-v": "vim::ToggleVisualLine",
"ctrl-v": "vim::ToggleVisualBlock",
"ctrl-q": "vim::ToggleVisualBlock",
- "*": "vim::MoveToNext",
- "#": "vim::MoveToPrev",
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
"ctrl-f": "vim::PageDown",
"pagedown": "vim::PageDown",
@@ -329,6 +327,8 @@
"backwards": true
}
],
+ "*": "vim::MoveToNext",
+ "#": "vim::MoveToPrev",
";": "vim::RepeatFind",
",": [
"vim::RepeatFind",
@@ -421,6 +421,18 @@
"shift-r": "vim::SubstituteLine",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
+ "*": [
+ "vim::MoveToNext",
+ {
+ "partialWord": true
+ }
+ ],
+ "#": [
+ "vim::MoveToPrev",
+ {
+ "partialWord": true
+ }
+ ],
"ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement",
"g ctrl-a": [
@@ -72,6 +72,9 @@
// Whether to use additional LSP queries to format (and amend) the code after
// every "trigger" symbol input, defined by LSP server capabilities.
"use_on_type_format": true,
+ // Whether to automatically type closing characters for you. For example,
+ // when you type (, Zed will automatically add a closing ) at the correct position.
+ "use_autoclose": true,
// Controls whether copilot provides suggestion immediately
// or waits for a `copilot::Toggle`
"show_copilot_suggestions": true,
@@ -134,7 +134,7 @@ pub async fn stream_completion(
line: Result<String, io::Error>,
) -> Result<Option<OpenAIResponseStreamEvent>> {
if let Some(data) = line?.strip_prefix("data: ") {
- let event = serde_json::from_str(&data)?;
+ let event = serde_json::from_str(data)?;
Ok(Some(event))
} else {
Ok(None)
@@ -1172,23 +1172,25 @@ impl Render for AssistantPanel {
.px_2()
.child(Label::new(editor.read(cx).title(cx)).into_element())
}))
- .end_child(if self.focus_handle.contains_focused(cx) {
- h_flex()
- .gap_2()
- .child(h_flex().gap_1().children(self.render_editor_tools(cx)))
- .child(
- ui::Divider::vertical()
- .inset()
- .color(ui::DividerColor::Border),
- )
- .child(
- h_flex()
- .gap_1()
- .child(Self::render_plus_button(cx))
- .child(self.render_zoom_button(cx)),
- )
- } else {
- div()
+ .when(self.focus_handle.contains_focused(cx), |this| {
+ this.end_child(
+ h_flex()
+ .gap_2()
+ .when(self.active_editor().is_some(), |this| {
+ this.child(h_flex().gap_1().children(self.render_editor_tools(cx)))
+ .child(
+ ui::Divider::vertical()
+ .inset()
+ .color(ui::DividerColor::Border),
+ )
+ })
+ .child(
+ h_flex()
+ .gap_1()
+ .child(Self::render_plus_button(cx))
+ .child(self.render_zoom_button(cx)),
+ ),
+ )
});
let contents = if self.active_editor().is_some() {
@@ -96,12 +96,6 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
workspace.register_action(|_, action, cx| {
view_release_notes(action, cx);
});
-
- // @nate - code to trigger update notification on launch
- // todo!("remove this when Nate is done")
- // workspace.show_notification(0, _cx, |cx| {
- // cx.build_view(|_| UpdateNotification::new(SemanticVersion::from_str("1.1.1").unwrap()))
- // });
})
.detach();
@@ -1432,7 +1432,7 @@ impl Room {
let display = displays
.first()
.ok_or_else(|| anyhow!("no display found"))?;
- let track = LocalVideoTrack::screen_share_for_display(&display);
+ let track = LocalVideoTrack::screen_share_for_display(display);
this.upgrade()
.ok_or_else(|| anyhow!("room was dropped"))?
.update(&mut cx, |this, _| {
@@ -1,6 +1,6 @@
use crate::{Channel, ChannelId, ChannelStore};
use anyhow::Result;
-use client::{Client, Collaborator, UserStore};
+use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE};
use collections::HashMap;
use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task};
use language::proto::serialize_version;
@@ -181,6 +181,16 @@ impl ChannelBuffer {
) {
match event {
language::Event::Operation(operation) => {
+ if *ZED_ALWAYS_ACTIVE {
+ match operation {
+ language::Operation::UpdateSelections { selections, .. } => {
+ if selections.is_empty() {
+ return;
+ }
+ }
+ _ => {}
+ }
+ }
let operation = language::proto::serialize_operation(operation);
self.client
.send(proto::UpdateChannelBuffer {
@@ -86,11 +86,11 @@ pub struct ChannelPathsInsertGuard<'a> {
impl<'a> ChannelPathsInsertGuard<'a> {
pub fn note_changed(&mut self, channel_id: ChannelId, epoch: u64, version: &clock::Global) {
- insert_note_changed(&mut self.channels_by_id, channel_id, epoch, &version);
+ insert_note_changed(self.channels_by_id, channel_id, epoch, version);
}
pub fn new_messages(&mut self, channel_id: ChannelId, message_id: u64) {
- insert_new_message(&mut self.channels_by_id, channel_id, message_id)
+ insert_new_message(self.channels_by_id, channel_id, message_id)
}
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
@@ -131,8 +131,8 @@ impl<'a> ChannelPathsInsertGuard<'a> {
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
fn drop(&mut self) {
self.channels_ordered.sort_by(|a, b| {
- let a = channel_path_sorting_key(*a, &self.channels_by_id);
- let b = channel_path_sorting_key(*b, &self.channels_by_id);
+ let a = channel_path_sorting_key(*a, self.channels_by_id);
+ let b = channel_path_sorting_key(*b, self.channels_by_id);
a.cmp(b)
});
self.channels_ordered.dedup();
@@ -167,7 +167,7 @@ fn insert_note_changed(
if epoch > unseen_version.0 {
*unseen_version = (epoch, version.clone());
} else {
- unseen_version.1.join(&version);
+ unseen_version.1.join(version);
}
}
}
@@ -1310,7 +1310,7 @@ impl Client {
drop(state);
if let Some(handler) = handler {
- let future = handler(subscriber, message, &self, cx.clone());
+ let future = handler(subscriber, message, self, cx.clone());
let client_id = self.id();
log::debug!(
"rpc message received. client_id:{}, sender_id:{:?}, type:{}",
@@ -38,8 +38,9 @@ struct TelemetryState {
flush_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>,
is_staff: Option<bool>,
- first_event_datetime: Option<DateTime<Utc>>,
+ first_event_date_time: Option<DateTime<Utc>>,
event_coalescer: EventCoalescer,
+ max_queue_size: usize,
}
const EVENTS_URL_PATH: &'static str = "/api/events";
@@ -69,14 +70,14 @@ struct EventWrapper {
event: Event,
}
-#[derive(Serialize, Debug)]
+#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AssistantKind {
Panel,
Inline,
}
-#[derive(Serialize, Debug)]
+#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(tag = "type")]
pub enum Event {
Editor {
@@ -168,8 +169,9 @@ impl Telemetry {
flush_events_task: None,
log_file: None,
is_staff: None,
- first_event_datetime: None,
+ first_event_date_time: None,
event_coalescer: EventCoalescer::new(),
+ max_queue_size: MAX_QUEUE_LEN,
}));
#[cfg(not(debug_assertions))]
@@ -310,7 +312,7 @@ impl Telemetry {
operation,
copilot_enabled,
copilot_enabled_for_language,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -326,7 +328,7 @@ impl Telemetry {
suggestion_id,
suggestion_accepted,
file_extension,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -342,7 +344,7 @@ impl Telemetry {
conversation_id,
kind,
model,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -358,7 +360,7 @@ impl Telemetry {
operation,
room_id,
channel_id,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -368,7 +370,7 @@ impl Telemetry {
let event = Event::Cpu {
usage_as_percentage,
core_count,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -382,26 +384,36 @@ impl Telemetry {
let event = Event::Memory {
memory_in_bytes,
virtual_memory_in_bytes,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
}
pub fn report_app_event(self: &Arc<Self>, operation: String) {
+ self.report_app_event_with_date_time(operation, Utc::now());
+ }
+
+ fn report_app_event_with_date_time(
+ self: &Arc<Self>,
+ operation: String,
+ date_time: DateTime<Utc>,
+ ) -> Event {
let event = Event::App {
operation,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(date_time),
};
- self.report_event(event)
+ self.report_event(event.clone());
+
+ event
}
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting {
setting,
value,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
@@ -416,7 +428,7 @@ impl Telemetry {
let event = Event::Edit {
duration: end.timestamp_millis() - start.timestamp_millis(),
environment,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event);
@@ -427,22 +439,21 @@ impl Telemetry {
let event = Event::Action {
source,
action,
- milliseconds_since_first_event: self.milliseconds_since_first_event(),
+ milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
};
self.report_event(event)
}
- fn milliseconds_since_first_event(&self) -> i64 {
+ fn milliseconds_since_first_event(self: &Arc<Self>, date_time: DateTime<Utc>) -> i64 {
let mut state = self.state.lock();
- match state.first_event_datetime {
- Some(first_event_datetime) => {
- let now: DateTime<Utc> = Utc::now();
- now.timestamp_millis() - first_event_datetime.timestamp_millis()
+ match state.first_event_date_time {
+ Some(first_event_date_time) => {
+ date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
None => {
- state.first_event_datetime = Some(Utc::now());
+ state.first_event_date_time = Some(date_time);
0
}
}
@@ -468,7 +479,7 @@ impl Telemetry {
state.events_queue.push(EventWrapper { signed_in, event });
if state.installation_id.is_some() {
- if state.events_queue.len() >= MAX_QUEUE_LEN {
+ if state.events_queue.len() >= state.max_queue_size {
drop(state);
self.flush_events();
}
@@ -489,7 +500,7 @@ impl Telemetry {
pub fn flush_events(self: &Arc<Self>) {
let mut state = self.state.lock();
- state.first_event_datetime = None;
+ state.first_event_date_time = None;
let mut events = mem::take(&mut state.events_queue);
state.flush_events_task.take();
drop(state);
@@ -548,3 +559,159 @@ impl Telemetry {
.detach();
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use chrono::TimeZone;
+ use gpui::TestAppContext;
+ use util::http::FakeHttpClient;
+
+ #[gpui::test]
+ fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
+ init_test(cx);
+ let http = FakeHttpClient::with_200_response();
+ let installation_id = Some("installation_id".to_string());
+ let session_id = "session_id".to_string();
+
+ cx.update(|cx| {
+ let telemetry = Telemetry::new(http, cx);
+
+ telemetry.state.lock().max_queue_size = 4;
+ telemetry.start(installation_id, session_id, cx);
+
+ assert!(is_empty_state(&telemetry));
+
+ let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
+ let operation = "test".to_string();
+
+ let event =
+ telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
+ assert_eq!(
+ event,
+ Event::App {
+ operation: operation.clone(),
+ milliseconds_since_first_event: 0
+ }
+ );
+ assert_eq!(telemetry.state.lock().events_queue.len(), 1);
+ assert!(telemetry.state.lock().flush_events_task.is_some());
+ assert_eq!(
+ telemetry.state.lock().first_event_date_time,
+ Some(first_date_time)
+ );
+
+ let mut date_time = first_date_time + chrono::Duration::milliseconds(100);
+
+ let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
+ assert_eq!(
+ event,
+ Event::App {
+ operation: operation.clone(),
+ milliseconds_since_first_event: 100
+ }
+ );
+ assert_eq!(telemetry.state.lock().events_queue.len(), 2);
+ assert!(telemetry.state.lock().flush_events_task.is_some());
+ assert_eq!(
+ telemetry.state.lock().first_event_date_time,
+ Some(first_date_time)
+ );
+
+ date_time += chrono::Duration::milliseconds(100);
+
+ let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
+ assert_eq!(
+ event,
+ Event::App {
+ operation: operation.clone(),
+ milliseconds_since_first_event: 200
+ }
+ );
+ assert_eq!(telemetry.state.lock().events_queue.len(), 3);
+ assert!(telemetry.state.lock().flush_events_task.is_some());
+ assert_eq!(
+ telemetry.state.lock().first_event_date_time,
+ Some(first_date_time)
+ );
+
+ date_time += chrono::Duration::milliseconds(100);
+
+ // Adding a 4th event should cause a flush
+ let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
+ assert_eq!(
+ event,
+ Event::App {
+ operation: operation.clone(),
+ milliseconds_since_first_event: 300
+ }
+ );
+
+ assert!(is_empty_state(&telemetry));
+ });
+ }
+
+ #[gpui::test]
+ async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
+ init_test(cx);
+ let http = FakeHttpClient::with_200_response();
+ let installation_id = Some("installation_id".to_string());
+ let session_id = "session_id".to_string();
+
+ cx.update(|cx| {
+ let telemetry = Telemetry::new(http, cx);
+ telemetry.state.lock().max_queue_size = 4;
+ telemetry.start(installation_id, session_id, cx);
+
+ assert!(is_empty_state(&telemetry));
+
+ let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
+ let operation = "test".to_string();
+
+ let event =
+ telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
+ assert_eq!(
+ event,
+ Event::App {
+ operation: operation.clone(),
+ milliseconds_since_first_event: 0
+ }
+ );
+ assert_eq!(telemetry.state.lock().events_queue.len(), 1);
+ assert!(telemetry.state.lock().flush_events_task.is_some());
+ assert_eq!(
+ telemetry.state.lock().first_event_date_time,
+ Some(first_date_time)
+ );
+
+ let duration = Duration::from_millis(1);
+
+ // Test 1 millisecond before the flush interval limit is met
+ executor.advance_clock(FLUSH_INTERVAL - duration);
+
+ assert!(!is_empty_state(&telemetry));
+
+ // Test the exact moment the flush interval limit is met
+ executor.advance_clock(duration);
+
+ assert!(is_empty_state(&telemetry));
+ });
+ }
+
+ // TODO:
+ // Test settings
+ // Update FakeHTTPClient to keep track of the number of requests and assert on it
+
+ fn init_test(cx: &mut TestAppContext) {
+ cx.update(|cx| {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ });
+ }
+
+ fn is_empty_state(telemetry: &Telemetry) -> bool {
+ telemetry.state.lock().events_queue.is_empty()
+ && telemetry.state.lock().flush_events_task.is_none()
+ && telemetry.state.lock().first_event_date_time.is_none()
+ }
+}
@@ -3,7 +3,10 @@ use anyhow::{anyhow, Context, Result};
use collections::{hash_map::Entry, HashMap, HashSet};
use feature_flags::FeatureFlagAppExt;
use futures::{channel::mpsc, Future, StreamExt};
-use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, SharedUrl, Task};
+use gpui::{
+ AppContext, AsyncAppContext, EventEmitter, Model, ModelContext, SharedString, SharedUrl, Task,
+ WeakModel,
+};
use postage::{sink::Sink, watch};
use rpc::proto::{RequestMessage, UsersResponse};
use std::sync::{Arc, Weak};
@@ -77,6 +80,7 @@ pub struct UserStore {
client: Weak<Client>,
_maintain_contacts: Task<()>,
_maintain_current_user: Task<Result<()>>,
+ weak_self: WeakModel<Self>,
}
#[derive(Clone)]
@@ -194,6 +198,7 @@ impl UserStore {
Ok(())
}),
pending_contact_requests: Default::default(),
+ weak_self: cx.weak_model(),
}
}
@@ -579,6 +584,19 @@ impl UserStore {
self.users.get(&user_id).cloned()
}
+ pub fn get_user_optimistic(
+ &mut self,
+ user_id: u64,
+ cx: &mut ModelContext<Self>,
+ ) -> Option<Arc<User>> {
+ if let Some(user) = self.users.get(&user_id).cloned() {
+ return Some(user);
+ }
+
+ self.get_user(user_id, cx).detach_and_log_err(cx);
+ None
+ }
+
pub fn get_user(
&mut self,
user_id: u64,
@@ -651,6 +669,31 @@ impl UserStore {
pub fn participant_indices(&self) -> &HashMap<u64, ParticipantIndex> {
&self.participant_indices
}
+
+ pub fn participant_names(
+ &self,
+ user_ids: impl Iterator<Item = u64>,
+ cx: &AppContext,
+ ) -> HashMap<u64, SharedString> {
+ let mut ret = HashMap::default();
+ let mut missing_user_ids = Vec::new();
+ for id in user_ids {
+ if let Some(github_login) = self.get_cached_user(id).map(|u| u.github_login.clone()) {
+ ret.insert(id, github_login.into());
+ } else {
+ missing_user_ids.push(id)
+ }
+ }
+ if !missing_user_ids.is_empty() {
+ let this = self.weak_self.clone();
+ cx.spawn(|mut cx| async move {
+ this.update(&mut cx, |this, cx| this.get_users(missing_user_ids, cx))?
+ .await
+ })
+ .detach_and_log_err(cx);
+ }
+ ret
+ }
}
impl User {
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
-version = "0.37.0"
+version = "0.38.0"
publish = false
[[bin]]
@@ -450,8 +450,21 @@ impl Database {
)> {
self.transaction(move |tx| async move {
let channel = self.get_channel_internal(channel_id, &*tx).await?;
- self.check_user_is_channel_member(&channel, user, &*tx)
- .await?;
+
+ let mut requires_write_permission = false;
+ for op in operations.iter() {
+ match op.variant {
+ None | Some(proto::operation::Variant::UpdateSelections(_)) => {}
+ Some(_) => requires_write_permission = true,
+ }
+ }
+ if requires_write_permission {
+ self.check_user_is_channel_member(&channel, user, &*tx)
+ .await?;
+ } else {
+ self.check_user_is_channel_participant(&channel, user, &*tx)
+ .await?;
+ }
let buffer = buffer::Entity::find()
.filter(buffer::Column::ChannelId.eq(channel_id))
@@ -66,7 +66,7 @@ impl Database {
.await
}
- /// Create a notification. If `avoid_duplicates` is set to true, then avoid
+ /// Creates a notification. If `avoid_duplicates` is set to true, then avoid
/// creating a new notification if the given recipient already has an
/// unread notification with the given kind and entity id.
pub async fn create_notification(
@@ -153,7 +153,7 @@ impl Database {
.await
}
- /// Set "connected_once" on the user for analytics.
+ /// Sets "connected_once" on the user for analytics.
pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
self.transaction(|tx| async move {
user::Entity::update_many()
@@ -252,7 +252,7 @@ impl Database {
.await
}
- /// Return the active flags for the user.
+ /// Returns the active flags for the user.
pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
self.transaction(|tx| async move {
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
@@ -950,7 +950,7 @@ async fn ping(_: proto::Ping, response: Response<proto::Ping>, _session: Session
Ok(())
}
-/// Create a new room for calling (outside of channels)
+/// Creates a new room for calling (outside of channels)
async fn create_room(
_request: proto::CreateRoom,
response: Response<proto::CreateRoom>,
@@ -1276,7 +1276,7 @@ async fn leave_room(
Ok(())
}
-/// Update the permissions of someone else in the room.
+/// Updates the permissions of someone else in the room.
async fn set_room_participant_role(
request: proto::SetRoomParticipantRole,
response: Response<proto::SetRoomParticipantRole>,
@@ -1460,7 +1460,7 @@ async fn decline_call(message: proto::DeclineCall, session: Session) -> Result<(
Ok(())
}
-/// Update other participants in the room with your current location.
+/// Updates other participants in the room with your current location.
async fn update_participant_location(
request: proto::UpdateParticipantLocation,
response: Response<proto::UpdateParticipantLocation>,
@@ -1673,7 +1673,7 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
Ok(())
}
-/// Update other participants with changes to the project
+/// Updates other participants with changes to the project
async fn update_project(
request: proto::UpdateProject,
response: Response<proto::UpdateProject>,
@@ -1700,7 +1700,7 @@ async fn update_project(
Ok(())
}
-/// Update other participants with changes to the worktree
+/// Updates other participants with changes to the worktree
async fn update_worktree(
request: proto::UpdateWorktree,
response: Response<proto::UpdateWorktree>,
@@ -1725,7 +1725,7 @@ async fn update_worktree(
Ok(())
}
-/// Update other participants with changes to the diagnostics
+/// Updates other participants with changes to the diagnostics
async fn update_diagnostic_summary(
message: proto::UpdateDiagnosticSummary,
session: Session,
@@ -1749,7 +1749,7 @@ async fn update_diagnostic_summary(
Ok(())
}
-/// Update other participants with changes to the worktree settings
+/// Updates other participants with changes to the worktree settings
async fn update_worktree_settings(
message: proto::UpdateWorktreeSettings,
session: Session,
@@ -2293,7 +2293,7 @@ async fn remove_contact(
Ok(())
}
-/// Create a new channel.
+/// Creates a new channel.
async fn create_channel(
request: proto::CreateChannel,
response: Response<proto::CreateChannel>,
@@ -34,6 +34,7 @@ use std::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
+ time::Duration,
};
use unindent::Unindent as _;
@@ -5945,3 +5946,26 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
});
assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
}
+
+#[gpui::test]
+async fn test_cmd_k_left(cx: &mut TestAppContext) {
+ let client = TestServer::start1(cx).await;
+ let (workspace, cx) = client.build_test_workspace(cx).await;
+
+ cx.simulate_keystrokes("cmd-n");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
+ });
+ cx.simulate_keystrokes("cmd-k left");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
+ });
+ cx.simulate_keystrokes("cmd-k");
+ // sleep for longer than the timeout in keyboard shortcut handling
+ // to verify that it doesn't fire in this case.
+ cx.executor().advance_clock(Duration::from_secs(2));
+ cx.simulate_keystrokes("left");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 3);
+ });
+}
@@ -127,6 +127,11 @@ impl TestServer {
(client_a, client_b, channel_id)
}
+ pub async fn start1<'a>(cx: &'a mut TestAppContext) -> TestClient {
+ let mut server = Self::start(cx.executor().clone()).await;
+ server.create_client(cx, "user_a").await
+ }
+
pub async fn reset(&self) {
self.app_state.db.reset();
let epoch = self
@@ -60,6 +60,7 @@ anyhow.workspace = true
futures.workspace = true
lazy_static.workspace = true
log.workspace = true
+parking_lot.workspace = true
schemars.workspace = true
postage.workspace = true
serde.workspace = true
@@ -442,4 +442,13 @@ impl CollaborationHub for ChannelBufferCollaborationHub {
) -> &'a HashMap<u64, ParticipantIndex> {
self.0.read(cx).user_store().read(cx).participant_indices()
}
+
+ fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
+ let user_ids = self.collaborators(cx).values().map(|c| c.user_id);
+ self.0
+ .read(cx)
+ .user_store()
+ .read(cx)
+ .participant_names(user_ids, cx)
+ }
}
@@ -18,7 +18,7 @@ use project::Fs;
use rich_text::RichText;
use serde::{Deserialize, Serialize};
use settings::Settings;
-use std::sync::Arc;
+use std::{sync::Arc, time::Duration};
use time::{OffsetDateTime, UtcOffset};
use ui::{
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label,
@@ -304,8 +304,11 @@ impl ChatPanel {
let last_message = active_chat.message(ix.saturating_sub(1));
let this_message = active_chat.message(ix).clone();
- let is_continuation_from_previous = last_message.id != this_message.id
- && last_message.sender.id == this_message.sender.id;
+ let duration_since_last_message = this_message.timestamp - last_message.timestamp;
+ let is_continuation_from_previous = last_message.sender.id
+ == this_message.sender.id
+ && last_message.id != this_message.id
+ && duration_since_last_message < Duration::from_secs(5 * 60);
if let ChannelMessageId::Saved(id) = this_message.id {
if this_message
@@ -325,8 +328,6 @@ impl ChatPanel {
Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message)
});
- let now = OffsetDateTime::now_utc();
-
let belongs_to_user = Some(message.sender.id) == self.client.user_id();
let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) =
(message.id, belongs_to_user || is_admin)
@@ -349,23 +350,21 @@ impl ChatPanel {
.when(!is_continuation_from_previous, |this| {
this.pt_3().child(
h_flex()
- .child(
- div().absolute().child(
- Avatar::new(message.sender.avatar_uri.clone())
- .size(cx.rem_size() * 1.5),
- ),
- )
+ .text_ui_sm()
+ .child(div().absolute().child(
+ Avatar::new(message.sender.avatar_uri.clone()).size(cx.rem_size()),
+ ))
.child(
div()
- .pl(cx.rem_size() * 1.5 + px(6.0))
+ .pl(cx.rem_size() + px(6.0))
.pr(px(8.0))
.font_weight(FontWeight::BOLD)
.child(Label::new(message.sender.github_login.clone())),
)
.child(
Label::new(format_timestamp(
+ OffsetDateTime::now_utc(),
message.timestamp,
- now,
self.local_timezone,
))
.size(LabelSize::Small)
@@ -559,6 +558,7 @@ impl Render for ChatPanel {
} else {
this.child(
div()
+ .full()
.p_4()
.child(
Label::new("Select a channel to chat in.")
@@ -596,7 +596,7 @@ impl Render for ChatPanel {
el.child(
div()
.rounded_md()
- .h_7()
+ .h_6()
.w_full()
.bg(cx.theme().colors().editor_background),
)
@@ -670,28 +670,44 @@ impl Panel for ChatPanel {
impl EventEmitter<PanelEvent> for ChatPanel {}
fn format_timestamp(
- mut timestamp: OffsetDateTime,
- mut now: OffsetDateTime,
- local_timezone: UtcOffset,
+ reference: OffsetDateTime,
+ timestamp: OffsetDateTime,
+ timezone: UtcOffset,
) -> String {
- timestamp = timestamp.to_offset(local_timezone);
- now = now.to_offset(local_timezone);
-
- let today = now.date();
- let date = timestamp.date();
- let mut hour = timestamp.hour();
- let mut part = "am";
- if hour > 12 {
- hour -= 12;
- part = "pm";
- }
- if date == today {
- format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
- } else if date.next_day() == Some(today) {
- format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
+ let timestamp_local = timestamp.to_offset(timezone);
+ let timestamp_local_hour = timestamp_local.hour();
+
+ let hour_12 = match timestamp_local_hour {
+ 0 => 12, // Midnight
+ 13..=23 => timestamp_local_hour - 12, // PM hours
+ _ => timestamp_local_hour, // AM hours
+ };
+ let meridiem = if timestamp_local_hour >= 12 {
+ "pm"
} else {
- format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
+ "am"
+ };
+ let timestamp_local_minute = timestamp_local.minute();
+ let formatted_time = format!("{:02}:{:02} {}", hour_12, timestamp_local_minute, meridiem);
+
+ let reference_local = reference.to_offset(timezone);
+ let reference_local_date = reference_local.date();
+ let timestamp_local_date = timestamp_local.date();
+
+ if timestamp_local_date == reference_local_date {
+ return formatted_time;
}
+
+ if reference_local_date.previous_day() == Some(timestamp_local_date) {
+ return format!("yesterday at {}", formatted_time);
+ }
+
+ format!(
+ "{:02}/{:02}/{}",
+ timestamp_local_date.month() as u32,
+ timestamp_local_date.day(),
+ timestamp_local_date.year()
+ )
}
#[cfg(test)]
@@ -700,6 +716,7 @@ mod tests {
use gpui::HighlightStyle;
use pretty_assertions::assert_eq;
use rich_text::Highlight;
+ use time::{Date, OffsetDateTime, Time, UtcOffset};
use util::test::marked_text_ranges;
#[gpui::test]
@@ -748,4 +765,99 @@ mod tests {
]
);
}
+
+ #[test]
+ fn test_format_today() {
+ let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
+ let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "03:30 pm"
+ );
+ }
+
+ #[test]
+ fn test_format_yesterday() {
+ let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
+ let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "yesterday at 09:00 am"
+ );
+ }
+
+ #[test]
+ fn test_format_yesterday_less_than_24_hours_ago() {
+ let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
+ let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "yesterday at 08:00 pm"
+ );
+ }
+
+ #[test]
+ fn test_format_yesterday_more_than_24_hours_ago() {
+ let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
+ let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "yesterday at 06:00 pm"
+ );
+ }
+
+ #[test]
+ fn test_format_yesterday_over_midnight() {
+ let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
+ let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "yesterday at 11:55 pm"
+ );
+ }
+
+ #[test]
+ fn test_format_yesterday_over_month() {
+ let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
+ let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "yesterday at 08:00 pm"
+ );
+ }
+
+ #[test]
+ fn test_format_before_yesterday() {
+ let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
+ let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
+
+ assert_eq!(
+ format_timestamp(reference, timestamp, test_timezone()),
+ "04/10/1990"
+ );
+ }
+
+ fn test_timezone() -> UtcOffset {
+ UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
+ }
+
+ fn create_offset_datetime(
+ year: i32,
+ month: u8,
+ day: u8,
+ hour: u8,
+ minute: u8,
+ second: u8,
+ ) -> OffsetDateTime {
+ let date =
+ Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day).unwrap();
+ let time = Time::from_hms(hour, minute, second).unwrap();
+ date.with_time(time).assume_utc() // Assume UTC for simplicity
+ }
}
@@ -1,19 +1,24 @@
-use std::{sync::Arc, time::Duration};
-
+use anyhow::Result;
use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams};
use client::UserId;
use collections::HashMap;
-use editor::{AnchorRangeExt, Editor, EditorElement, EditorStyle};
+use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
+use fuzzy::StringMatchCandidate;
use gpui::{
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
};
-use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry};
+use language::{
+ language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, Completion,
+ LanguageRegistry, LanguageServerId, ToOffset,
+};
use lazy_static::lazy_static;
+use parking_lot::RwLock;
use project::search::SearchQuery;
use settings::Settings;
+use std::{sync::Arc, time::Duration};
use theme::ThemeSettings;
-use ui::prelude::*;
+use ui::{prelude::*, UiTextSize};
const MENTIONS_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(50);
@@ -31,6 +36,43 @@ pub struct MessageEditor {
channel_id: Option<ChannelId>,
}
+struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
+
+impl CompletionProvider for MessageEditorCompletionProvider {
+ fn completions(
+ &self,
+ buffer: &Model<Buffer>,
+ buffer_position: language::Anchor,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<anyhow::Result<Vec<language::Completion>>> {
+ let Some(handle) = self.0.upgrade() else {
+ return Task::ready(Ok(Vec::new()));
+ };
+ handle.update(cx, |message_editor, cx| {
+ message_editor.completions(buffer, buffer_position, cx)
+ })
+ }
+
+ fn resolve_completions(
+ &self,
+ _completion_indices: Vec<usize>,
+ _completions: Arc<RwLock<Box<[language::Completion]>>>,
+ _cx: &mut ViewContext<Editor>,
+ ) -> Task<anyhow::Result<bool>> {
+ Task::ready(Ok(false))
+ }
+
+ fn apply_additional_edits_for_completion(
+ &self,
+ _buffer: Model<Buffer>,
+ _completion: Completion,
+ _push_to_history: bool,
+ _cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Option<language::Transaction>>> {
+ Task::ready(Ok(None))
+ }
+}
+
impl MessageEditor {
pub fn new(
language_registry: Arc<LanguageRegistry>,
@@ -38,8 +80,11 @@ impl MessageEditor {
editor: View<Editor>,
cx: &mut ViewContext<Self>,
) -> Self {
+ let this = cx.view().downgrade();
editor.update(cx, |editor, cx| {
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
+ editor.set_use_autoclose(false);
+ editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
});
let buffer = editor
@@ -149,6 +194,71 @@ impl MessageEditor {
}
}
+ fn completions(
+ &mut self,
+ buffer: &Model<Buffer>,
+ end_anchor: Anchor,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<Vec<Completion>>> {
+ let end_offset = end_anchor.to_offset(buffer.read(cx));
+
+ let Some(query) = buffer.update(cx, |buffer, _| {
+ let mut query = String::new();
+ for ch in buffer.reversed_chars_at(end_offset).take(100) {
+ if ch == '@' {
+ return Some(query.chars().rev().collect::<String>());
+ }
+ if ch.is_whitespace() || !ch.is_ascii() {
+ break;
+ }
+ query.push(ch);
+ }
+ return None;
+ }) else {
+ return Task::ready(Ok(vec![]));
+ };
+
+ let start_offset = end_offset - query.len();
+ let start_anchor = buffer.read(cx).anchor_before(start_offset);
+
+ let candidates = self
+ .users
+ .keys()
+ .map(|user| StringMatchCandidate {
+ id: 0,
+ string: user.clone(),
+ char_bag: user.chars().collect(),
+ })
+ .collect::<Vec<_>>();
+ cx.spawn(|_, cx| async move {
+ let matches = fuzzy::match_strings(
+ &candidates,
+ &query,
+ true,
+ 10,
+ &Default::default(),
+ cx.background_executor().clone(),
+ )
+ .await;
+
+ Ok(matches
+ .into_iter()
+ .map(|mat| Completion {
+ old_range: start_anchor..end_anchor,
+ new_text: mat.string.clone(),
+ label: CodeLabel {
+ filter_range: 1..mat.string.len() + 1,
+ text: format!("@{}", mat.string),
+ runs: Vec::new(),
+ },
+ documentation: None,
+ server_id: LanguageServerId(0), // TODO: Make this optional or something?
+ lsp_completion: Default::default(), // TODO: Make this optional or something?
+ })
+ .collect())
+ })
+ }
+
async fn find_mentions(
this: WeakView<MessageEditor>,
buffer: BufferSnapshot,
@@ -216,7 +326,7 @@ impl Render for MessageEditor {
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features,
- font_size: rems(0.875).into(),
+ font_size: UiTextSize::Small.rems().into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3).into(),
@@ -20,7 +20,7 @@ use gpui::{
Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, SharedString, Styled,
Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView, WhiteSpace,
};
-use menu::{Cancel, Confirm, SelectNext, SelectPrev};
+use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
use project::{Fs, Project};
use rpc::proto::{self, PeerId};
use serde_derive::{Deserialize, Serialize};
@@ -1124,7 +1124,7 @@ impl CollabPanel {
)
.entry(
"Rename",
- None,
+ Some(Box::new(SecondaryConfirm)),
cx.handler_for(&this, move |this, cx| this.rename_channel(channel_id, cx)),
)
.entry(
@@ -1492,7 +1492,7 @@ impl CollabPanel {
}
}
- fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+ fn rename_selected_channel(&mut self, _: &SecondaryConfirm, cx: &mut ViewContext<Self>) {
if let Some(channel) = self.selected_channel() {
self.rename_channel(channel.id, cx);
}
@@ -2214,15 +2214,15 @@ impl CollabPanel {
let face_pile = if !participants.is_empty() {
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
- let result = FacePile {
- faces: participants
+ let result = FacePile::new(
+ participants
.iter()
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())
.take(FACEPILE_LIMIT)
.chain(if extra_count > 0 {
Some(
div()
- .ml_1()
+ .ml_2()
.child(Label::new(format!("+{extra_count}")))
.into_any_element(),
)
@@ -2230,7 +2230,7 @@ impl CollabPanel {
None
})
.collect::<SmallVec<_>>(),
- };
+ );
Some(result)
} else {
@@ -2295,7 +2295,7 @@ impl CollabPanel {
h_flex()
.id(channel_id as usize)
.child(Label::new(channel.name.clone()))
- .children(face_pile.map(|face_pile| face_pile.render().p_1())),
+ .children(face_pile.map(|face_pile| face_pile.p_1())),
),
)
.child(
@@ -85,7 +85,14 @@ impl Render for CollabTitlebarItem {
.gap_1()
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
- .child(div().pr_1().children(self.render_project_branch(cx)))
+ .children(self.render_project_branch(cx)),
+ )
+ .child(
+ h_flex()
+ .id("collaborator-list")
+ .w_full()
+ .gap_1()
+ .overflow_x_scroll()
.when_some(
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|this, ((current_user, peer_id), room)| {
@@ -495,7 +502,7 @@ impl CollabTitlebarItem {
div.rounded_md().bg(color)
})
.child(
- FacePile::default()
+ FacePile::empty()
.child(
Avatar::new(user.avatar_uri.clone())
.grayscale(!is_present)
@@ -547,8 +554,7 @@ impl CollabTitlebarItem {
)
} else {
None
- })
- .render(),
+ }),
),
)
}
@@ -1,28 +1,49 @@
-use gpui::{div, AnyElement, Div, IntoElement, ParentElement, Styled};
+use gpui::AnyElement;
use smallvec::SmallVec;
+use ui::prelude::*;
-#[derive(Default)]
+#[derive(IntoElement)]
pub struct FacePile {
- pub faces: SmallVec<[AnyElement; 2]>,
+ base: Div,
+ faces: SmallVec<[AnyElement; 2]>,
}
impl FacePile {
- pub fn render(self) -> Div {
+ pub fn empty() -> Self {
+ Self::new(SmallVec::new())
+ }
+
+ pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
+ Self {
+ base: h_flex(),
+ faces,
+ }
+ }
+}
+
+impl RenderOnce for FacePile {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
let player_count = self.faces.len();
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
- .z_index((player_count - ix) as u8)
+ .z_index((player_count - ix) as u16)
.when(isnt_last, |div| div.neg_mr_1())
.child(player)
});
- div().flex().items_center().children(player_list)
+ self.base.children(player_list)
}
}
impl ParentElement for FacePile {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.faces
+ fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
+ self.faces.extend(elements);
+ }
+}
+
+impl Styled for FacePile {
+ fn style(&mut self) -> &mut gpui::StyleRefinement {
+ self.base.style()
}
}
@@ -713,6 +713,9 @@ impl Render for NotificationToast {
h_flex()
.id("notification_panel_toast")
+ .elevation_3(cx)
+ .p_2()
+ .gap_2()
.children(user.map(|user| Avatar::new(user.avatar_uri.clone())))
.child(Label::new(self.text.clone()))
.child(
@@ -26,8 +26,8 @@ impl CollabNotification {
}
impl ParentElement for CollabNotification {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -311,7 +311,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let action = command.action;
cx.focus(&self.previous_focus_handle);
cx.window_context()
- .spawn(move |mut cx| async move { cx.update(|_, cx| cx.dispatch_action(action)) })
+ .spawn(move |mut cx| async move { cx.update(|cx| cx.dispatch_action(action)) })
.detach_and_log_err(cx);
self.dismissed(cx);
}
@@ -974,7 +974,7 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
.browser_download_url;
let mut response = http
- .get(&url, Default::default(), true)
+ .get(url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading copilot release: {}", err))?;
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
@@ -355,7 +355,7 @@ fn initiate_sign_in(cx: &mut WindowContext) {
cx.spawn(|mut cx| async move {
task.await;
- if let Some(copilot) = cx.update(|_, cx| Copilot::global(cx)).ok().flatten() {
+ if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() {
workspace
.update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
Status::Authorized => workspace.show_toast(
@@ -1584,27 +1584,34 @@ mod tests {
}
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
+ let editor_view = editor.clone();
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
snapshot
.blocks_in_range(0..snapshot.max_point().row())
.enumerate()
.filter_map(|(ix, (row, block))| {
- let name = match block {
- TransformBlock::Custom(block) => block
- .render(&mut BlockContext {
- view_context: cx,
- anchor_x: px(0.),
- gutter_padding: px(0.),
- gutter_width: px(0.),
- line_height: px(0.),
- em_width: px(0.),
- block_id: ix,
- editor_style: &editor::EditorStyle::default(),
- })
- .inner_id()?
- .try_into()
- .ok()?,
+ let name: SharedString = match block {
+ TransformBlock::Custom(block) => cx.with_element_context({
+ let editor_view = editor_view.clone();
+ |cx| -> Option<SharedString> {
+ block
+ .render(&mut BlockContext {
+ context: cx,
+ anchor_x: px(0.),
+ gutter_padding: px(0.),
+ gutter_width: px(0.),
+ line_height: px(0.),
+ em_width: px(0.),
+ block_id: ix,
+ view: editor_view,
+ editor_style: &editor::EditorStyle::default(),
+ })
+ .inner_id()?
+ .try_into()
+ .ok()
+ }
+ })?,
TransformBlock::ExcerptHeader {
starts_new_buffer, ..
@@ -214,5 +214,6 @@ gpui::actions!(
Undo,
UndoSelection,
UnfoldLines,
+ DisplayCursorNames
]
);
@@ -4,7 +4,7 @@ use super::{
};
use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _};
use collections::{Bound, HashMap, HashSet};
-use gpui::{AnyElement, Pixels, ViewContext};
+use gpui::{AnyElement, ElementContext, Pixels, View};
use language::{BufferSnapshot, Chunk, Patch, Point};
use parking_lot::Mutex;
use std::{
@@ -81,7 +81,8 @@ pub enum BlockStyle {
}
pub struct BlockContext<'a, 'b> {
- pub view_context: &'b mut ViewContext<'a, Editor>,
+ pub context: &'b mut ElementContext<'a>,
+ pub view: View<Editor>,
pub anchor_x: Pixels,
pub gutter_width: Pixels,
pub gutter_padding: Pixels,
@@ -933,16 +934,16 @@ impl BlockDisposition {
}
impl<'a> Deref for BlockContext<'a, '_> {
- type Target = ViewContext<'a, Editor>;
+ type Target = ElementContext<'a>;
fn deref(&self) -> &Self::Target {
- self.view_context
+ self.context
}
}
impl DerefMut for BlockContext<'_, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
- self.view_context
+ self.context
}
}
@@ -40,7 +40,7 @@ pub(crate) use actions::*;
use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager;
-use client::{Client, Collaborator, ParticipantIndex};
+use client::{Collaborator, ParticipantIndex};
use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
@@ -56,10 +56,11 @@ use git::diff_hunk_to_display;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Context,
- DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
- HighlightStyle, Hsla, InputHandler, InteractiveText, KeyContext, Model, MouseButton,
+ DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusableView, FontId, FontStyle,
+ FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, Model, MouseButton,
ParentElement, Pixels, Render, SharedString, Styled, StyledText, Subscription, Task, TextStyle,
- UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
+ UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakView,
+ WhiteSpace, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -71,8 +72,7 @@ use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
- Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection,
- SelectionGoal, TransactionId,
+ Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
};
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
@@ -88,7 +88,7 @@ use ordered_float::OrderedFloat;
use parking_lot::RwLock;
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*;
-use rpc::proto::{self, *};
+use rpc::proto::*;
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize};
@@ -365,8 +365,11 @@ pub struct Editor {
active_diagnostics: Option<ActiveDiagnosticGroup>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
project: Option<Model<Project>>,
+ completion_provider: Option<Box<dyn CompletionProvider>>,
collaboration_hub: Option<Box<dyn CollaborationHub>>,
blink_manager: Model<BlinkManager>,
+ show_cursor_names: bool,
+ hovered_cursor: Option<HoveredCursor>,
pub show_local_selections: bool,
mode: EditorMode,
show_gutter: bool,
@@ -406,11 +409,12 @@ pub struct Editor {
style: Option<EditorStyle>,
editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
show_copilot_suggestions: bool,
+ use_autoclose: bool,
}
pub struct EditorSnapshot {
pub mode: EditorMode,
- pub show_gutter: bool,
+ show_gutter: bool,
pub display_snapshot: DisplaySnapshot,
pub placeholder_text: Option<Arc<str>>,
is_focused: bool,
@@ -418,6 +422,23 @@ pub struct EditorSnapshot {
ongoing_scroll: OngoingScroll,
}
+pub struct GutterDimensions {
+ pub padding: Pixels,
+ pub width: Pixels,
+ pub margin: Pixels,
+}
+
+impl Default for GutterDimensions {
+ fn default() -> Self {
+ Self {
+ padding: Pixels::ZERO,
+ width: Pixels::ZERO,
+ margin: Pixels::ZERO,
+ }
+ }
+}
+
+#[derive(Debug)]
pub struct RemoteSelection {
pub replica_id: ReplicaId,
pub selection: Selection<Anchor>,
@@ -425,6 +446,7 @@ pub struct RemoteSelection {
pub peer_id: PeerId,
pub line_mode: bool,
pub participant_index: Option<ParticipantIndex>,
+ pub user_name: Option<SharedString>,
}
#[derive(Clone, Debug)]
@@ -441,6 +463,11 @@ enum SelectionHistoryMode {
Redoing,
}
+struct HoveredCursor {
+ replica_id: u16,
+ selection_id: usize,
+}
+
impl Default for SelectionHistoryMode {
fn default() -> Self {
Self::Normal
@@ -722,85 +749,21 @@ impl CompletionsMenu {
return None;
}
- let Some(project) = editor.project.clone() else {
+ let Some(provider) = editor.completion_provider.as_ref() else {
return None;
};
- let client = project.read(cx).client();
- let language_registry = project.read(cx).languages().clone();
-
- let is_remote = project.read(cx).is_remote();
- let project_id = project.read(cx).remote_id();
-
- let completions = self.completions.clone();
- let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
-
- Some(cx.spawn(move |this, mut cx| async move {
- if is_remote {
- let Some(project_id) = project_id else {
- log::error!("Remote project without remote_id");
- return;
- };
-
- for completion_index in completion_indices {
- let completions_guard = completions.read();
- let completion = &completions_guard[completion_index];
- if completion.documentation.is_some() {
- continue;
- }
-
- let server_id = completion.server_id;
- let completion = completion.lsp_completion.clone();
- drop(completions_guard);
-
- Self::resolve_completion_documentation_remote(
- project_id,
- server_id,
- completions.clone(),
- completion_index,
- completion,
- client.clone(),
- language_registry.clone(),
- )
- .await;
-
- _ = this.update(&mut cx, |_, cx| cx.notify());
- }
- } else {
- for completion_index in completion_indices {
- let completions_guard = completions.read();
- let completion = &completions_guard[completion_index];
- if completion.documentation.is_some() {
- continue;
- }
-
- let server_id = completion.server_id;
- let completion = completion.lsp_completion.clone();
- drop(completions_guard);
-
- let server = project
- .read_with(&mut cx, |project, _| {
- project.language_server_for_id(server_id)
- })
- .ok()
- .flatten();
- let Some(server) = server else {
- return;
- };
-
- Self::resolve_completion_documentation_local(
- server,
- completions.clone(),
- completion_index,
- completion,
- language_registry.clone(),
- )
- .await;
+ let resolve_task = provider.resolve_completions(
+ self.matches.iter().map(|m| m.candidate_id).collect(),
+ self.completions.clone(),
+ cx,
+ );
- _ = this.update(&mut cx, |_, cx| cx.notify());
- }
+ return Some(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(
@@ -817,146 +780,16 @@ impl CompletionsMenu {
let Some(project) = project else {
return;
};
- let language_registry = project.read(cx).languages().clone();
-
- let completions = self.completions.clone();
- let completions_guard = completions.read();
- let completion = &completions_guard[completion_index];
- if completion.documentation.is_some() {
- return;
- }
-
- let server_id = completion.server_id;
- let completion = completion.lsp_completion.clone();
- drop(completions_guard);
-
- if project.read(cx).is_remote() {
- let Some(project_id) = project.read(cx).remote_id() else {
- log::error!("Remote project without remote_id");
- return;
- };
-
- let client = project.read(cx).client();
-
- cx.spawn(move |this, mut cx| async move {
- Self::resolve_completion_documentation_remote(
- project_id,
- server_id,
- completions.clone(),
- completion_index,
- completion,
- client,
- language_registry.clone(),
- )
- .await;
-
- _ = this.update(&mut cx, |_, cx| cx.notify());
- })
- .detach();
- } else {
- let Some(server) = project.read(cx).language_server_for_id(server_id) else {
- return;
- };
-
- cx.spawn(move |this, mut cx| async move {
- Self::resolve_completion_documentation_local(
- server,
- completions,
- completion_index,
- completion,
- language_registry,
- )
- .await;
-
- _ = this.update(&mut cx, |_, cx| cx.notify());
- })
- .detach();
- }
- }
-
- async fn resolve_completion_documentation_remote(
- project_id: u64,
- server_id: LanguageServerId,
- completions: Arc<RwLock<Box<[Completion]>>>,
- completion_index: usize,
- completion: lsp::CompletionItem,
- client: Arc<Client>,
- language_registry: Arc<LanguageRegistry>,
- ) {
- let request = proto::ResolveCompletionDocumentation {
- project_id,
- language_server_id: server_id.0 as u64,
- lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
- };
-
- let Some(response) = client
- .request(request)
- .await
- .context("completion documentation resolve proto request")
- .log_err()
- else {
- return;
- };
-
- if response.text.is_empty() {
- let mut completions = completions.write();
- let completion = &mut completions[completion_index];
- completion.documentation = Some(Documentation::Undocumented);
- }
-
- let documentation = if response.is_markdown {
- Documentation::MultiLineMarkdown(
- markdown::parse_markdown(&response.text, &language_registry, None).await,
- )
- } else if response.text.lines().count() <= 1 {
- Documentation::SingleLine(response.text)
- } else {
- Documentation::MultiLinePlainText(response.text)
- };
-
- let mut completions = completions.write();
- let completion = &mut completions[completion_index];
- completion.documentation = Some(documentation);
- }
-
- async fn resolve_completion_documentation_local(
- server: Arc<lsp::LanguageServer>,
- completions: Arc<RwLock<Box<[Completion]>>>,
- completion_index: usize,
- completion: lsp::CompletionItem,
- language_registry: Arc<LanguageRegistry>,
- ) {
- let can_resolve = server
- .capabilities()
- .completion_provider
- .as_ref()
- .and_then(|options| options.resolve_provider)
- .unwrap_or(false);
- if !can_resolve {
- return;
- }
-
- let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
- let Some(completion_item) = request.await.log_err() else {
- return;
- };
-
- if let Some(lsp_documentation) = completion_item.documentation {
- let documentation = language::prepare_completion_documentation(
- &lsp_documentation,
- &language_registry,
- None, // TODO: Try to reasonably work out which language the completion is for
- )
- .await;
- let mut completions = completions.write();
- let completion = &mut completions[completion_index];
- completion.documentation = Some(documentation);
- } else {
- let mut completions = completions.write();
- let completion = &mut completions[completion_index];
- completion.documentation = Some(Documentation::Undocumented);
- }
+ let resolve_task = project.update(cx, |project, cx| {
+ project.resolve_completions(vec![completion_index], self.completions.clone(), cx)
+ });
+ 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();
+ }
+ })
+ .detach();
}
fn visible(&self) -> bool {
@@ -1565,6 +1398,7 @@ impl Editor {
ime_transaction: Default::default(),
active_diagnostics: None,
soft_wrap_mode_override,
+ completion_provider: project.clone().map(|project| Box::new(project) as _),
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
project,
blink_manager: blink_manager.clone(),
@@ -1594,6 +1428,7 @@ impl Editor {
keymap_context_layers: Default::default(),
input_enabled: true,
read_only: false,
+ use_autoclose: true,
leader_peer_id: None,
remote_id: None,
hover_state: Default::default(),
@@ -1604,6 +1439,8 @@ impl Editor {
pixel_position_of_newest_cursor: None,
gutter_width: Default::default(),
style: None,
+ show_cursor_names: false,
+ hovered_cursor: Default::default(),
editor_actions: Default::default(),
show_copilot_suggestions: mode == EditorMode::Full,
_subscriptions: vec![
@@ -1795,6 +1632,10 @@ impl Editor {
self.collaboration_hub = Some(hub);
}
+ pub fn set_completion_provider(&mut self, hub: Box<dyn CompletionProvider>) {
+ self.completion_provider = Some(hub);
+ }
+
pub fn placeholder_text(&self) -> Option<&str> {
self.placeholder_text.as_deref()
}
@@ -1869,6 +1710,10 @@ impl Editor {
self.read_only = read_only;
}
+ pub fn set_use_autoclose(&mut self, autoclose: bool) {
+ self.use_autoclose = autoclose;
+ }
+
pub fn set_show_copilot_suggestions(&mut self, show_copilot_suggestions: bool) {
self.show_copilot_suggestions = show_copilot_suggestions;
}
@@ -2467,7 +2312,12 @@ impl Editor {
),
&bracket_pair.start[..prefix_len],
));
- if following_text_allows_autoclose && preceding_text_matches_prefix {
+ let autoclose = self.use_autoclose
+ && snapshot.settings_at(selection.start, cx).use_autoclose;
+ if autoclose
+ && following_text_allows_autoclose
+ && preceding_text_matches_prefix
+ {
let anchor = snapshot.anchor_before(selection.end);
new_selections.push((selection.map(|_| anchor), text.len()));
new_autoclose_regions.push((
@@ -3241,9 +3091,7 @@ impl Editor {
return;
}
- let project = if let Some(project) = self.project.clone() {
- project
- } else {
+ let Some(provider) = self.completion_provider.as_ref() else {
return;
};
@@ -3259,9 +3107,7 @@ impl Editor {
};
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone());
- let completions = project.update(cx, |project, cx| {
- project.completions(&buffer, buffer_position, cx)
- });
+ let completions = provider.completions(&buffer, buffer_position, cx);
let id = post_inc(&mut self.next_completion_id);
let task = cx.spawn(|this, mut cx| {
@@ -3370,6 +3216,7 @@ impl Editor {
let buffer_handle = completions_menu.buffer;
let completions = completions_menu.completions.read();
let completion = completions.get(mat.candidate_id)?;
+ cx.stop_propagation();
let snippet;
let text;
@@ -3466,15 +3313,13 @@ impl Editor {
this.refresh_copilot_suggestions(true, cx);
});
- let project = self.project.clone()?;
- let apply_edits = project.update(cx, |project, cx| {
- project.apply_additional_edits_for_completion(
- buffer_handle,
- completion.clone(),
- true,
- cx,
- )
- });
+ let provider = self.completion_provider.as_ref()?;
+ let apply_edits = provider.apply_additional_edits_for_completion(
+ buffer_handle,
+ completion.clone(),
+ true,
+ cx,
+ );
Some(cx.foreground_executor().spawn(async move {
apply_edits.await?;
Ok(())
@@ -3561,7 +3406,7 @@ impl Editor {
let replica_id = this.update(&mut cx, |this, cx| this.replica_id(cx))?;
let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
- cx.update(|_, cx| {
+ cx.update(|cx| {
entries.sort_unstable_by_key(|(buffer, _)| {
buffer.read(cx).file().map(|f| f.path().clone())
});
@@ -3888,6 +3733,24 @@ impl Editor {
self.update_visible_copilot_suggestion(cx);
}
+ pub fn display_cursor_names(&mut self, _: &DisplayCursorNames, cx: &mut ViewContext<Self>) {
+ self.show_cursor_names(cx);
+ }
+
+ fn show_cursor_names(&mut self, cx: &mut ViewContext<Self>) {
+ self.show_cursor_names = true;
+ cx.notify();
+ cx.spawn(|this, mut cx| async move {
+ cx.background_executor().timer(Duration::from_secs(3)).await;
+ this.update(&mut cx, |this, cx| {
+ this.show_cursor_names = false;
+ cx.notify()
+ })
+ .ok()
+ })
+ .detach();
+ }
+
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
if self.has_active_copilot_suggestion(cx) {
self.cycle_copilot_suggestions(Direction::Next, cx);
@@ -4065,7 +3928,7 @@ impl Editor {
gutter_hovered: bool,
_line_height: Pixels,
_gutter_margin: Pixels,
- cx: &mut ViewContext<Self>,
+ editor_view: View<Editor>,
) -> Vec<Option<IconButton>> {
fold_data
.iter()
@@ -4075,14 +3938,19 @@ impl Editor {
.map(|(fold_status, buffer_row, active)| {
(active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
IconButton::new(ix as usize, ui::IconName::ChevronDown)
- .on_click(cx.listener(move |editor, _e, cx| match fold_status {
- FoldStatus::Folded => {
- editor.unfold_at(&UnfoldAt { buffer_row }, cx);
+ .on_click({
+ let view = editor_view.clone();
+ move |_e, cx| {
+ view.update(cx, |editor, cx| match fold_status {
+ FoldStatus::Folded => {
+ editor.unfold_at(&UnfoldAt { buffer_row }, cx);
+ }
+ FoldStatus::Foldable => {
+ editor.fold_at(&FoldAt { buffer_row }, cx);
+ }
+ })
}
- FoldStatus::Foldable => {
- editor.fold_at(&FoldAt { buffer_row }, cx);
- }
- }))
+ })
.icon_color(ui::Color::Muted)
.icon_size(ui::IconSize::Small)
.selected(fold_status == FoldStatus::Folded)
@@ -8992,6 +8860,7 @@ impl Editor {
cx.focus(&rename_editor_focus_handle);
} else {
self.blink_manager.update(cx, BlinkManager::enable);
+ self.show_cursor_names(cx);
self.buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction(cx);
if self.leader_peer_id.is_none() {
@@ -9043,6 +8912,7 @@ pub trait CollaborationHub {
&self,
cx: &'a AppContext,
) -> &'a HashMap<u64, ParticipantIndex>;
+ fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString>;
}
impl CollaborationHub for Model<Project> {
@@ -9056,6 +8926,74 @@ impl CollaborationHub for Model<Project> {
) -> &'a HashMap<u64, ParticipantIndex> {
self.read(cx).user_store().read(cx).participant_indices()
}
+
+ fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
+ let this = self.read(cx);
+ let user_ids = this.collaborators().values().map(|c| c.user_id);
+ this.user_store().read_with(cx, |user_store, cx| {
+ user_store.participant_names(user_ids, cx)
+ })
+ }
+}
+
+pub trait CompletionProvider {
+ fn completions(
+ &self,
+ buffer: &Model<Buffer>,
+ buffer_position: text::Anchor,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Vec<Completion>>>;
+
+ fn resolve_completions(
+ &self,
+ completion_indices: Vec<usize>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<bool>>;
+
+ fn apply_additional_edits_for_completion(
+ &self,
+ buffer: Model<Buffer>,
+ completion: Completion,
+ push_to_history: bool,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Option<language::Transaction>>>;
+}
+
+impl CompletionProvider for Model<Project> {
+ fn completions(
+ &self,
+ buffer: &Model<Buffer>,
+ buffer_position: text::Anchor,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Vec<Completion>>> {
+ self.update(cx, |project, cx| {
+ project.completions(&buffer, buffer_position, cx)
+ })
+ }
+
+ fn resolve_completions(
+ &self,
+ completion_indices: Vec<usize>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<bool>> {
+ self.update(cx, |project, cx| {
+ project.resolve_completions(completion_indices, completions, cx)
+ })
+ }
+
+ fn apply_additional_edits_for_completion(
+ &self,
+ buffer: Model<Buffer>,
+ completion: Completion,
+ push_to_history: bool,
+ cx: &mut ViewContext<Editor>,
+ ) -> Task<Result<Option<language::Transaction>>> {
+ self.update(cx, |project, cx| {
+ project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx)
+ })
+ }
}
fn inlay_hint_settings(
@@ -9107,6 +9045,7 @@ impl EditorSnapshot {
collaboration_hub: &dyn CollaborationHub,
cx: &'a AppContext,
) -> impl 'a + Iterator<Item = RemoteSelection> {
+ let participant_names = collaboration_hub.user_names(cx);
let participant_indices = collaboration_hub.user_participant_indices(cx);
let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
let collaborators_by_replica_id = collaborators_by_peer_id
@@ -9118,6 +9057,7 @@ impl EditorSnapshot {
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
let collaborator = collaborators_by_replica_id.get(&replica_id)?;
let participant_index = participant_indices.get(&collaborator.user_id).copied();
+ let user_name = participant_names.get(&collaborator.user_id).cloned();
Some(RemoteSelection {
replica_id,
selection,
@@ -9125,6 +9065,7 @@ impl EditorSnapshot {
line_mode,
participant_index,
peer_id: collaborator.peer_id,
+ user_name,
})
})
}
@@ -9144,6 +9085,34 @@ impl EditorSnapshot {
pub fn scroll_position(&self) -> gpui::Point<f32> {
self.scroll_anchor.scroll_position(&self.display_snapshot)
}
+
+ pub fn gutter_dimensions(
+ &self,
+ font_id: FontId,
+ font_size: Pixels,
+ em_width: Pixels,
+ max_line_number_width: Pixels,
+ cx: &AppContext,
+ ) -> GutterDimensions {
+ if self.show_gutter {
+ let descent = cx.text_system().descent(font_id, font_size);
+ let gutter_padding_factor = 4.0;
+ let gutter_padding = (em_width * gutter_padding_factor).round();
+ // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
+ let min_width_for_number_on_gutter = em_width * 4.0;
+ let gutter_width =
+ max_line_number_width.max(min_width_for_number_on_gutter) + gutter_padding * 2.0;
+ let gutter_margin = -descent;
+
+ GutterDimensions {
+ padding: gutter_padding,
+ width: gutter_width,
+ margin: gutter_margin,
+ }
+ } else {
+ GutterDimensions::default()
+ }
+ }
}
impl Deref for EditorSnapshot {
@@ -9258,7 +9227,7 @@ impl Render for Editor {
}
}
-impl InputHandler for Editor {
+impl ViewInputHandler for Editor {
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
@@ -9655,10 +9624,10 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
.visible_on_hover(group_id)
- .on_click(cx.listener({
+ .on_click({
let message = diagnostic.message.clone();
- move |_, _, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
- }))
+ move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
+ })
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
)
.into_any_element()
@@ -25,12 +25,12 @@ use collections::{BTreeMap, HashMap};
use git::diff::DiffHunkStatus;
use gpui::{
div, fill, outline, overlay, point, px, quad, relative, size, transparent_black, Action,
- AnchorCorner, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners,
- CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla,
- InteractiveBounds, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton,
- MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta,
- ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
- Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext,
+ AnchorCorner, AnyElement, AvailableSpace, Bounds, ContentMask, Corners, CursorStyle,
+ DispatchPhase, Edges, Element, ElementInputHandler, Entity, Hsla, InteractiveBounds,
+ InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton, MouseDownEvent,
+ MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine,
+ SharedString, Size, StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun,
+ TextStyle, View, ViewContext, WindowContext,
};
use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting;
@@ -64,6 +64,7 @@ struct SelectionLayout {
is_local: bool,
range: Range<DisplayPoint>,
active_rows: Range<u32>,
+ user_name: Option<SharedString>,
}
impl SelectionLayout {
@@ -74,6 +75,7 @@ impl SelectionLayout {
map: &DisplaySnapshot,
is_newest: bool,
is_local: bool,
+ user_name: Option<SharedString>,
) -> Self {
let point_selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
let display_selection = point_selection.map(|p| p.to_display_point(map));
@@ -113,6 +115,7 @@ impl SelectionLayout {
is_local,
range,
active_rows,
+ user_name,
}
}
}
@@ -324,9 +327,10 @@ impl EditorElement {
register_action(view, cx, Editor::context_menu_prev);
register_action(view, cx, Editor::context_menu_next);
register_action(view, cx, Editor::context_menu_last);
+ register_action(view, cx, Editor::display_cursor_names);
}
- fn register_key_listeners(&self, cx: &mut WindowContext) {
+ fn register_key_listeners(&self, cx: &mut ElementContext) {
cx.on_key_event({
let editor = self.editor.clone();
move |event: &ModifiersChangedEvent, phase, cx| {
@@ -564,6 +568,7 @@ impl EditorElement {
cx,
);
hover_at(editor, Some(point), cx);
+ Self::update_visible_cursor(editor, point, cx);
}
None => {
update_inlay_link_and_hover_points(
@@ -585,12 +590,45 @@ impl EditorElement {
}
}
+ fn update_visible_cursor(
+ editor: &mut Editor,
+ point: DisplayPoint,
+ cx: &mut ViewContext<Editor>,
+ ) {
+ let snapshot = editor.snapshot(cx);
+ let Some(hub) = editor.collaboration_hub() else {
+ return;
+ };
+ let range = DisplayPoint::new(point.row(), point.column().saturating_sub(1))
+ ..DisplayPoint::new(
+ point.row(),
+ (point.column() + 1).min(snapshot.line_len(point.row())),
+ );
+
+ let range = snapshot
+ .buffer_snapshot
+ .anchor_at(range.start.to_point(&snapshot.display_snapshot), Bias::Left)
+ ..snapshot
+ .buffer_snapshot
+ .anchor_at(range.end.to_point(&snapshot.display_snapshot), Bias::Right);
+
+ let Some(selection) = snapshot.remote_selections_in_range(&range, hub, cx).next() else {
+ editor.hovered_cursor.take();
+ return;
+ };
+ editor.hovered_cursor.replace(crate::HoveredCursor {
+ replica_id: selection.replica_id,
+ selection_id: selection.selection.id,
+ });
+ cx.notify()
+ }
+
fn paint_background(
&self,
gutter_bounds: Bounds<Pixels>,
text_bounds: Bounds<Pixels>,
layout: &LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let bounds = gutter_bounds.union(&text_bounds);
let scroll_top =
@@ -673,7 +711,7 @@ impl EditorElement {
&mut self,
bounds: Bounds<Pixels>,
layout: &mut LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let line_height = layout.position_map.line_height;
@@ -744,7 +782,7 @@ impl EditorElement {
});
}
- fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
+ fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
let line_height = layout.position_map.line_height;
let scroll_position = layout.position_map.snapshot.scroll_position();
@@ -848,7 +886,7 @@ impl EditorElement {
&mut self,
text_bounds: Bounds<Pixels>,
layout: &mut LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let start_row = layout.visible_display_row_range.start;
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
@@ -982,8 +1020,10 @@ impl EditorElement {
let corner_radius = 0.15 * layout.position_map.line_height;
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
- for (selection_style, selections) in &layout.selections {
- for selection in selections {
+ for (participant_ix, (selection_style, selections)) in
+ layout.selections.iter().enumerate()
+ {
+ for selection in selections.into_iter() {
self.paint_highlighted_range(
selection.range.clone(),
selection_style.selection,
@@ -1064,6 +1104,7 @@ impl EditorElement {
))
});
}
+
cursors.push(Cursor {
color: selection_style.cursor,
block_width,
@@ -1071,6 +1112,14 @@ impl EditorElement {
line_height: layout.position_map.line_height,
shape: selection.cursor_shape,
block_text,
+ cursor_name: selection.user_name.clone().map(|name| {
+ CursorName {
+ string: name,
+ color: self.style.background,
+ is_top_row: cursor_position.row() == 0,
+ z_index: (participant_ix % 256).try_into().unwrap(),
+ }
+ }),
});
}
}
@@ -1104,7 +1153,7 @@ impl EditorElement {
&mut self,
text_bounds: Bounds<Pixels>,
layout: &mut LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO);
let start_row = layout.visible_display_row_range.start;
@@ -1169,9 +1218,11 @@ impl EditorElement {
popover_origin.x = popover_origin.x + x_out_of_bounds;
}
- cx.break_content_mask(|cx| {
- hover_popover.draw(popover_origin, available_space, cx)
- });
+ if cx.was_top_layer(&popover_origin, cx.stacking_order()) {
+ cx.break_content_mask(|cx| {
+ hover_popover.draw(popover_origin, available_space, cx)
+ });
+ }
current_y = popover_origin.y - HOVER_POPOVER_GAP;
}
@@ -1217,7 +1268,7 @@ impl EditorElement {
&mut self,
bounds: Bounds<Pixels>,
layout: &mut LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
if layout.mode != EditorMode::Full {
return;
@@ -1461,7 +1512,7 @@ impl EditorElement {
layout: &LayoutState,
content_origin: gpui::Point<Pixels>,
bounds: Bounds<Pixels>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let start_row = layout.visible_display_row_range.start;
let end_row = layout.visible_display_row_range.end;
@@ -1513,7 +1564,7 @@ impl EditorElement {
&mut self,
bounds: Bounds<Pixels>,
layout: &mut LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let scroll_position = layout.position_map.snapshot.scroll_position();
let scroll_left = scroll_position.x * layout.position_map.em_width;
@@ -1763,7 +1814,7 @@ impl EditorElement {
}
}
- fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut WindowContext) -> LayoutState {
+ fn compute_layout(&mut self, bounds: Bounds<Pixels>, cx: &mut ElementContext) -> LayoutState {
self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx);
let style = self.style.clone();
@@ -1783,30 +1834,16 @@ impl EditorElement {
.unwrap()
.width;
- let gutter_padding;
- let gutter_width;
- let gutter_margin;
- if snapshot.show_gutter {
- let descent = cx.text_system().descent(font_id, font_size);
+ let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, em_width, self.max_line_number_width(&snapshot, cx), cx);
- let gutter_padding_factor = 3.5;
- gutter_padding = (em_width * gutter_padding_factor).round();
- gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
- gutter_margin = -descent;
- } else {
- gutter_padding = Pixels::ZERO;
- gutter_width = Pixels::ZERO;
- gutter_margin = Pixels::ZERO;
- };
-
- editor.gutter_width = gutter_width;
+ editor.gutter_width = gutter_dimensions.width;
- let text_width = bounds.size.width - gutter_width;
+ let text_width = bounds.size.width - gutter_dimensions.width;
let overscroll = size(em_width, px(0.));
let _snapshot = {
editor.set_visible_line_count((bounds.size.height / line_height).into(), cx);
- let editor_width = text_width - gutter_margin - overscroll.width - em_width;
+ let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
let wrap_width = match editor.soft_wrap_mode(cx) {
SoftWrap::None => (MAX_LINE_LEN / 2) as f32 * em_advance,
SoftWrap::EditorWidth => editor_width,
@@ -1826,7 +1863,7 @@ impl EditorElement {
.map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
.collect::<SmallVec<[_; 2]>>();
- let gutter_size = size(gutter_width, bounds.size.height);
+ let gutter_size = size(gutter_dimensions.width, bounds.size.height);
let text_size = size(text_width, bounds.size.height);
let autoscroll_horizontally =
@@ -1889,6 +1926,7 @@ impl EditorElement {
&snapshot.display_snapshot,
is_newest,
true,
+ None,
);
if is_newest {
newest_selection_head = Some(layout.head);
@@ -1949,6 +1987,7 @@ impl EditorElement {
if Some(selection.peer_id) == editor.leader_peer_id {
continue;
}
+ let is_shown = editor.show_cursor_names || editor.hovered_cursor.as_ref().is_some_and(|c| c.replica_id == selection.replica_id && c.selection_id == selection.selection.id);
remote_selections
.entry(selection.replica_id)
@@ -1961,6 +2000,11 @@ impl EditorElement {
&snapshot.display_snapshot,
false,
false,
+ if is_shown {
+ selection.user_name
+ } else {
+ None
+ },
));
}
@@ -1992,6 +2036,7 @@ impl EditorElement {
&snapshot.display_snapshot,
true,
true,
+ None,
)
.head
});
@@ -2022,22 +2067,26 @@ impl EditorElement {
.width;
let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.width;
- let (scroll_width, blocks) = cx.with_element_id(Some("editor_blocks"), |cx| {
+ let editor_view = cx.view().clone();
+ let (scroll_width, blocks) = cx.with_element_context(|cx| {
+ cx.with_element_id(Some("editor_blocks"), |cx| {
self.layout_blocks(
start_row..end_row,
&snapshot,
bounds.size.width,
scroll_width,
- gutter_padding,
- gutter_width,
+ gutter_dimensions.padding,
+ gutter_dimensions.width,
em_width,
- gutter_width + gutter_margin,
+ gutter_dimensions.width + gutter_dimensions.margin,
line_height,
&style,
&line_layouts,
editor,
+ editor_view,
cx,
)
+ })
});
let scroll_max = point(
@@ -2069,7 +2118,13 @@ impl EditorElement {
if let Some(newest_selection_head) = newest_selection_head {
if (start_row..end_row).contains(&newest_selection_head.row()) {
if editor.context_menu_visible() {
- let max_height = (12. * line_height).min((bounds.size.height - line_height) / 2.);
+ let max_height = cmp::min(
+ 12. * line_height,
+ cmp::max(
+ 3. * line_height,
+ (bounds.size.height - line_height) / 2.,
+ )
+ );
context_menu =
editor.render_context_menu(newest_selection_head, &self.style, max_height, cx);
}
@@ -2107,15 +2162,19 @@ impl EditorElement {
cx,
);
- let fold_indicators = cx.with_element_id(Some("gutter_fold_indicators"), |cx| {
+ let editor_view = cx.view().clone();
+ let fold_indicators = cx.with_element_context(|cx| {
+
+ cx.with_element_id(Some("gutter_fold_indicators"), |_cx| {
editor.render_fold_indicators(
fold_statuses,
&style,
editor.gutter_hovered,
line_height,
- gutter_margin,
- cx,
+ gutter_dimensions.margin,
+ editor_view,
)
+ })
});
let invisible_symbol_font_size = font_size / 2.;
@@ -2167,13 +2226,13 @@ impl EditorElement {
visible_display_row_range: start_row..end_row,
wrap_guides,
gutter_size,
- gutter_padding,
+ gutter_padding: gutter_dimensions.padding,
text_size,
scrollbar_row_range,
show_scrollbars,
is_singleton,
max_row,
- gutter_margin,
+ gutter_margin: gutter_dimensions.margin,
active_rows,
highlighted_rows,
highlighted_ranges,
@@ -2206,7 +2265,8 @@ impl EditorElement {
style: &EditorStyle,
line_layouts: &[LineWithInvisibles],
editor: &mut Editor,
- cx: &mut ViewContext<Editor>,
+ editor_view: View<Editor>,
+ cx: &mut ElementContext,
) -> (Pixels, Vec<BlockLayout>) {
let mut block_id = 0;
let (fixed_blocks, non_fixed_blocks) = snapshot
@@ -2220,7 +2280,7 @@ impl EditorElement {
available_space: Size<AvailableSpace>,
block_id: usize,
editor: &mut Editor,
- cx: &mut ViewContext<Editor>| {
+ cx: &mut ElementContext| {
let mut element = match block {
TransformBlock::Custom(block) => {
let align_to = block
@@ -2239,13 +2299,14 @@ impl EditorElement {
};
block.render(&mut BlockContext {
- view_context: cx,
+ context: cx,
anchor_x,
gutter_padding,
line_height,
gutter_width,
em_width,
block_id,
+ view: editor_view.clone(),
editor_style: &self.style,
})
}
@@ -2437,7 +2498,7 @@ impl EditorElement {
&mut self,
interactive_bounds: &InteractiveBounds,
layout: &LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
cx.on_mouse_event({
let position_map = layout.position_map.clone();
@@ -2497,7 +2558,7 @@ impl EditorElement {
gutter_bounds: Bounds<Pixels>,
text_bounds: Bounds<Pixels>,
layout: &LayoutState,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let interactive_bounds = InteractiveBounds {
bounds: bounds.intersect(&cx.content_mask().bounds),
@@ -2720,7 +2781,7 @@ impl LineWithInvisibles {
content_origin: gpui::Point<Pixels>,
whitespace_setting: ShowWhitespaceSetting,
selection_ranges: &[Range<DisplayPoint>],
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let line_height = layout.position_map.line_height;
let line_y = line_height * row as f32 - layout.position_map.scroll_position.y;
@@ -2754,7 +2815,7 @@ impl LineWithInvisibles {
row: u32,
line_height: Pixels,
whitespace_setting: ShowWhitespaceSetting,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let allowed_invisibles_regions = match whitespace_setting {
ShowWhitespaceSetting::None => return,
@@ -2803,7 +2864,7 @@ impl Element for EditorElement {
fn request_layout(
&mut self,
_element_state: Option<Self::State>,
- cx: &mut gpui::WindowContext,
+ cx: &mut gpui::ElementContext,
) -> (gpui::LayoutId, Self::State) {
cx.with_view_id(self.editor.entity_id(), |cx| {
self.editor.update(cx, |editor, cx| {
@@ -2815,34 +2876,36 @@ impl Element for EditorElement {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
- cx.request_layout(&style, None)
+ cx.with_element_context(|cx| cx.request_layout(&style, None))
}
EditorMode::AutoHeight { max_lines } => {
let editor_handle = cx.view().clone();
let max_line_number_width =
self.max_line_number_width(&editor.snapshot(cx), cx);
- cx.request_measured_layout(
- Style::default(),
- move |known_dimensions, _, cx| {
- editor_handle
- .update(cx, |editor, cx| {
- compute_auto_height_layout(
- editor,
- max_lines,
- max_line_number_width,
- known_dimensions,
- cx,
- )
- })
- .unwrap_or_default()
- },
- )
+ cx.with_element_context(|cx| {
+ cx.request_measured_layout(
+ Style::default(),
+ move |known_dimensions, _, cx| {
+ editor_handle
+ .update(cx, |editor, cx| {
+ compute_auto_height_layout(
+ editor,
+ max_lines,
+ max_line_number_width,
+ known_dimensions,
+ cx,
+ )
+ })
+ .unwrap_or_default()
+ },
+ )
+ })
}
EditorMode::Full => {
let mut style = Style::default();
style.size.width = relative(1.).into();
style.size.height = relative(1.).into();
- cx.request_layout(&style, None)
+ cx.with_element_context(|cx| cx.request_layout(&style, None))
}
};
@@ -2855,7 +2918,7 @@ impl Element for EditorElement {
&mut self,
bounds: Bounds<gpui::Pixels>,
_element_state: &mut Self::State,
- cx: &mut gpui::WindowContext,
+ cx: &mut gpui::ElementContext,
) {
let editor = self.editor.clone();
@@ -2884,9 +2947,10 @@ impl Element for EditorElement {
self.register_key_listeners(cx);
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
- let input_handler =
- ElementInputHandler::new(bounds, self.editor.clone(), cx);
- cx.handle_input(&focus_handle, input_handler);
+ cx.handle_input(
+ &focus_handle,
+ ElementInputHandler::new(bounds, self.editor.clone()),
+ );
self.paint_background(gutter_bounds, text_bounds, &layout, cx);
if layout.gutter_size.width > Pixels::ZERO {
@@ -3097,6 +3161,15 @@ pub struct Cursor {
color: Hsla,
shape: CursorShape,
block_text: Option<ShapedLine>,
+ cursor_name: Option<CursorName>,
+}
+
+#[derive(Debug)]
+pub struct CursorName {
+ string: SharedString,
+ color: Hsla,
+ is_top_row: bool,
+ z_index: u16,
}
impl Cursor {
@@ -3107,6 +3180,7 @@ impl Cursor {
color: Hsla,
shape: CursorShape,
block_text: Option<ShapedLine>,
+ cursor_name: Option<CursorName>,
) -> Cursor {
Cursor {
origin,
@@ -3115,6 +3189,7 @@ impl Cursor {
color,
shape,
block_text,
+ cursor_name,
}
}
@@ -3125,7 +3200,7 @@ impl Cursor {
}
}
- pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
+ pub fn paint(&self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
let bounds = match self.shape {
CursorShape::Bar => Bounds {
origin: self.origin + origin,
@@ -3150,6 +3225,31 @@ impl Cursor {
fill(bounds, self.color)
};
+ if let Some(name) = &self.cursor_name {
+ let text_size = self.line_height / 1.5;
+
+ let name_origin = if name.is_top_row {
+ point(bounds.right() - px(1.), bounds.top())
+ } else {
+ point(bounds.left(), bounds.top() - text_size / 2. - px(1.))
+ };
+ cx.with_z_index(name.z_index, |cx| {
+ div()
+ .bg(self.color)
+ .text_size(text_size)
+ .px_0p5()
+ .line_height(text_size + px(2.))
+ .text_color(name.color)
+ .child(name.string.clone())
+ .into_any_element()
+ .draw(
+ name_origin,
+ size(AvailableSpace::MinContent, AvailableSpace::MinContent),
+ cx,
+ )
+ })
+ }
+
cx.paint_quad(cursor);
if let Some(block_text) = &self.block_text {
@@ -3180,7 +3280,7 @@ pub struct HighlightedRangeLine {
}
impl HighlightedRange {
- pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
+ pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
self.paint_lines(
@@ -3199,7 +3299,7 @@ impl HighlightedRange {
start_y: Pixels,
lines: &[HighlightedRangeLine],
_bounds: Bounds<Pixels>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
if lines.is_empty() {
return;
@@ -3417,14 +3517,16 @@ mod tests {
.unwrap();
let state = cx
.update_window(window.into(), |view, cx| {
- cx.with_view_id(view.entity_id(), |cx| {
- element.compute_layout(
- Bounds {
- origin: point(px(500.), px(500.)),
- size: size(px(500.), px(500.)),
- },
- cx,
- )
+ cx.with_element_context(|cx| {
+ cx.with_view_id(view.entity_id(), |cx| {
+ element.compute_layout(
+ Bounds {
+ origin: point(px(500.), px(500.)),
+ size: size(px(500.), px(500.)),
+ },
+ cx,
+ )
+ })
})
})
.unwrap();
@@ -3511,14 +3613,16 @@ mod tests {
let state = cx
.update_window(window.into(), |view, cx| {
- cx.with_view_id(view.entity_id(), |cx| {
- element.compute_layout(
- Bounds {
- origin: point(px(500.), px(500.)),
- size: size(px(500.), px(500.)),
- },
- cx,
- )
+ cx.with_element_context(|cx| {
+ cx.with_view_id(view.entity_id(), |cx| {
+ element.compute_layout(
+ Bounds {
+ origin: point(px(500.), px(500.)),
+ size: size(px(500.), px(500.)),
+ },
+ cx,
+ )
+ })
})
})
.unwrap();
@@ -3575,14 +3679,16 @@ mod tests {
let mut element = EditorElement::new(&editor, style);
let state = cx
.update_window(window.into(), |view, cx| {
- cx.with_view_id(view.entity_id(), |cx| {
- element.compute_layout(
- Bounds {
- origin: point(px(500.), px(500.)),
- size: size(px(500.), px(500.)),
- },
- cx,
- )
+ cx.with_element_context(|cx| {
+ cx.with_view_id(view.entity_id(), |cx| {
+ element.compute_layout(
+ Bounds {
+ origin: point(px(500.), px(500.)),
+ size: size(px(500.), px(500.)),
+ },
+ cx,
+ )
+ })
})
})
.unwrap();
@@ -3600,8 +3706,10 @@ mod tests {
// Don't panic.
let bounds = Bounds::<Pixels>::new(Default::default(), size);
- cx.update_window(window.into(), |_, cx| element.paint(bounds, &mut (), cx))
- .unwrap()
+ cx.update_window(window.into(), |_, cx| {
+ cx.with_element_context(|cx| element.paint(bounds, &mut (), cx))
+ })
+ .unwrap()
}
#[gpui::test]
@@ -3776,13 +3884,15 @@ mod tests {
.unwrap();
let layout_state = cx
.update_window(window.into(), |_, cx| {
- element.compute_layout(
- Bounds {
- origin: point(px(500.), px(500.)),
- size: size(px(500.), px(500.)),
- },
- cx,
- )
+ cx.with_element_context(|cx| {
+ element.compute_layout(
+ Bounds {
+ origin: point(px(500.), px(500.)),
+ size: size(px(500.), px(500.)),
+ },
+ cx,
+ )
+ })
})
.unwrap();
@@ -3837,24 +3947,14 @@ fn compute_auto_height_layout(
.width;
let mut snapshot = editor.snapshot(cx);
- let gutter_width;
- let gutter_margin;
- if snapshot.show_gutter {
- let descent = cx.text_system().descent(font_id, font_size);
- let gutter_padding_factor = 3.5;
- let gutter_padding = (em_width * gutter_padding_factor).round();
- gutter_width = max_line_number_width + gutter_padding * 2.0;
- gutter_margin = -descent;
- } else {
- gutter_width = Pixels::ZERO;
- gutter_margin = Pixels::ZERO;
- };
+ let gutter_dimensions =
+ snapshot.gutter_dimensions(font_id, font_size, em_width, max_line_number_width, cx);
- editor.gutter_width = gutter_width;
- let text_width = width - gutter_width;
+ editor.gutter_width = gutter_dimensions.width;
+ let text_width = width - gutter_dimensions.width;
let overscroll = size(em_width, px(0.));
- let editor_width = text_width - gutter_margin - overscroll.width - em_width;
+ let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width;
if editor.set_wrap_width(Some(editor_width), cx) {
snapshot = editor.snapshot(cx);
}
@@ -247,7 +247,7 @@ fn show_hover(
};
// query the LSP for hover info
- let hover_request = cx.update(|_, cx| {
+ let hover_request = cx.update(|cx| {
project.update(cx, |project, cx| {
project.hover(&buffer, buffer_position, cx)
})
@@ -545,6 +545,7 @@ impl DiagnosticPopover {
div()
.id("diagnostic")
+ .elevation_2(cx)
.overflow_y_scroll()
.px_2()
.py_1()
@@ -72,7 +72,7 @@ impl GitRepository for LibGitRepository {
// This check is required because index.get_path() unwraps internally :(
check_path_to_repo_path_errors(relative_file_path)?;
- let oid = match index.get_path(&relative_file_path, STAGE_NORMAL) {
+ let oid = match index.get_path(relative_file_path, STAGE_NORMAL) {
Some(entry) => entry.id,
None => return Ok(None),
};
@@ -81,7 +81,7 @@ impl GitRepository for LibGitRepository {
Ok(Some(String::from_utf8(content)?))
}
- match logic(&self, relative_file_path) {
+ match logic(self, relative_file_path) {
Ok(value) => return value,
Err(err) => log::error!("Error loading head text: {:?}", err),
}
@@ -199,7 +199,7 @@ impl GitRepository for LibGitRepository {
fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool {
if let Some(index) = repo.index().log_err() {
- if let Some(entry) = index.get_path(&path, 0) {
+ if let Some(entry) = index.get_path(path, 0) {
if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() {
if entry.mtime.seconds() == mtime.as_secs() as i32
&& entry.mtime.nanoseconds() == mtime.subsec_nanos()
@@ -165,7 +165,7 @@ impl BufferDiff {
let mut tree = SumTree::new();
let buffer_text = buffer.as_rope().to_string();
- let patch = Self::diff(&diff_base, &buffer_text);
+ let patch = Self::diff(diff_base, &buffer_text);
if let Some(patch) = patch {
let mut divergence = 0;
@@ -3,7 +3,7 @@ name = "gpui"
version = "0.1.0"
edition = "2021"
authors = ["Nathan Sobo <nathan@zed.dev>"]
-description = "The next version of Zed's GPU-accelerated UI framework"
+description = "Zed's GPU-accelerated UI framework"
publish = false
[features]
@@ -40,14 +40,25 @@ use std::any::{Any, TypeId};
/// register_action!(Paste);
/// ```
pub trait Action: 'static {
+ /// Clone the action into a new box
fn boxed_clone(&self) -> Box<dyn Action>;
+
+ /// Cast the action to the any type
fn as_any(&self) -> &dyn Any;
+
+ /// Do a partial equality check on this action and the other
fn partial_eq(&self, action: &dyn Action) -> bool;
+
+ /// Get the name of this action, for displaying in UI
fn name(&self) -> &str;
+ /// Get the name of this action for debugging
fn debug_name() -> &'static str
where
Self: Sized;
+
+ /// Build this action from a JSON value. This is used to construct actions from the keymap.
+ /// A value of `{}` will be passed for actions that don't have any parameters.
fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
where
Self: Sized;
@@ -62,6 +73,7 @@ impl std::fmt::Debug for dyn Action {
}
impl dyn Action {
+ /// Get the type id of this action
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
@@ -170,6 +182,7 @@ impl ActionRegistry {
macro_rules! actions {
($namespace:path, [ $($name:ident),* $(,)? ]) => {
$(
+ /// The `$name` action see [`gpui::actions!`]
#[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)]
#[serde(crate = "gpui::private::serde")]
pub struct $name;
@@ -1,5 +1,3 @@
-#![deny(missing_docs)]
-
mod async_context;
mod entity_map;
mod model_context;
@@ -864,7 +862,7 @@ impl AppContext {
.unwrap()
}
- /// Set the value of the global of the given type.
+ /// Sets the value of the global of the given type.
pub fn set_global<G: Any>(&mut self, global: G) {
let global_type = TypeId::of::<G>();
self.push_effect(Effect::NotifyGlobalObservers { global_type });
@@ -889,7 +887,7 @@ impl AppContext {
.unwrap()
}
- /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides
+ /// Updates the global of the given type with a closure. Unlike `global_mut`, this method provides
/// your closure with mutable access to the `AppContext` and the global simultaneously.
pub fn update_global<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
self.update(|cx| {
@@ -1106,7 +1104,7 @@ impl AppContext {
.contains_key(&action.as_any().type_id())
}
- /// Set the menu bar for this application. This will replace any existing menu bar.
+ /// Sets the menu bar for this application. This will replace any existing menu bar.
pub fn set_menus(&mut self, menus: Vec<Menu>) {
self.platform.set_menus(menus, &self.keymap.lock());
}
@@ -1190,7 +1188,7 @@ impl Context for AppContext {
})
}
- /// Update the entity referenced by the given model. The function is passed a mutable reference to the
+ /// Updates the entity referenced by the given model. The function is passed a mutable reference to the
/// entity along with a `ModelContext` for the entity.
fn update_model<T: 'static, R>(
&mut self,
@@ -213,7 +213,12 @@ impl AsyncWindowContext {
}
/// A convenience method for [WindowContext::update()]
- pub fn update<R>(
+ pub fn update<R>(&mut self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
+ self.app.update_window(self.window, |_, cx| update(cx))
+ }
+
+ /// A convenience method for [WindowContext::update()]
+ pub fn update_root<R>(
&mut self,
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
) -> Result<R> {
@@ -104,7 +104,7 @@ impl EntityMap {
}
}
- /// Return an entity after moving it to the stack.
+ /// Returns an entity after moving it to the stack.
pub fn end_lease<T>(&mut self, mut lease: Lease<T>) {
self.entities
.insert(lease.model.entity_id, lease.entity.take().unwrap());
@@ -391,7 +391,7 @@ impl<T: 'static> Model<T> {
cx.read_model(self, f)
}
- /// Update the entity referenced by this model with the given function.
+ /// Updates the entity referenced by this model with the given function.
///
/// The update function receives a context appropriate for its environment.
/// When updating in an `AppContext`, it receives a `ModelContext`.
@@ -571,7 +571,7 @@ impl<T: 'static> WeakModel<T> {
Model::upgrade_from(self)
}
- /// Update the entity referenced by this model with the given function if
+ /// Updates the entity referenced by this model with the given function if
/// the referenced entity still exists. Returns an error if the entity has
/// been released.
pub fn update<C, R>(
@@ -189,7 +189,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
}
}
- /// Update the given global
+ /// Updates the given global
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
G: 'static,
@@ -1,5 +1,3 @@
-#![deny(missing_docs)]
-
use crate::{
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
@@ -352,7 +350,7 @@ impl TestAppContext {
}
/// Returns the `TestWindow` backing the given handle.
- pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
+ pub(crate) fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
self.app
.borrow_mut()
.windows
@@ -578,7 +576,7 @@ impl<'a> VisualTestContext {
self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
}
- /// Create a new VisualTestContext. You would typically shadow the passed in
+ /// Creates a new VisualTestContext. You would typically shadow the passed in
/// TestAppContext with this, as this is typically more useful.
/// `let cx = VisualTestContext::from_window(window, cx);`
pub fn from_window(window: AnyWindowHandle, cx: &TestAppContext) -> Self {
@@ -642,8 +640,11 @@ impl<'a> VisualTestContext {
.as_ref()
.expect("Can't draw to this window without a root view")
.entity_id();
- cx.with_view_id(entity_id, |cx| {
- f(cx).draw(origin, space, cx);
+
+ cx.with_element_context(|cx| {
+ cx.with_view_id(entity_id, |cx| {
+ f(cx).draw(origin, space, cx);
+ })
});
cx.refresh();
@@ -8,8 +8,12 @@ use std::{
sync::atomic::{AtomicUsize, Ordering::SeqCst},
};
+/// A source of assets for this app to use.
pub trait AssetSource: 'static + Send + Sync {
+ /// Load the given asset from the source path.
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
+
+ /// List the assets at the given path.
fn list(&self, path: &str) -> Result<Vec<SharedString>>;
}
@@ -26,15 +30,19 @@ impl AssetSource for () {
}
}
+/// A unique identifier for the image cache
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ImageId(usize);
+/// A cached and processed image.
pub struct ImageData {
+ /// The ID associated with this image
pub id: ImageId,
data: ImageBuffer<Bgra<u8>, Vec<u8>>,
}
impl ImageData {
+ /// Create a new image from the given data.
pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
@@ -44,10 +52,12 @@ impl ImageData {
}
}
+ /// Convert this image into a byte slice.
pub fn as_bytes(&self) -> &[u8] {
&self.data
}
+ /// Get the size of this image, in pixels
pub fn size(&self) -> Size<DevicePixels> {
let (width, height) = self.data.dimensions();
size(width.into(), height.into())
@@ -2,6 +2,7 @@ use anyhow::bail;
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
+/// Convert an RGB hex color code number to a color type
pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
@@ -9,6 +10,7 @@ pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
Rgba { r, g, b, a: 1.0 }.into()
}
+/// Convert an RGBA hex color code number to [`Rgba`]
pub fn rgba(hex: u32) -> Rgba {
let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
@@ -17,11 +19,16 @@ pub fn rgba(hex: u32) -> Rgba {
Rgba { r, g, b, a }
}
+/// An RGBA color
#[derive(PartialEq, Clone, Copy, Default)]
pub struct Rgba {
+ /// The red component of the color, in the range 0.0 to 1.0
pub r: f32,
+ /// The green component of the color, in the range 0.0 to 1.0
pub g: f32,
+ /// The blue component of the color, in the range 0.0 to 1.0
pub b: f32,
+ /// The alpha component of the color, in the range 0.0 to 1.0
pub a: f32,
}
@@ -32,6 +39,8 @@ impl fmt::Debug for Rgba {
}
impl Rgba {
+ /// Create a new [`Rgba`] color by blending this and another color together
+ /// TODO!(docs): find the source for this algorithm
pub fn blend(&self, other: Rgba) -> Self {
if other.a >= 1.0 {
other
@@ -165,12 +174,20 @@ impl TryFrom<&'_ str> for Rgba {
}
}
+/// An HSLA color
#[derive(Default, Copy, Clone, Debug)]
#[repr(C)]
pub struct Hsla {
+ /// Hue, in a range from 0 to 1
pub h: f32,
+
+ /// Saturation, in a range from 0 to 1
pub s: f32,
+
+ /// Lightness, in a range from 0 to 1
pub l: f32,
+
+ /// Alpha, in a range from 0 to 1
pub a: f32,
}
@@ -203,38 +220,9 @@ impl Ord for Hsla {
}
}
-impl Hsla {
- pub fn to_rgb(self) -> Rgba {
- self.into()
- }
-
- pub fn red() -> Self {
- red()
- }
-
- pub fn green() -> Self {
- green()
- }
-
- pub fn blue() -> Self {
- blue()
- }
-
- pub fn black() -> Self {
- black()
- }
-
- pub fn white() -> Self {
- white()
- }
-
- pub fn transparent_black() -> Self {
- transparent_black()
- }
-}
-
impl Eq for Hsla {}
+/// Construct an [`Hsla`] object from plain values
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla {
h: h.clamp(0., 1.),
@@ -244,6 +232,7 @@ pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
}
}
+/// Pure black in [`Hsla`]
pub fn black() -> Hsla {
Hsla {
h: 0.,
@@ -253,6 +242,7 @@ pub fn black() -> Hsla {
}
}
+/// Transparent black in [`Hsla`]
pub fn transparent_black() -> Hsla {
Hsla {
h: 0.,
@@ -262,6 +252,7 @@ pub fn transparent_black() -> Hsla {
}
}
+/// Pure white in [`Hsla`]
pub fn white() -> Hsla {
Hsla {
h: 0.,
@@ -271,6 +262,7 @@ pub fn white() -> Hsla {
}
}
+/// The color red in [`Hsla`]
pub fn red() -> Hsla {
Hsla {
h: 0.,
@@ -280,6 +272,7 @@ pub fn red() -> Hsla {
}
}
+/// The color blue in [`Hsla`]
pub fn blue() -> Hsla {
Hsla {
h: 0.6,
@@ -289,6 +282,7 @@ pub fn blue() -> Hsla {
}
}
+/// The color green in [`Hsla`]
pub fn green() -> Hsla {
Hsla {
h: 0.33,
@@ -298,6 +292,7 @@ pub fn green() -> Hsla {
}
}
+/// The color yellow in [`Hsla`]
pub fn yellow() -> Hsla {
Hsla {
h: 0.16,
@@ -308,6 +303,41 @@ pub fn yellow() -> Hsla {
}
impl Hsla {
+ /// Converts this HSLA color to an RGBA color.
+ pub fn to_rgb(self) -> Rgba {
+ self.into()
+ }
+
+ /// The color red
+ pub fn red() -> Self {
+ red()
+ }
+
+ /// The color green
+ pub fn green() -> Self {
+ green()
+ }
+
+ /// The color blue
+ pub fn blue() -> Self {
+ blue()
+ }
+
+ /// The color black
+ pub fn black() -> Self {
+ black()
+ }
+
+ /// The color white
+ pub fn white() -> Self {
+ white()
+ }
+
+ /// The color transparent black
+ pub fn transparent_black() -> Self {
+ transparent_black()
+ }
+
/// Returns true if the HSLA color is fully transparent, false otherwise.
pub fn is_transparent(&self) -> bool {
self.a == 0.0
@@ -339,6 +369,7 @@ impl Hsla {
}
}
+ /// Returns a new HSLA color with the same hue, and lightness, but with no saturation.
pub fn grayscale(&self) -> Self {
Hsla {
h: self.h,
@@ -1,26 +1,69 @@
+//! Elements are the workhorses of GPUI. They are responsible for laying out and painting all of
+//! the contents of a window. Elements form a tree and are laid out according to the web layout
+//! standards as implemented by [taffy](https://github.com/DioxusLabs/taffy). Most of the time,
+//! you won't need to interact with this module or these APIs directly. Elements provide their
+//! own APIs and GPUI, or other element implementation, uses the APIs in this module to convert
+//! that element tree into the pixels you see on the screen.
+//!
+//! # Element Basics
+//!
+//! Elements are constructed by calling [`Render::render()`] on the root view of the window, which
+//! which recursively constructs the element tree from the current state of the application,.
+//! These elements are then laid out by Taffy, and painted to the screen according to their own
+//! implementation of [`Element::paint()`]. Before the start of the next frame, the entire element
+//! tree and any callbacks they have registered with GPUI are dropped and the process repeats.
+//!
+//! But some state is too simple and voluminous to store in every view that needs it, e.g.
+//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
+//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
+//! appears in the same place relative to other views and ElementIds in the frame, then the previous
+//! frame's state will be passed to the element's layout and paint methods.
+//!
+//! # Implementing your own elements
+//!
+//! Elements are intended to be the low level, imperative API to GPUI. They are responsible for upholding,
+//! or breaking, GPUI's features as they deem necessary. As an example, most GPUI elements are expected
+//! to stay in the bounds that their parent element gives them. But with [`WindowContext::break_content_mask`],
+//! you can ignore this restriction and paint anywhere inside of the window's bounds. This is useful for overlays
+//! and popups and anything else that shows up 'on top' of other elements.
+//! With great power, comes great responsibility.
+//!
+//! However, most of the time, you won't need to implement your own elements. GPUI provides a number of
+//! elements that should cover most common use cases out of the box and it's recommended that you use those
+//! to construct `components`, using the [`RenderOnce`] trait and the `#[derive(IntoElement)]` macro. Only implement
+//! elements when you need to take manual control of the layout and painting process, such as when using
+//! your own custom layout algorithm or rendering a code editor.
+
use crate::{
- ArenaBox, AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size,
- ViewContext, WindowContext, ELEMENT_ARENA,
+ util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
+ Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug};
+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.
-/// Rather than calling methods on implementers of this trait directly, you'll usually call `into_any` to convert them into an AnyElement, which manages state internally.
-/// You can create custom elements by implementing this trait.
+/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
+/// You can create custom elements by implementing this trait, see the module-level documentation
+/// for more details.
pub trait Element: 'static + IntoElement {
+ /// The type of state to store for this element between frames. See the module-level documentation
+ /// for details.
type State: 'static;
+ /// Before an element can be painted, we need to know where it's going to be and how big it is.
+ /// Use this method to request a layout from Taffy and initialize the element's state.
fn request_layout(
&mut self,
state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State);
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
+ /// Once layout has been completed, this method will be called to paint the element to the screen.
+ /// 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);
+ /// Convert this element into a dynamically-typed [`AnyElement`].
fn into_any(self) -> AnyElement {
AnyElement::new(self)
}
@@ -29,6 +72,7 @@ pub trait Element: 'static + IntoElement {
/// Implemented by any type that can be converted into an element.
pub trait IntoElement: Sized {
/// The specific type of element into which the implementing type is converted.
+ /// Useful for converting other types into elements automatically, like Strings
type Element: Element;
/// The [`ElementId`] of self once converted into an [`Element`].
@@ -51,8 +95,8 @@ pub trait IntoElement: Sized {
self,
origin: Point<Pixels>,
available_space: Size<T>,
- cx: &mut WindowContext,
- f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
+ cx: &mut ElementContext,
+ f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
) -> R
where
T: Clone + Default + Debug + Into<AvailableSpace>,
@@ -77,41 +121,14 @@ pub trait IntoElement: Sized {
})
}
}
-
- /// Convert self to another type by calling the given closure. Useful in rendering code.
- fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
- where
- Self: Sized,
- U: IntoElement,
- {
- f(self)
- }
-
- /// Conditionally chain onto self with the given closure. Useful in rendering code.
- fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
- where
- Self: Sized,
- {
- self.map(|this| if condition { then(this) } else { this })
- }
-
- /// Conditionally chain onto self with the given closure if the given option is Some.
- /// The contents of the option are provided to the closure.
- fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
- where
- Self: Sized,
- {
- self.map(|this| {
- if let Some(value) = option {
- then(this, value)
- } else {
- this
- }
- })
- }
}
+impl<T: IntoElement> FluentBuilder for T {}
+
+/// An object that can be drawn to the screen. This is the trait that distinguishes `Views` from
+/// models. Views are drawn to the screen and care about the current window's state, models are not and do not.
pub trait Render: 'static + Sized {
+ /// Render this view into an element tree.
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
}
@@ -122,35 +139,49 @@ impl Render for () {
}
/// You can derive [`IntoElement`] on any type that implements this trait.
-/// It is used to allow views to be expressed in terms of abstract data.
+/// It is used to construct reusable `components` out of plain data. Think of
+/// components as a recipe for a certain pattern of elements. RenderOnce allows
+/// you to invoke this pattern, without breaking the fluent builder pattern of
+/// the element APIs.
pub trait RenderOnce: 'static {
+ /// Render this component into an element tree. Note that this method
+ /// takes ownership of self, as compared to [`Render::render()`] method
+ /// which takes a mutable reference.
fn render(self, cx: &mut WindowContext) -> impl IntoElement;
}
+/// This is a helper trait to provide a uniform interface for constructing elements that
+/// can accept any number of any kind of child elements
pub trait ParentElement {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>;
+ /// Extend this element's children with the given child elements.
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>);
+ /// Add a single child element to this element.
fn child(mut self, child: impl IntoElement) -> Self
where
Self: Sized,
{
- self.children_mut().push(child.into_element().into_any());
+ self.extend(std::iter::once(child.into_element().into_any()));
self
}
+ /// Add multiple child elements to this element.
fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self
where
Self: Sized,
{
- self.children_mut()
- .extend(children.into_iter().map(|child| child.into_any_element()));
+ self.extend(children.into_iter().map(|child| child.into_any_element()));
self
}
}
+/// An element for rendering components. An implementation detail of the [`IntoElement`] derive macro
+/// for [`RenderOnce`]
+#[doc(hidden)]
pub struct Component<C: RenderOnce>(Option<C>);
impl<C: RenderOnce> Component<C> {
+ /// Create a new component from the given RenderOnce type.
pub fn new(component: C) -> Self {
Component(Some(component))
}
@@ -162,14 +193,19 @@ impl<C: RenderOnce> Element for Component<C> {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
- let mut element = self.0.take().unwrap().render(cx).into_any_element();
+ let mut element = self
+ .0
+ .take()
+ .unwrap()
+ .render(cx.deref_mut())
+ .into_any_element();
let layout_id = element.request_layout(cx);
(layout_id, element)
}
- fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
element.paint(cx)
}
}
@@ -186,31 +222,33 @@ impl<C: RenderOnce> IntoElement for Component<C> {
}
}
+/// A globally unique identifier for an element, used to track state across frames.
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
+pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
trait ElementObject {
fn element_id(&self) -> Option<ElementId>;
- fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId;
+ fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
- fn paint(&mut self, cx: &mut WindowContext);
+ fn paint(&mut self, cx: &mut ElementContext);
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
);
}
-pub struct DrawableElement<E: Element> {
+/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
+pub(crate) struct DrawableElement<E: Element> {
element: Option<E>,
phase: ElementDrawPhase<E::State>,
}
@@ -243,7 +281,7 @@ impl<E: Element> DrawableElement<E> {
self.element.as_ref()?.element_id()
}
- fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
+ fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
{
let layout_id = cx.with_element_state(id, |element_state, cx| {
@@ -265,7 +303,7 @@ impl<E: Element> DrawableElement<E> {
layout_id
}
- fn paint(mut self, cx: &mut WindowContext) -> Option<E::State> {
+ fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
match self.phase {
ElementDrawPhase::LayoutRequested {
layout_id,
@@ -310,7 +348,7 @@ impl<E: Element> DrawableElement<E> {
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.request_layout(cx);
@@ -351,7 +389,7 @@ impl<E: Element> DrawableElement<E> {
mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Option<E::State> {
self.measure(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
@@ -367,18 +405,18 @@ where
self.as_ref().unwrap().element_id()
}
- fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
+ fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
}
- fn paint(&mut self, cx: &mut WindowContext) {
+ fn paint(&mut self, cx: &mut ElementContext) {
DrawableElement::paint(self.take().unwrap(), cx);
}
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Size<Pixels> {
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
}
@@ -387,16 +425,17 @@ where
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
}
}
+/// A dynamically typed element that can be used to store any element type.
pub struct AnyElement(ArenaBox<dyn ElementObject>);
impl AnyElement {
- pub fn new<E>(element: E) -> Self
+ pub(crate) fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::State: Any,
@@ -407,11 +446,14 @@ impl AnyElement {
AnyElement(element)
}
- pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
+ /// Request the layout ID of the element stored in this `AnyElement`.
+ /// Used for laying out child elements in a parent element.
+ pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
self.0.request_layout(cx)
}
- pub fn paint(&mut self, cx: &mut WindowContext) {
+ /// Paints the element stored in this `AnyElement`.
+ pub fn paint(&mut self, cx: &mut ElementContext) {
self.0.paint(cx)
}
@@ -419,7 +461,7 @@ impl AnyElement {
pub fn measure(
&mut self,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Size<Pixels> {
self.0.measure(available_space, cx)
}
@@ -429,11 +471,12 @@ impl AnyElement {
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
self.0.draw(origin, available_space, cx)
}
+ /// Returns the element ID of the element stored in this `AnyElement`, if any.
pub fn inner_id(&self) -> Option<ElementId> {
self.0.element_id()
}
@@ -445,13 +488,13 @@ impl Element for AnyElement {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let layout_id = self.request_layout(cx);
(layout_id, ())
}
- fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
self.paint(cx)
}
}
@@ -493,7 +536,7 @@ impl Element for () {
fn request_layout(
&mut self,
_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
(cx.request_layout(&crate::Style::default(), None), ())
}
@@ -502,7 +545,7 @@ impl Element for () {
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::State,
- _cx: &mut WindowContext,
+ _cx: &mut ElementContext,
) {
}
}
@@ -1,16 +1,20 @@
use refineable::Refineable as _;
-use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
+use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled};
-pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut WindowContext)) -> Canvas {
+/// 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 {
Canvas {
paint_callback: Some(Box::new(callback)),
style: StyleRefinement::default(),
}
}
+/// 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 WindowContext)>>,
+ paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
style: StyleRefinement,
}
@@ -32,7 +36,7 @@ impl Element for Canvas {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
let mut style = Style::default();
style.refine(&self.style);
@@ -40,7 +44,7 @@ impl Element for Canvas {
(layout_id, style)
}
- fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
style.paint(bounds, cx, |cx| {
(self.paint_callback.take().unwrap())(&bounds, cx)
});
@@ -1,6 +1,30 @@
+//! Div is the central, reusable element that most GPUI trees will be built from.
+//! It functions as a container for other elements, and provides a number of
+//! useful features for laying out and styling its children as well as binding
+//! mouse events and action handlers. It is meant to be similar to the HTML <div>
+//! element, but for GPUI.
+//!
+//! # Build your own div
+//!
+//! GPUI does not directly provide APIs for stateful, multi step events like `click`
+//! and `drag`. We want GPUI users to be able to build their own abstractions for
+//! their own needs. However, as a UI framework, we're also obliged to provide some
+//! building blocks to make the process of building your own elements easier.
+//! For this we have the [`Interactivity`] and the [`StyleRefinement`] structs, as well
+//! as several associated traits. Together, these provide the full suite of Dom-like events
+//! and Tailwind-like styling that you can use to build your own custom elements. Div is
+//! constructed by combining these two systems into an all-in-one element.
+//!
+//! # Capturing and bubbling
+//!
+//! Note that while event dispatch in GPUI uses similar names and concepts to the web
+//! even API, the details are very different. See the documentation in [TODO!(docs)
+//! DOCUMENT EVENT DISPATCH SOMEWHERE IN WINDOW CONTEXT] for more details
+//!
+
use crate::{
- point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
- BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement,
+ point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
+ ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, IntoElement,
IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent,
SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility,
@@ -17,6 +41,7 @@ use std::{
fmt::Debug,
marker::PhantomData,
mem,
+ ops::DerefMut,
rc::Rc,
time::Duration,
};
@@ -24,20 +49,30 @@ use taffy::style::Overflow;
use util::ResultExt;
const DRAG_THRESHOLD: f64 = 2.;
-const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
+pub(crate) const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
+/// The styling information for a given group.
pub struct GroupStyle {
+ /// The identifier for this group.
pub group: SharedString,
+
+ /// The specific style refinement that this group would apply
+ /// to its children.
pub style: Box<StyleRefinement>,
}
+/// An event for when a drag is moving over this element, with the given state type.
pub struct DragMoveEvent<T> {
+ /// The mouse move event that triggered this drag move event.
pub event: MouseMoveEvent,
+
+ /// The bounds of this element.
pub bounds: Bounds<Pixels>,
drag: PhantomData<T>,
}
impl<T: 'static> DragMoveEvent<T> {
+ /// Returns the drag state for this event.
pub fn drag<'b>(&self, cx: &'b AppContext) -> &'b T {
cx.active_drag
.as_ref()
@@ -47,6 +82,10 @@ impl<T: 'static> DragMoveEvent<T> {
}
impl Interactivity {
+ /// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase
+ /// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to the view state from this callback
pub fn on_mouse_down(
&mut self,
button: MouseButton,
@@ -63,6 +102,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse down event for any button, during the capture phase
+ /// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn capture_any_mouse_down(
&mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -75,6 +118,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse down event for any button, during the bubble phase
+ /// the imperative API equivalent to [`InteractiveElement::on_any_mouse_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_any_mouse_down(
&mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -87,6 +134,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse up event for the given button, during the bubble phase
+ /// the imperative API equivalent to [`InteractiveElement::on_mouse_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_mouse_up(
&mut self,
button: MouseButton,
@@ -103,6 +154,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse up event for any button, during the capture phase
+ /// the imperative API equivalent to [`InteractiveElement::capture_any_mouse_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn capture_any_mouse_up(
&mut self,
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
@@ -115,6 +170,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse up event for any button, during the bubble phase
+ /// the imperative API equivalent to [`InteractiveElement::on_any_mouse_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_any_mouse_up(
&mut self,
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
@@ -127,6 +186,11 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse down event, on any button, during the capture phase,
+ /// when the mouse is outside of the bounds of this element.
+ /// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_mouse_down_out(
&mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -140,6 +204,11 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
+ /// when the mouse is outside of the bounds of this element.
+ /// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_mouse_up_out(
&mut self,
button: MouseButton,
@@ -156,6 +225,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse move event, during the bubble phase
+ /// The imperative API equivalent to [`InteractiveElement::on_mouse_move()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_mouse_move(
&mut self,
listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
@@ -168,6 +241,13 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to the mouse drag event of the given type. Note that this
+ /// will be called for all move events, inside or outside of this element, as long as the
+ /// drag was started with this element under the mouse. Useful for implementing draggable
+ /// UIs that don't conform to a drag and drop style interaction, like resizing.
+ /// The imperative API equivalent to [`InteractiveElement::on_drag_move()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_drag_move<T>(
&mut self,
listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
@@ -194,6 +274,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to scroll wheel events during the bubble phase
+ /// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_scroll_wheel(
&mut self,
listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
@@ -206,6 +290,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to an action dispatch during the capture phase
+ /// The imperative API equivalent to [`InteractiveElement::capture_action()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn capture_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
@@ -221,6 +309,10 @@ impl Interactivity {
));
}
+ /// Bind the given callback to an action dispatch during the bubble phase
+ /// The imperative API equivalent to [`InteractiveElement::on_action()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_action<A: Action>(&mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) {
self.action_listeners.push((
TypeId::of::<A>(),
@@ -233,6 +325,12 @@ impl Interactivity {
));
}
+ /// Bind the given callback to an action dispatch, based on a dynamic action parameter
+ /// instead of a type parameter. Useful for component libraries that want to expose
+ /// action bindings to their users.
+ /// The imperative API equivalent to [`InteractiveElement::on_boxed_action()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_boxed_action(
&mut self,
action: &dyn Action,
@@ -249,6 +347,10 @@ impl Interactivity {
));
}
+ /// Bind the given callback to key down events during the bubble phase
+ /// The imperative API equivalent to [`InteractiveElement::on_key_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_key_down(&mut self, listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static) {
self.key_down_listeners
.push(Box::new(move |event, phase, cx| {
@@ -258,6 +360,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to key down events during the capture phase
+ /// The imperative API equivalent to [`InteractiveElement::capture_key_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn capture_key_down(
&mut self,
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
@@ -270,6 +376,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to key up events during the bubble phase
+ /// The imperative API equivalent to [`InteractiveElement::on_key_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
self.key_up_listeners
.push(Box::new(move |event, phase, cx| {
@@ -279,6 +389,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to key up events during the capture phase
+ /// The imperative API equivalent to [`InteractiveElement::on_key_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn capture_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) {
self.key_up_listeners
.push(Box::new(move |event, phase, cx| {
@@ -288,6 +402,10 @@ impl Interactivity {
}));
}
+ /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
+ /// The imperative API equivalent to [`InteractiveElement::on_drop()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_drop<T: 'static>(&mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) {
self.drop_listeners.push((
TypeId::of::<T>(),
@@ -297,10 +415,16 @@ impl Interactivity {
));
}
+ /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
+ /// The imperative API equivalent to [`InteractiveElement::can_drop()`]
pub fn can_drop(&mut self, predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static) {
self.can_drop_predicate = Some(Box::new(predicate));
}
+ /// Bind the given callback to click events of this element
+ /// The imperative API equivalent to [`InteractiveElement::on_click()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static)
where
Self: Sized,
@@ -309,6 +433,12 @@ impl Interactivity {
.push(Box::new(move |event, cx| listener(event, cx)));
}
+ /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
+ /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
+ /// the [`Self::on_drag_move()`] API
+ /// The imperative API equivalent to [`InteractiveElement::on_drag()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_drag<T, W>(
&mut self,
value: T,
@@ -328,6 +458,11 @@ impl Interactivity {
));
}
+ /// Bind the given callback on the hover start and end events of this element. Note that the boolean
+ /// passed to the callback is true when the hover starts and false when it ends.
+ /// The imperative API equivalent to [`InteractiveElement::on_drag()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static)
where
Self: Sized,
@@ -339,6 +474,8 @@ impl Interactivity {
self.hover_listener = Some(Box::new(listener));
}
+ /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
+ /// The imperative API equivalent to [`InteractiveElement::tooltip()`]
pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static)
where
Self: Sized,
@@ -350,31 +487,43 @@ impl Interactivity {
self.tooltip_builder = Some(Rc::new(build_tooltip));
}
+ /// Block the mouse from interacting with this element or any of it's children
+ /// The imperative API equivalent to [`InteractiveElement::block_mouse()`]
pub fn block_mouse(&mut self) {
self.block_mouse = true;
}
}
+/// A trait for elements that want to use the standard GPUI event handlers that don't
+/// require any state.
pub trait InteractiveElement: Sized {
+ /// Retrieve the interactivity state associated with this element
fn interactivity(&mut self) -> &mut Interactivity;
+ /// Assign this element to a group of elements that can be styled together
fn group(mut self, group: impl Into<SharedString>) -> Self {
self.interactivity().group = Some(group.into());
self
}
+ /// Assign this elements
fn id(mut self, id: impl Into<ElementId>) -> Stateful<Self> {
self.interactivity().element_id = Some(id.into());
Stateful { element: self }
}
+ /// Track the focus state of the given focus handle on this element.
+ /// If the focus handle is focused by the application, this element will
+ /// apply it's focused styles.
fn track_focus(mut self, focus_handle: &FocusHandle) -> Focusable<Self> {
self.interactivity().focusable = true;
self.interactivity().tracked_focus_handle = Some(focus_handle.clone());
Focusable { element: self }
}
+ /// Set the keymap context for this element. This will be used to determine
+ /// which action to dispatch from the keymap.
fn key_context<C, E>(mut self, key_context: C) -> Self
where
C: TryInto<KeyContext, Error = E>,
@@ -386,6 +535,7 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Apply the given style to this element when the mouse hovers over it
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
debug_assert!(
self.interactivity().hover_style.is_none(),
@@ -395,6 +545,7 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Apply the given style to this element when the mouse hovers over a group member
fn group_hover(
mut self,
group_name: impl Into<SharedString>,
@@ -407,6 +558,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse down event for the given mouse button,
+ /// the fluent API equivalent to [`Interactivity::on_mouse_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to the view state from this callback
fn on_mouse_down(
mut self,
button: MouseButton,
@@ -417,17 +572,27 @@ pub trait InteractiveElement: Sized {
}
#[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
fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self {
self.interactivity().debug_selector = Some(f());
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()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn capture_any_mouse_down(
mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -436,6 +601,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse down event for any button, during the capture phase
+ /// the fluent API equivalent to [`Interactivity::on_any_mouse_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_any_mouse_down(
mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -444,6 +613,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse up event for the given button, during the bubble phase
+ /// the fluent API equivalent to [`Interactivity::on_mouse_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_mouse_up(
mut self,
button: MouseButton,
@@ -453,6 +626,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse up event for any button, during the capture phase
+ /// the fluent API equivalent to [`Interactivity::capture_any_mouse_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn capture_any_mouse_up(
mut self,
listener: impl Fn(&MouseUpEvent, &mut WindowContext) + 'static,
@@ -461,6 +638,11 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse down event, on any button, during the capture phase,
+ /// when the mouse is outside of the bounds of this element.
+ /// The fluent API equivalent to [`Interactivity::on_mouse_down_out()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_mouse_down_out(
mut self,
listener: impl Fn(&MouseDownEvent, &mut WindowContext) + 'static,
@@ -469,6 +651,11 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse up event, for the given button, during the capture phase,
+ /// when the mouse is outside of the bounds of this element.
+ /// The fluent API equivalent to [`Interactivity::on_mouse_up_out()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_mouse_up_out(
mut self,
button: MouseButton,
@@ -478,6 +665,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse move event, during the bubble phase
+ /// The fluent API equivalent to [`Interactivity::on_mouse_move()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_mouse_move(
mut self,
listener: impl Fn(&MouseMoveEvent, &mut WindowContext) + 'static,
@@ -486,6 +677,13 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to the mouse drag event of the given type. Note that this
+ /// will be called for all move events, inside or outside of this element, as long as the
+ /// drag was started with this element under the mouse. Useful for implementing draggable
+ /// UIs that don't conform to a drag and drop style interaction, like resizing.
+ /// The fluent API equivalent to [`Interactivity::on_drag_move()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_drag_move<T: 'static>(
mut self,
listener: impl Fn(&DragMoveEvent<T>, &mut WindowContext) + 'static,
@@ -497,6 +695,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to scroll wheel events during the bubble phase
+ /// The fluent API equivalent to [`Interactivity::on_scroll_wheel()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_scroll_wheel(
mut self,
listener: impl Fn(&ScrollWheelEvent, &mut WindowContext) + 'static,
@@ -506,6 +708,9 @@ pub trait InteractiveElement: Sized {
}
/// Capture the given action, before normal action dispatch can fire
+ /// The fluent API equivalent to [`Interactivity::on_scroll_wheel()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn capture_action<A: Action>(
mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
@@ -514,12 +719,21 @@ pub trait InteractiveElement: Sized {
self
}
- /// Add a listener for the given action, fires during the bubble event phase
+ /// Bind the given callback to an action dispatch during the bubble phase
+ /// The fluent API equivalent to [`Interactivity::on_action()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_action<A: Action>(mut self, listener: impl Fn(&A, &mut WindowContext) + 'static) -> Self {
self.interactivity().on_action(listener);
self
}
+ /// Bind the given callback to an action dispatch, based on a dynamic action parameter
+ /// instead of a type parameter. Useful for component libraries that want to expose
+ /// action bindings to their users.
+ /// The fluent API equivalent to [`Interactivity::on_boxed_action()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_boxed_action(
mut self,
action: &dyn Action,
@@ -529,6 +743,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to key down events during the bubble phase
+ /// The fluent API equivalent to [`Interactivity::on_key_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_key_down(
mut self,
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
@@ -537,6 +755,10 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to key down events during the capture phase
+ /// The fluent API equivalent to [`Interactivity::capture_key_down()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn capture_key_down(
mut self,
listener: impl Fn(&KeyDownEvent, &mut WindowContext) + 'static,
@@ -545,11 +767,19 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to key up events during the bubble phase
+ /// The fluent API equivalent to [`Interactivity::on_key_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_key_up(mut self, listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static) -> Self {
self.interactivity().on_key_up(listener);
self
}
+ /// Bind the given callback to key up events during the capture phase
+ /// The fluent API equivalent to [`Interactivity::capture_key_up()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn capture_key_up(
mut self,
listener: impl Fn(&KeyUpEvent, &mut WindowContext) + 'static,
@@ -558,6 +788,7 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Apply the given style when the given data type is dragged over this element
fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self {
self.interactivity()
.drag_over_styles
@@ -565,6 +796,7 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Apply the given style when the given data type is dragged over this element's group
fn group_drag_over<S: 'static>(
mut self,
group_name: impl Into<SharedString>,
@@ -580,11 +812,17 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Bind the given callback to drop events of the given type, whether or not the drag started on this element
+ /// The fluent API equivalent to [`Interactivity::on_drop()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_drop<T: 'static>(mut self, listener: impl Fn(&T, &mut WindowContext) + 'static) -> Self {
self.interactivity().on_drop(listener);
self
}
+ /// Use the given predicate to determine whether or not a drop event should be dispatched to this element
+ /// The fluent API equivalent to [`Interactivity::can_drop()`]
fn can_drop(
mut self,
predicate: impl Fn(&dyn Any, &mut WindowContext) -> bool + 'static,
@@ -593,39 +831,49 @@ pub trait InteractiveElement: Sized {
self
}
+ /// Block the mouse from interacting with this element or any of it's children
+ /// The fluent API equivalent to [`Interactivity::block_mouse()`]
fn block_mouse(mut self) -> Self {
self.interactivity().block_mouse();
self
}
}
+/// A trait for elements that want to use the standard GPUI interactivity features
+/// that require state.
pub trait StatefulInteractiveElement: InteractiveElement {
+ /// Set this element to focusable.
fn focusable(mut self) -> Focusable<Self> {
self.interactivity().focusable = true;
Focusable { element: self }
}
+ /// Set the overflow x and y to scroll.
fn overflow_scroll(mut self) -> Self {
self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
self
}
+ /// Set the overflow x to scroll.
fn overflow_x_scroll(mut self) -> Self {
self.interactivity().base_style.overflow.x = Some(Overflow::Scroll);
self
}
+ /// Set the overflow y to scroll.
fn overflow_y_scroll(mut self) -> Self {
self.interactivity().base_style.overflow.y = Some(Overflow::Scroll);
self
}
+ /// Track the scroll state of this element with the given handle.
fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
self.interactivity().scroll_handle = Some(scroll_handle.clone());
self
}
+ /// Set the given styles to be applied when this element is active.
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
@@ -634,6 +882,7 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// Set the given styles to be applied when this element's group is active.
fn group_active(
mut self,
group_name: impl Into<SharedString>,
@@ -649,6 +898,10 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// Bind the given callback to click events of this element
+ /// The fluent API equivalent to [`Interactivity::on_click()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self
where
Self: Sized,
@@ -657,6 +910,12 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// On drag initiation, this callback will be used to create a new view to render the dragged value for a
+ /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with
+ /// the [`Self::on_drag_move()`] API
+ /// The fluent API equivalent to [`Interactivity::on_drag()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_drag<T, W>(
mut self,
value: T,
@@ -671,6 +930,11 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// Bind the given callback on the hover start and end events of this element. Note that the boolean
+ /// passed to the callback is true when the hover starts and false when it ends.
+ /// The fluent API equivalent to [`Interactivity::on_hover()`]
+ ///
+ /// See [`ViewContext::listener()`] to get access to a view's state from this callback
fn on_hover(mut self, listener: impl Fn(&bool, &mut WindowContext) + 'static) -> Self
where
Self: Sized,
@@ -679,6 +943,8 @@ pub trait StatefulInteractiveElement: InteractiveElement {
self
}
+ /// Use the given callback to construct a new tooltip view when the mouse hovers over this element.
+ /// The fluent API equivalent to [`Interactivity::tooltip()`]
fn tooltip(mut self, build_tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self
where
Self: Sized,
@@ -688,7 +954,9 @@ pub trait StatefulInteractiveElement: InteractiveElement {
}
}
+/// A trait for providing focus related APIs to interactive elements
pub trait FocusableElement: InteractiveElement {
+ /// Set the given styles to be applied when this element, specifically, is focused.
fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
@@ -697,6 +965,7 @@ pub trait FocusableElement: InteractiveElement {
self
}
+ /// Set the given styles to be applied when this element is inside another element that is focused.
fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where
Self: Sized,
@@ -706,35 +975,36 @@ pub trait FocusableElement: InteractiveElement {
}
}
-pub type MouseDownListener =
+pub(crate) type MouseDownListener =
Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
-pub type MouseUpListener =
+pub(crate) type MouseUpListener =
Box<dyn Fn(&MouseUpEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
-pub type MouseMoveListener =
+pub(crate) type MouseMoveListener =
Box<dyn Fn(&MouseMoveEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
-pub type ScrollWheelListener =
+pub(crate) type ScrollWheelListener =
Box<dyn Fn(&ScrollWheelEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
-pub type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
+pub(crate) type ClickListener = Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>;
-pub type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
+pub(crate) type DragListener = Box<dyn Fn(&dyn Any, &mut WindowContext) -> AnyView + 'static>;
type DropListener = Box<dyn Fn(&dyn Any, &mut WindowContext) + 'static>;
type CanDropPredicate = Box<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>;
-pub type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
+pub(crate) type TooltipBuilder = Rc<dyn Fn(&mut WindowContext) -> AnyView + 'static>;
-pub type KeyDownListener = Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
+pub(crate) type KeyDownListener =
+ Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + 'static>;
-pub type KeyUpListener = Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
+pub(crate) type KeyUpListener =
+ Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + 'static>;
-pub type DragEventListener = Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>;
-
-pub type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
+pub(crate) type ActionListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
+/// Construct a new [`Div`] element
#[track_caller]
pub fn div() -> Div {
#[cfg(debug_assertions)]
@@ -753,6 +1023,7 @@ pub fn div() -> Div {
}
}
+/// A [`Div`] element, the all-in-one element for building complex UIs in GPUI
pub struct Div {
interactivity: Interactivity,
children: SmallVec<[AnyElement; 2]>,
@@ -771,8 +1042,8 @@ impl InteractiveElement for Div {
}
impl ParentElement for Div {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -782,7 +1053,7 @@ impl Element for Div {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let mut child_layout_ids = SmallVec::new();
let (layout_id, interactive_state) = self.interactivity.layout(
@@ -812,7 +1083,7 @@ impl Element for Div {
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
@@ -875,12 +1146,14 @@ impl IntoElement for Div {
}
}
+/// The state a div needs to keep track of between frames.
pub struct DivState {
child_layout_ids: SmallVec<[LayoutId; 2]>,
interactive_state: InteractiveElementState,
}
impl DivState {
+ /// Is the div currently being clicked on?
pub fn is_active(&self) -> bool {
self.interactive_state
.pending_mouse_down
@@ -889,56 +1162,67 @@ impl DivState {
}
}
+/// The interactivity struct. Powers all of the general-purpose
+/// interactivity in the `Div` element.
#[derive(Default)]
pub struct Interactivity {
+ /// The element ID of the element
pub element_id: Option<ElementId>,
- pub key_context: Option<KeyContext>,
- pub focusable: bool,
- pub tracked_focus_handle: Option<FocusHandle>,
- pub scroll_handle: Option<ScrollHandle>,
- pub group: Option<SharedString>,
+ pub(crate) key_context: Option<KeyContext>,
+ pub(crate) focusable: bool,
+ pub(crate) tracked_focus_handle: Option<FocusHandle>,
+ pub(crate) scroll_handle: Option<ScrollHandle>,
+ pub(crate) group: Option<SharedString>,
+ /// The base style of the element, before any modifications are applied
+ /// by focus, active, etc.
pub base_style: Box<StyleRefinement>,
- pub focus_style: Option<Box<StyleRefinement>>,
- pub in_focus_style: Option<Box<StyleRefinement>>,
- pub hover_style: Option<Box<StyleRefinement>>,
- pub group_hover_style: Option<GroupStyle>,
- pub active_style: Option<Box<StyleRefinement>>,
- pub group_active_style: Option<GroupStyle>,
- pub drag_over_styles: Vec<(TypeId, StyleRefinement)>,
- pub group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
- pub mouse_down_listeners: Vec<MouseDownListener>,
- pub mouse_up_listeners: Vec<MouseUpListener>,
- pub mouse_move_listeners: Vec<MouseMoveListener>,
- pub scroll_wheel_listeners: Vec<ScrollWheelListener>,
- pub key_down_listeners: Vec<KeyDownListener>,
- pub key_up_listeners: Vec<KeyUpListener>,
- pub action_listeners: Vec<(TypeId, ActionListener)>,
- pub drop_listeners: Vec<(TypeId, DropListener)>,
- pub can_drop_predicate: Option<CanDropPredicate>,
- pub click_listeners: Vec<ClickListener>,
- pub drag_listener: Option<(Box<dyn Any>, DragListener)>,
- pub hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
- pub tooltip_builder: Option<TooltipBuilder>,
- pub block_mouse: bool,
+ pub(crate) focus_style: Option<Box<StyleRefinement>>,
+ pub(crate) in_focus_style: Option<Box<StyleRefinement>>,
+ pub(crate) hover_style: Option<Box<StyleRefinement>>,
+ pub(crate) group_hover_style: Option<GroupStyle>,
+ pub(crate) active_style: Option<Box<StyleRefinement>>,
+ pub(crate) group_active_style: Option<GroupStyle>,
+ pub(crate) drag_over_styles: Vec<(TypeId, StyleRefinement)>,
+ pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>,
+ pub(crate) mouse_down_listeners: Vec<MouseDownListener>,
+ pub(crate) mouse_up_listeners: Vec<MouseUpListener>,
+ pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
+ pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
+ pub(crate) key_down_listeners: Vec<KeyDownListener>,
+ pub(crate) key_up_listeners: Vec<KeyUpListener>,
+ pub(crate) action_listeners: Vec<(TypeId, ActionListener)>,
+ pub(crate) drop_listeners: Vec<(TypeId, DropListener)>,
+ pub(crate) can_drop_predicate: Option<CanDropPredicate>,
+ pub(crate) click_listeners: Vec<ClickListener>,
+ pub(crate) drag_listener: Option<(Box<dyn Any>, DragListener)>,
+ pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut WindowContext)>>,
+ pub(crate) tooltip_builder: Option<TooltipBuilder>,
+ pub(crate) block_mouse: bool,
#[cfg(debug_assertions)]
- pub location: Option<core::panic::Location<'static>>,
+ pub(crate) location: Option<core::panic::Location<'static>>,
#[cfg(any(test, feature = "test-support"))]
- pub debug_selector: Option<String>,
+ pub(crate) debug_selector: Option<String>,
}
+/// The bounds and depth of an element in the computed element tree.
#[derive(Clone, Debug)]
pub struct InteractiveBounds {
+ /// The 2D bounds of the element
pub bounds: Bounds<Pixels>,
+ /// The 'stacking order', or depth, for this element
pub stacking_order: StackingOrder,
}
impl InteractiveBounds {
+ /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
pub fn visibly_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
self.bounds.contains(point) && cx.was_top_layer(point, &self.stacking_order)
}
+ /// Checks whether this point was inside these bounds, and that these bounds where the topmost layer
+ /// under an active drag
pub fn drag_target_contains(&self, point: &Point<Pixels>, cx: &WindowContext) -> bool {
self.bounds.contains(point)
&& cx.was_top_layer_under_active_drag(point, &self.stacking_order)
@@ -946,11 +1230,12 @@ impl InteractiveBounds {
}
impl Interactivity {
+ /// Layout this element according to this interactivity state's configured styles
pub fn layout(
&mut self,
element_state: Option<InteractiveElementState>,
- cx: &mut WindowContext,
- f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
+ cx: &mut ElementContext,
+ f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
) -> (LayoutId, InteractiveElementState) {
let mut element_state = element_state.unwrap_or_default();
@@ -984,13 +1269,21 @@ impl Interactivity {
(layout_id, element_state)
}
+ /// Paint this element according to this interactivity state's configured styles
+ /// and bind the element's mouse and keyboard events.
+ ///
+ /// content_size is the size of the content of the element, which may be larger than the
+ /// element's bounds if the element is scrollable.
+ ///
+ /// the final computed style will be passed to the provided function, along
+ /// with the current scroll offset
pub fn paint(
&mut self,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
element_state: &mut InteractiveElementState,
- cx: &mut WindowContext,
- f: impl FnOnce(&Style, Point<Pixels>, &mut WindowContext),
+ cx: &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);
@@ -1003,7 +1296,7 @@ impl Interactivity {
.insert(debug_selector.clone(), bounds);
}
- let paint_hover_group_handler = |cx: &mut WindowContext| {
+ let paint_hover_group_handler = |cx: &mut ElementContext| {
let hover_group_bounds = self
.group_hover_style
.as_ref()
@@ -1,19 +1,23 @@
use std::sync::Arc;
use crate::{
- point, size, BorrowWindow, Bounds, DevicePixels, Element, ImageData, InteractiveElement,
+ point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUrl, Size,
- StyleRefinement, Styled, WindowContext,
+ StyleRefinement, Styled,
};
use futures::FutureExt;
use media::core_video::CVImageBuffer;
use util::ResultExt;
+/// A source of image content.
#[derive(Clone, Debug)]
pub enum ImageSource {
/// Image content will be loaded from provided URI at render time.
Uri(SharedUrl),
+ /// Cached image data
Data(Arc<ImageData>),
+ // TODO: move surface definitions into mac platform module
+ /// A CoreVideo image buffer
Surface(CVImageBuffer),
}
@@ -47,12 +51,14 @@ impl From<CVImageBuffer> for ImageSource {
}
}
+/// An image element.
pub struct Img {
interactivity: Interactivity,
source: ImageSource,
grayscale: bool,
}
+/// Create a new image element.
pub fn img(source: impl Into<ImageSource>) -> Img {
Img {
interactivity: Interactivity::default(),
@@ -62,6 +68,7 @@ pub fn img(source: impl Into<ImageSource>) -> Img {
}
impl Img {
+ /// Set the image to be displayed in grayscale.
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.grayscale = grayscale;
self
@@ -74,7 +81,7 @@ impl Element for Img {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
self.interactivity
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
@@ -84,7 +91,7 @@ impl Element for Img {
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let source = self.source.clone();
self.interactivity.paint(
@@ -1,13 +1,22 @@
+//! A list element that can be used to render a large number of differently sized elements
+//! efficiently. Clients of this API need to ensure that elements outside of the scrolled
+//! area do not change their height for this element to function correctly. In order to minimize
+//! re-renders, this element's state is stored intrusively on your own views, so that your code
+//! can coordinate directly with the list element's cached state.
+//!
+//! If all of your elements are the same height, see [`UniformList`] for a simpler API
+
use crate::{
- point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
- DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
- StyleRefinement, Styled, WindowContext,
+ point, px, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Element,
+ IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled,
+ WindowContext,
};
use collections::VecDeque;
use refineable::Refineable as _;
use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
+/// Construct a new list element
pub fn list(state: ListState) -> List {
List {
state,
@@ -15,11 +24,13 @@ pub fn list(state: ListState) -> List {
}
}
+/// A list element
pub struct List {
state: ListState,
style: StyleRefinement,
}
+/// The list state that views must hold on behalf of the list element.
#[derive(Clone)]
pub struct ListState(Rc<RefCell<StateInner>>);
@@ -35,15 +46,24 @@ struct StateInner {
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
}
+/// Whether the list is scrolling from top to bottom or bottom to top.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ListAlignment {
+ /// The list is scrolling from top to bottom, like most lists.
Top,
+ /// The list is scrolling from bottom to top, like a chat log.
Bottom,
}
+/// A scroll event that has been converted to be in terms of the list's items.
pub struct ListScrollEvent {
+ /// The range of items currently visible in the list, after applying the scroll event.
pub visible_range: Range<usize>,
+
+ /// The number of items that are currently visible in the list, after applying the scroll event.
pub count: usize,
+
+ /// Whether the list has been scrolled.
pub is_scrolled: bool,
}
@@ -74,6 +94,11 @@ struct UnrenderedCount(usize);
struct Height(Pixels);
impl ListState {
+ /// Construct a new list state, for storage on a view.
+ ///
+ /// the overdraw parameter controls how much extra space is rendered
+ /// above and below the visible area. This can help ensure that the list
+ /// doesn't flicker or pop in when scrolling.
pub fn new<F>(
element_count: usize,
orientation: ListAlignment,
@@ -111,10 +136,13 @@ impl ListState {
.extend((0..element_count).map(|_| ListItem::Unrendered), &());
}
+ /// The number of items in this list.
pub fn item_count(&self) -> usize {
self.0.borrow().items.summary().count
}
+ /// Register with the list state that the items in `old_range` have been replaced
+ /// by `count` new items that must be recalculated.
pub fn splice(&self, old_range: Range<usize>, count: usize) {
let state = &mut *self.0.borrow_mut();
@@ -141,6 +169,7 @@ impl ListState {
state.items = new_heights;
}
+ /// Set a handler that will be called when the list is scrolled.
pub fn set_scroll_handler(
&self,
handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static,
@@ -148,10 +177,12 @@ impl ListState {
self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
}
+ /// Get the current scroll offset, in terms of the list's items.
pub fn logical_scroll_top(&self) -> ListOffset {
self.0.borrow().logical_scroll_top()
}
+ /// Scroll the list to the given offset
pub fn scroll_to(&self, mut scroll_top: ListOffset) {
let state = &mut *self.0.borrow_mut();
let item_count = state.items.summary().count;
@@ -163,6 +194,7 @@ impl ListState {
state.logical_scroll_top = Some(scroll_top);
}
+ /// Scroll the list to the given item, such that the item is fully visible.
pub fn scroll_to_reveal_item(&self, ix: usize) {
let state = &mut *self.0.borrow_mut();
@@ -193,7 +225,8 @@ impl ListState {
state.logical_scroll_top = Some(scroll_top);
}
- /// Get the bounds for the given item in window coordinates.
+ /// Get the bounds for the given item in window coordinates, if it's
+ /// been rendered.
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
let state = &*self.0.borrow();
@@ -310,9 +343,13 @@ impl std::fmt::Debug for ListItem {
}
}
+/// An offset into the list's items, in terms of the item index and the number
+/// of pixels off the top left of the item.
#[derive(Debug, Clone, Copy, Default)]
pub struct ListOffset {
+ /// The index of an item in the list
pub item_ix: usize,
+ /// The number of pixels to offset from the item index.
pub offset_in_item: Pixels,
}
@@ -322,7 +359,7 @@ impl Element for List {
fn request_layout(
&mut self,
_state: Option<Self::State>,
- cx: &mut crate::WindowContext,
+ cx: &mut crate::ElementContext,
) -> (crate::LayoutId, Self::State) {
let mut style = Style::default();
style.refine(&self.style);
@@ -336,7 +373,7 @@ impl Element for List {
&mut self,
bounds: Bounds<crate::Pixels>,
_state: &mut Self::State,
- cx: &mut crate::WindowContext,
+ cx: &mut crate::ElementContext,
) {
let state = &mut *self.state.0.borrow_mut();
@@ -2,14 +2,17 @@ use smallvec::SmallVec;
use taffy::style::{Display, Position};
use crate::{
- point, AnyElement, BorrowWindow, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels,
- Point, Size, Style, WindowContext,
+ point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement,
+ Pixels, Point, Size, Style,
};
+/// The state that the overlay element uses to track its children.
pub struct OverlayState {
child_layout_ids: SmallVec<[LayoutId; 4]>,
}
+/// An overlay element that can be used to display UI that
+/// floats on top of other UI elements.
pub struct Overlay {
children: SmallVec<[AnyElement; 2]>,
anchor_corner: AnchorCorner,
@@ -60,8 +63,8 @@ impl Overlay {
}
impl ParentElement for Overlay {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -71,7 +74,7 @@ impl Element for Overlay {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
let child_layout_ids = self
.children
@@ -94,7 +97,7 @@ impl Element for Overlay {
&mut self,
bounds: crate::Bounds<crate::Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
if element_state.child_layout_ids.is_empty() {
return;
@@ -191,15 +194,21 @@ enum Axis {
Vertical,
}
+/// Which algorithm to use when fitting the overlay to be inside the window.
#[derive(Copy, Clone, PartialEq)]
pub enum OverlayFitMode {
+ /// Snap the overlay to the window edge
SnapToWindow,
+ /// Switch which corner anchor this overlay is attached to
SwitchAnchor,
}
+/// Which algorithm to use when positioning the overlay.
#[derive(Copy, Clone, PartialEq)]
pub enum OverlayPositionMode {
+ /// Position the overlay relative to the window
Window,
+ /// Position the overlay relative to its parent
Local,
}
@@ -226,11 +235,16 @@ impl OverlayPositionMode {
}
}
+/// Which corner of the overlay should be considered the anchor.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AnchorCorner {
+ /// The top left corner
TopLeft,
+ /// The top right corner
TopRight,
+ /// The bottom left corner
BottomLeft,
+ /// The bottom right corner
BottomRight,
}
@@ -255,6 +269,7 @@ impl AnchorCorner {
Bounds { origin, size }
}
+ /// Get the point corresponding to this anchor corner in `bounds`.
pub fn corner(&self, bounds: Bounds<Pixels>) -> Point<Pixels> {
match self {
Self::TopLeft => bounds.origin,
@@ -1,14 +1,16 @@
use crate::{
- Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
- IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext,
+ Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
+ Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
};
use util::ResultExt;
+/// An SVG element.
pub struct Svg {
interactivity: Interactivity,
path: Option<SharedString>,
}
+/// Create a new SVG element.
pub fn svg() -> Svg {
Svg {
interactivity: Interactivity::default(),
@@ -17,6 +19,7 @@ pub fn svg() -> Svg {
}
impl Svg {
+ /// Set the path to the SVG file for this element.
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
self.path = Some(path.into());
self
@@ -29,7 +32,7 @@ impl Element for Svg {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None)
@@ -40,7 +43,7 @@ impl Element for Svg {
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) where
Self: Sized,
{
@@ -1,12 +1,19 @@
use crate::{
- Bounds, DispatchPhase, Element, ElementId, HighlightStyle, IntoElement, LayoutId,
- MouseDownEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextRun, TextStyle,
- WhiteSpace, WindowContext, WrappedLine,
+ ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
+ HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+ Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
+ TOOLTIP_DELAY,
};
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard};
use smallvec::SmallVec;
-use std::{cell::Cell, mem, ops::Range, rc::Rc, sync::Arc};
+use std::{
+ cell::{Cell, RefCell},
+ mem,
+ ops::Range,
+ rc::Rc,
+ sync::Arc,
+};
use util::ResultExt;
impl Element for &'static str {
@@ -15,14 +22,14 @@ impl Element for &'static str {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let mut state = TextState::default();
let layout_id = state.layout(SharedString::from(*self), None, cx);
(layout_id, state)
}
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
state.paint(bounds, self, cx)
}
}
@@ -45,14 +52,14 @@ impl Element for SharedString {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let mut state = TextState::default();
let layout_id = state.layout(self.clone(), None, cx);
(layout_id, state)
}
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
let text_str: &str = self.as_ref();
state.paint(bounds, text_str, cx)
}
@@ -81,6 +88,7 @@ pub struct StyledText {
}
impl StyledText {
+ /// Construct a new styled text element from the given string.
pub fn new(text: impl Into<SharedString>) -> Self {
StyledText {
text: text.into(),
@@ -88,6 +96,8 @@ impl StyledText {
}
}
+ /// Set the styling attributes for the given text, as well as
+ /// as any ranges of text that have had their style customized.
pub fn with_highlights(
mut self,
default_style: &TextStyle,
@@ -121,14 +131,14 @@ impl Element for StyledText {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let mut state = TextState::default();
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
(layout_id, state)
}
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
state.paint(bounds, &self.text, cx)
}
}
@@ -145,6 +155,7 @@ impl IntoElement for StyledText {
}
}
+#[doc(hidden)]
#[derive(Default, Clone)]
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
@@ -164,7 +175,7 @@ impl TextState {
&mut self,
text: SharedString,
runs: Option<Vec<TextRun>>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> LayoutId {
let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size());
@@ -239,7 +250,7 @@ impl TextState {
layout_id
}
- fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut ElementContext) {
let element_state = self.lock();
let element_state = element_state
.as_ref()
@@ -284,11 +295,14 @@ impl TextState {
}
}
+/// A text element that can be interacted with.
pub struct InteractiveText {
element_id: ElementId,
text: StyledText,
click_listener:
Option<Box<dyn Fn(&[Range<usize>], InteractiveTextClickEvent, &mut WindowContext<'_>)>>,
+ hover_listener: Option<Box<dyn Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>)>>,
+ tooltip_builder: Option<Rc<dyn Fn(usize, &mut WindowContext<'_>) -> Option<AnyView>>>,
clickable_ranges: Vec<Range<usize>>,
}
@@ -297,21 +311,30 @@ struct InteractiveTextClickEvent {
mouse_up_index: usize,
}
+#[doc(hidden)]
pub struct InteractiveTextState {
text_state: TextState,
mouse_down_index: Rc<Cell<Option<usize>>>,
+ hovered_index: Rc<Cell<Option<usize>>>,
+ active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
}
+/// InteractiveTest is a wrapper around StyledText that adds mouse interactions.
impl InteractiveText {
+ /// Creates a new InteractiveText from the given text.
pub fn new(id: impl Into<ElementId>, text: StyledText) -> Self {
Self {
element_id: id.into(),
text,
click_listener: None,
+ hover_listener: None,
+ tooltip_builder: None,
clickable_ranges: Vec::new(),
}
}
+ /// on_click is called when the user clicks on one of the given ranges, passing the index of
+ /// the clicked range.
pub fn on_click(
mut self,
ranges: Vec<Range<usize>>,
@@ -328,6 +351,25 @@ impl InteractiveText {
self.clickable_ranges = ranges;
self
}
+
+ /// on_hover is called when the mouse moves over a character within the text, passing the
+ /// index of the hovered character, or None if the mouse leaves the text.
+ pub fn on_hover(
+ mut self,
+ listener: impl Fn(Option<usize>, MouseMoveEvent, &mut WindowContext<'_>) + 'static,
+ ) -> Self {
+ self.hover_listener = Some(Box::new(listener));
+ self
+ }
+
+ /// tooltip lets you specify a tooltip for a given character index in the string.
+ pub fn tooltip(
+ mut self,
+ builder: impl Fn(usize, &mut WindowContext<'_>) -> Option<AnyView> + 'static,
+ ) -> Self {
+ self.tooltip_builder = Some(Rc::new(builder));
+ self
+ }
}
impl Element for InteractiveText {
@@ -336,16 +378,21 @@ impl Element for InteractiveText {
fn request_layout(
&mut self,
state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
if let Some(InteractiveTextState {
- mouse_down_index, ..
+ mouse_down_index,
+ hovered_index,
+ active_tooltip,
+ ..
}) = state
{
let (layout_id, text_state) = self.text.request_layout(None, cx);
let element_state = InteractiveTextState {
text_state,
mouse_down_index,
+ hovered_index,
+ active_tooltip,
};
(layout_id, element_state)
} else {
@@ -353,12 +400,14 @@ impl Element for InteractiveText {
let element_state = InteractiveTextState {
text_state,
mouse_down_index: Rc::default(),
+ hovered_index: Rc::default(),
+ active_tooltip: Rc::default(),
};
(layout_id, element_state)
}
}
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, 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) {
@@ -408,6 +457,83 @@ impl Element for InteractiveText {
});
}
}
+ if let Some(hover_listener) = self.hover_listener.take() {
+ let text_state = state.text_state.clone();
+ let hovered_index = state.hovered_index.clone();
+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ let current = hovered_index.get();
+ let updated = text_state.index_for_position(bounds, event.position);
+ if current != updated {
+ hovered_index.set(updated);
+ hover_listener(updated, event.clone(), cx);
+ cx.refresh();
+ }
+ }
+ });
+ }
+ if let Some(tooltip_builder) = self.tooltip_builder.clone() {
+ let active_tooltip = state.active_tooltip.clone();
+ let pending_mouse_down = state.mouse_down_index.clone();
+ let text_state = state.text_state.clone();
+
+ cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+ let position = text_state.index_for_position(bounds, event.position);
+ let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
+ if !is_hovered {
+ active_tooltip.take();
+ return;
+ }
+ let position = position.unwrap();
+
+ if phase != DispatchPhase::Bubble {
+ return;
+ }
+
+ if active_tooltip.borrow().is_none() {
+ let task = cx.spawn({
+ let active_tooltip = active_tooltip.clone();
+ let tooltip_builder = tooltip_builder.clone();
+
+ move |mut cx| async move {
+ cx.background_executor().timer(TOOLTIP_DELAY).await;
+ cx.update(|cx| {
+ let new_tooltip =
+ tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
+ tooltip: Some(AnyTooltip {
+ view: tooltip,
+ cursor_offset: cx.mouse_position(),
+ }),
+ _task: None,
+ });
+ *active_tooltip.borrow_mut() = new_tooltip;
+ cx.refresh();
+ })
+ .ok();
+ }
+ });
+ *active_tooltip.borrow_mut() = Some(ActiveTooltip {
+ tooltip: None,
+ _task: Some(task),
+ });
+ }
+ });
+
+ let active_tooltip = state.active_tooltip.clone();
+ cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
+ active_tooltip.take();
+ });
+
+ if let Some(tooltip) = state
+ .active_tooltip
+ .clone()
+ .borrow()
+ .as_ref()
+ .and_then(|at| at.tooltip.clone())
+ {
+ cx.set_tooltip(tooltip);
+ }
+ }
self.text.paint(bounds, &mut state.text_state, cx)
}
@@ -1,5 +1,11 @@
+//! A scrollable list of elements with uniform height, optimized for large lists.
+//! Rather than use the full taffy layout system, uniform_list simply measures
+//! the first element and then lays out all remaining elements in a line based on that
+//! measurement. This is much faster than the full layout system, but only works for
+//! elements with uniform height.
+
use crate::{
- point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, ContentMask, Element,
+ point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
@@ -53,6 +59,7 @@ where
}
}
+/// A list element for efficiently laying out and displaying a list of uniform-height elements.
pub struct UniformList {
id: ElementId,
item_count: usize,
@@ -63,18 +70,22 @@ pub struct UniformList {
scroll_handle: Option<UniformListScrollHandle>,
}
+/// A handle for controlling the scroll position of a uniform list.
+/// This should be stored in your view and passed to the uniform_list on each frame.
#[derive(Clone, Default)]
pub struct UniformListScrollHandle {
deferred_scroll_to_item: Rc<RefCell<Option<usize>>>,
}
impl UniformListScrollHandle {
+ /// Create a new scroll handle to bind to a uniform list.
pub fn new() -> Self {
Self {
deferred_scroll_to_item: Rc::new(RefCell::new(None)),
}
}
+ /// Scroll the list to the given item index.
pub fn scroll_to_item(&mut self, ix: usize) {
self.deferred_scroll_to_item.replace(Some(ix));
}
@@ -86,6 +97,7 @@ impl Styled for UniformList {
}
}
+#[doc(hidden)]
#[derive(Default)]
pub struct UniformListState {
interactive: InteractiveElementState,
@@ -98,7 +110,7 @@ impl Element for UniformList {
fn request_layout(
&mut self,
state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let max_items = self.item_count;
let item_size = state
@@ -146,7 +158,7 @@ impl Element for UniformList {
&mut self,
bounds: Bounds<crate::Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let style =
self.interactivity
@@ -262,12 +274,13 @@ impl IntoElement for UniformList {
}
impl UniformList {
+ /// Selects a specific list item for measurement.
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
self.item_to_measure_index = item_index.unwrap_or(0);
self
}
- fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
+ fn measure_item(&self, list_width: Option<Pixels>, cx: &mut ElementContext) -> Size<Pixels> {
if self.item_count == 0 {
return Size::default();
}
@@ -284,6 +297,7 @@ impl UniformList {
item_to_measure.measure(available_space, cx)
}
+ /// Track and render scroll state of this list with reference to the given scroll handle.
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
self.scroll_handle = Some(handle);
self
@@ -21,11 +21,15 @@ use waker_fn::waker_fn;
#[cfg(any(test, feature = "test-support"))]
use rand::rngs::StdRng;
+/// A pointer to the executor that is currently running,
+/// for spawning background tasks.
#[derive(Clone)]
pub struct BackgroundExecutor {
dispatcher: Arc<dyn PlatformDispatcher>,
}
+/// A pointer to the executor that is currently running,
+/// for spawning tasks on the main thread.
#[derive(Clone)]
pub struct ForegroundExecutor {
dispatcher: Arc<dyn PlatformDispatcher>,
@@ -37,16 +41,19 @@ pub struct ForegroundExecutor {
/// It implements [`Future`] so you can `.await` on it.
///
/// If you drop a task it will be cancelled immediately. Calling [`Task::detach`] allows
-/// the task to continue running in the background, but with no way to return a value.
+/// the task to continue running, but with no way to return a value.
#[must_use]
#[derive(Debug)]
pub enum Task<T> {
+ /// A task that is ready to return a value
Ready(Option<T>),
+
+ /// A task that is currently running.
Spawned(async_task::Task<T>),
}
impl<T> Task<T> {
- /// Create a new task that will resolve with the value
+ /// Creates a new task that will resolve with the value
pub fn ready(val: T) -> Self {
Task::Ready(Some(val))
}
@@ -68,7 +75,7 @@ where
/// Run the task to completion in the background and log any
/// errors that occur.
#[track_caller]
- pub fn detach_and_log_err(self, cx: &mut AppContext) {
+ pub fn detach_and_log_err(self, cx: &AppContext) {
let location = core::panic::Location::caller();
cx.foreground_executor()
.spawn(self.log_tracked_err(*location))
@@ -87,6 +94,8 @@ impl<T> Future for Task<T> {
}
}
+/// A task label is an opaque identifier that you can use to
+/// refer to a task in tests.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct TaskLabel(NonZeroUsize);
@@ -97,6 +106,7 @@ impl Default for TaskLabel {
}
impl TaskLabel {
+ /// Construct a new task label.
pub fn new() -> Self {
static NEXT_TASK_LABEL: AtomicUsize = AtomicUsize::new(1);
Self(NEXT_TASK_LABEL.fetch_add(1, SeqCst).try_into().unwrap())
@@ -363,6 +373,7 @@ impl BackgroundExecutor {
/// ForegroundExecutor runs things on the main thread.
impl ForegroundExecutor {
+ /// Creates a new ForegroundExecutor from the given PlatformDispatcher.
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self {
dispatcher,
@@ -411,13 +422,14 @@ impl<'a> Scope<'a> {
}
}
+ /// Spawn a future into this scope.
pub fn spawn<F>(&mut self, f: F)
where
F: Future<Output = ()> + Send + 'a,
{
let tx = self.tx.clone().unwrap();
- // Safety: The 'a lifetime is guaranteed to outlive any of these futures because
+ // SAFETY: The 'a lifetime is guaranteed to outlive any of these futures because
// dropping this `Scope` blocks until all of the futures have resolved.
let f = unsafe {
mem::transmute::<
@@ -497,6 +497,20 @@ where
}
}
+impl<T> Add for Size<T>
+where
+ T: Add<Output = T> + Clone + Default + Debug,
+{
+ type Output = Size<T>;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ Size {
+ width: self.width + rhs.width,
+ height: self.height + rhs.height,
+ }
+ }
+}
+
impl<T, Rhs> Mul<Rhs> for Size<T>
where
T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
@@ -1,3 +1,31 @@
+//! # Welcome to GPUI!
+//!
+//! GPUI is a hybrid immediate and retained mode, GPU accelerated, UI framework
+//! for Rust, designed to support a wide variety of applications. GPUI is currently
+//! being actively developed and improved for the [Zed code editor](https://zed.dev/), and new versions
+//! will have breaking changes. You'll probably need to use the latest stable version
+//! of rust to use GPUI.
+//!
+//! # Getting started with GPUI
+//!
+//! TODO!(docs): Write a code sample showing how to create a window and render a simple
+//! div
+//!
+//! # Drawing interesting things
+//!
+//! TODO!(docs): Expand demo to show how to draw a more interesting scene, with
+//! a counter to store state and a button to increment it.
+//!
+//! # Interacting with your application state
+//!
+//! TODO!(docs): Expand demo to show GPUI entity interactions, like subscriptions and entities
+//! maybe make a network request to show async stuff?
+//!
+//! # Conclusion
+//!
+//! TODO!(docs): Wrap up with a conclusion and links to other places? Zed / GPUI website?
+//! Discord for chatting about it? Other tutorials or references?
+
#[macro_use]
mod action;
mod app;
@@ -58,10 +86,10 @@ pub use elements::*;
pub use executor::*;
pub use geometry::*;
pub use gpui_macros::{register_action, test, IntoElement, Render};
-pub use image_cache::*;
+use image_cache::*;
pub use input::*;
pub use interactive::*;
-pub use key_dispatch::*;
+use key_dispatch::*;
pub use keymap::*;
pub use platform::*;
pub use refineable::*;
@@ -73,7 +101,7 @@ pub use smol::Timer;
pub use style::*;
pub use styled::*;
pub use subscription::*;
-pub use svg_renderer::*;
+use svg_renderer::*;
pub use taffy::{AvailableSpace, LayoutId};
#[cfg(any(test, feature = "test-support"))]
pub use test::*;
@@ -82,20 +110,23 @@ pub use util::arc_cow::ArcCow;
pub use view::*;
pub use window::*;
-use std::{
- any::{Any, TypeId},
- borrow::BorrowMut,
-};
+use std::{any::Any, borrow::BorrowMut};
use taffy::TaffyLayoutEngine;
+/// The context trait, allows the different contexts in GPUI to be used
+/// interchangeably for certain operations.
pub trait Context {
+ /// The result type for this context, used for async contexts that
+ /// can't hold a direct reference to the application context.
type Result<T>;
+ /// Create a new model in the app context.
fn new_model<T: 'static>(
&mut self,
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
) -> Self::Result<Model<T>>;
+ /// Update a model in the app context.
fn update_model<T, R>(
&mut self,
handle: &Model<T>,
@@ -104,6 +135,7 @@ pub trait Context {
where
T: 'static;
+ /// Read a model from the app context.
fn read_model<T, R>(
&self,
handle: &Model<T>,
@@ -112,10 +144,12 @@ pub trait Context {
where
T: 'static;
+ /// Update a window for the given handle.
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
+ /// Read a window off of the application context.
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
@@ -125,7 +159,10 @@ pub trait Context {
T: 'static;
}
+/// This trait is used for the different visual contexts in GPUI that
+/// require a window to be present.
pub trait VisualContext: Context {
+ /// Construct a new view in the window referenced by this context.
fn new_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -133,12 +170,14 @@ pub trait VisualContext: Context {
where
V: 'static + Render;
+ /// Update a view with the given callback
fn update_view<V: 'static, R>(
&mut self,
view: &View<V>,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Self::Result<R>;
+ /// Replace the root view of a window with a new view.
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -146,38 +185,42 @@ pub trait VisualContext: Context {
where
V: 'static + Render;
+ /// Focus a view in the window, if it implements the [`FocusableView`] trait.
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
where
V: FocusableView;
+ /// Dismiss a view in the window, if it implements the [`ManagedView`] trait.
fn dismiss_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
where
V: ManagedView;
}
+/// A trait that allows models and views to be interchangeable in certain operations
pub trait Entity<T>: Sealed {
+ /// The weak reference type for this entity.
type Weak: 'static;
+ /// The ID for this entity
fn entity_id(&self) -> EntityId;
+
+ /// Downgrade this entity to a weak reference.
fn downgrade(&self) -> Self::Weak;
+
+ /// Upgrade this entity from a weak reference.
fn upgrade_from(weak: &Self::Weak) -> Option<Self>
where
Self: Sized;
}
+/// A trait for tying together the types of a GPUI entity and the events it can
+/// emit.
pub trait EventEmitter<E: Any>: 'static {}
-pub enum GlobalKey {
- Numeric(usize),
- View(EntityId),
- Type(TypeId),
-}
-
+/// A helper trait for auto-implementing certain methods on contexts that
+/// can be used interchangeably.
pub trait BorrowAppContext {
- fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
- where
- F: FnOnce(&mut Self) -> R;
-
+ /// Set a global value on the context.
fn set_global<T: 'static>(&mut self, global: T);
}
@@ -185,26 +228,14 @@ impl<C> BorrowAppContext for C
where
C: BorrowMut<AppContext>,
{
- fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
- where
- F: FnOnce(&mut Self) -> R,
- {
- if let Some(style) = style {
- self.borrow_mut().push_text_style(style);
- let result = f(self);
- self.borrow_mut().pop_text_style();
- result
- } else {
- f(self)
- }
- }
-
fn set_global<G: 'static>(&mut self, global: G) {
self.borrow_mut().set_global(global)
}
}
+/// A flatten equivalent for anyhow `Result`s.
pub trait Flatten<T> {
+ /// Convert this type into a simple `Result<T>`.
fn flatten(self) -> Result<T>;
}
@@ -11,12 +11,12 @@ use thiserror::Error;
use util::http::{self, HttpClient};
#[derive(PartialEq, Eq, Hash, Clone)]
-pub struct RenderImageParams {
+pub(crate) struct RenderImageParams {
pub(crate) image_id: ImageId,
}
#[derive(Debug, Error, Clone)]
-pub enum Error {
+pub(crate) enum Error {
#[error("http error: {0}")]
Client(#[from] http::Error),
#[error("IO error: {0}")]
@@ -42,7 +42,7 @@ impl From<ImageError> for Error {
}
}
-pub struct ImageCache {
+pub(crate) struct ImageCache {
client: Arc<dyn HttpClient>,
images: Arc<Mutex<HashMap<SharedUrl, FetchImageFuture>>>,
}
@@ -1,24 +1,35 @@
-use crate::{
- AsyncWindowContext, Bounds, Pixels, PlatformInputHandler, View, ViewContext, WindowContext,
-};
+use crate::{Bounds, InputHandler, Pixels, View, ViewContext, WindowContext};
use std::ops::Range;
/// Implement this trait to allow views to handle textual input when implementing an editor, field, etc.
///
-/// Once your view `V` implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
+/// Once your view implements this trait, you can use it to construct an [`ElementInputHandler<V>`].
/// This input handler can then be assigned during paint by calling [`WindowContext::handle_input`].
-pub trait InputHandler: 'static + Sized {
+///
+/// See [`InputHandler`] for details on how to implement each method.
+pub trait ViewInputHandler: 'static + Sized {
+ /// See [`InputHandler::text_for_range`] for details
fn text_for_range(&mut self, range: Range<usize>, cx: &mut ViewContext<Self>)
-> Option<String>;
+
+ /// See [`InputHandler::selected_text_range`] for details
fn selected_text_range(&mut self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+
+ /// See [`InputHandler::marked_text_range`] for details
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
+
+ /// See [`InputHandler::unmark_text`] for details
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
+
+ /// See [`InputHandler::replace_text_in_range`] for details
fn replace_text_in_range(
&mut self,
range: Option<Range<usize>>,
text: &str,
cx: &mut ViewContext<Self>,
);
+
+ /// See [`InputHandler::replace_and_mark_text_in_range`] for details
fn replace_and_mark_text_in_range(
&mut self,
range: Option<Range<usize>>,
@@ -26,6 +37,8 @@ pub trait InputHandler: 'static + Sized {
new_selected_range: Option<Range<usize>>,
cx: &mut ViewContext<Self>,
);
+
+ /// See [`InputHandler::bounds_for_range`] for details
fn bounds_for_range(
&mut self,
range_utf16: Range<usize>,
@@ -39,7 +52,6 @@ pub trait InputHandler: 'static + Sized {
pub struct ElementInputHandler<V> {
view: View<V>,
element_bounds: Bounds<Pixels>,
- cx: AsyncWindowContext,
}
impl<V: 'static> ElementInputHandler<V> {
@@ -47,45 +59,42 @@ impl<V: 'static> ElementInputHandler<V> {
/// containing view.
///
/// [element_paint]: crate::Element::paint
- pub fn new(element_bounds: Bounds<Pixels>, view: View<V>, cx: &mut WindowContext) -> Self {
+ pub fn new(element_bounds: Bounds<Pixels>, view: View<V>) -> Self {
ElementInputHandler {
view,
element_bounds,
- cx: cx.to_async(),
}
}
}
-impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
- fn selected_text_range(&mut self) -> Option<Range<usize>> {
+impl<V: ViewInputHandler> InputHandler for ElementInputHandler<V> {
+ fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
self.view
- .update(&mut self.cx, |view, cx| view.selected_text_range(cx))
- .ok()
- .flatten()
+ .update(cx, |view, cx| view.selected_text_range(cx))
}
- fn marked_text_range(&mut self) -> Option<Range<usize>> {
- self.view
- .update(&mut self.cx, |view, cx| view.marked_text_range(cx))
- .ok()
- .flatten()
+ fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>> {
+ self.view.update(cx, |view, cx| view.marked_text_range(cx))
}
- fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
+ fn text_for_range(
+ &mut self,
+ range_utf16: Range<usize>,
+ cx: &mut WindowContext,
+ ) -> Option<String> {
self.view
- .update(&mut self.cx, |view, cx| {
- view.text_for_range(range_utf16, cx)
- })
- .ok()
- .flatten()
+ .update(cx, |view, cx| view.text_for_range(range_utf16, cx))
}
- fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
- self.view
- .update(&mut self.cx, |view, cx| {
- view.replace_text_in_range(replacement_range, text, cx)
- })
- .ok();
+ fn replace_text_in_range(
+ &mut self,
+ replacement_range: Option<Range<usize>>,
+ text: &str,
+ cx: &mut WindowContext,
+ ) {
+ self.view.update(cx, |view, cx| {
+ view.replace_text_in_range(replacement_range, text, cx)
+ });
}
fn replace_and_mark_text_in_range(
@@ -93,26 +102,24 @@ impl<V: InputHandler> PlatformInputHandler for ElementInputHandler<V> {
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
+ cx: &mut WindowContext,
) {
- self.view
- .update(&mut self.cx, |view, cx| {
- view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
- })
- .ok();
+ self.view.update(cx, |view, cx| {
+ view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
+ });
}
- fn unmark_text(&mut self) {
- self.view
- .update(&mut self.cx, |view, cx| view.unmark_text(cx))
- .ok();
+ fn unmark_text(&mut self, cx: &mut WindowContext) {
+ self.view.update(cx, |view, cx| view.unmark_text(cx));
}
- fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
- self.view
- .update(&mut self.cx, |view, cx| {
- view.bounds_for_range(range_utf16, self.element_bounds, cx)
- })
- .ok()
- .flatten()
+ fn bounds_for_range(
+ &mut self,
+ range_utf16: Range<usize>,
+ cx: &mut WindowContext,
+ ) -> Option<Bounds<Pixels>> {
+ self.view.update(cx, |view, cx| {
+ view.bounds_for_range(range_utf16, self.element_bounds, cx)
+ })
}
}
@@ -4,15 +4,25 @@ use crate::{
use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
+/// An event from a platform input source.
pub trait InputEvent: Sealed + 'static {
+ /// Convert this event into the platform input enum.
fn to_platform_input(self) -> PlatformInput;
}
+
+/// A key event from the platform.
pub trait KeyEvent: InputEvent {}
+
+/// A mouse event from the platform.
pub trait MouseEvent: InputEvent {}
+/// The key down event equivalent for the platform.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyDownEvent {
+ /// The keystroke that was generated.
pub keystroke: Keystroke,
+
+ /// Whether the key is currently held down.
pub is_held: bool,
}
@@ -24,8 +34,10 @@ impl InputEvent for KeyDownEvent {
}
impl KeyEvent for KeyDownEvent {}
+/// The key up event equivalent for the platform.
#[derive(Clone, Debug)]
pub struct KeyUpEvent {
+ /// The keystroke that was released.
pub keystroke: Keystroke,
}
@@ -37,8 +49,10 @@ impl InputEvent for KeyUpEvent {
}
impl KeyEvent for KeyUpEvent {}
+/// The modifiers changed event equivalent for the platform.
#[derive(Clone, Debug, Default)]
pub struct ModifiersChangedEvent {
+ /// The new state of the modifier keys
pub modifiers: Modifiers,
}
@@ -62,17 +76,28 @@ impl Deref for ModifiersChangedEvent {
/// Based on the winit enum of the same name.
#[derive(Clone, Copy, Debug, Default)]
pub enum TouchPhase {
+ /// The touch started.
Started,
+ /// The touch event is moving.
#[default]
Moved,
+ /// The touch phase has ended
Ended,
}
+/// A mouse down event from the platform
#[derive(Clone, Debug, Default)]
pub struct MouseDownEvent {
+ /// Which mouse button was pressed.
pub button: MouseButton,
+
+ /// The position of the mouse on the window.
pub position: Point<Pixels>,
+
+ /// The modifiers that were held down when the mouse was pressed.
pub modifiers: Modifiers,
+
+ /// The number of times the button has been clicked.
pub click_count: usize,
}
@@ -84,11 +109,19 @@ impl InputEvent for MouseDownEvent {
}
impl MouseEvent for MouseDownEvent {}
+/// A mouse up event from the platform
#[derive(Clone, Debug, Default)]
pub struct MouseUpEvent {
+ /// Which mouse button was released.
pub button: MouseButton,
+
+ /// The position of the mouse on the window.
pub position: Point<Pixels>,
+
+ /// The modifiers that were held down when the mouse was released.
pub modifiers: Modifiers,
+
+ /// The number of times the button has been clicked.
pub click_count: usize,
}
@@ -100,21 +133,34 @@ impl InputEvent for MouseUpEvent {
}
impl MouseEvent for MouseUpEvent {}
+/// A click event, generated when a mouse button is pressed and released.
#[derive(Clone, Debug, Default)]
pub struct ClickEvent {
+ /// The mouse event when the button was pressed.
pub down: MouseDownEvent,
+
+ /// The mouse event when the button was released.
pub up: MouseUpEvent,
}
+/// An enum representing the mouse button that was pressed.
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {
+ /// The left mouse button.
Left,
+
+ /// The right mouse button.
Right,
+
+ /// The middle mouse button.
Middle,
+
+ /// A navigation button, such as back or forward.
Navigate(NavigationDirection),
}
impl MouseButton {
+ /// Get all the mouse buttons in a list.
pub fn all() -> Vec<Self> {
vec![
MouseButton::Left,
@@ -132,9 +178,13 @@ impl Default for MouseButton {
}
}
+/// A navigation direction, such as back or forward.
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum NavigationDirection {
+ /// The back button.
Back,
+
+ /// The forward button.
Forward,
}
@@ -144,10 +194,16 @@ impl Default for NavigationDirection {
}
}
+/// A mouse move event from the platform
#[derive(Clone, Debug, Default)]
pub struct MouseMoveEvent {
+ /// The position of the mouse on the window.
pub position: Point<Pixels>,
+
+ /// The mouse button that was pressed, if any.
pub pressed_button: Option<MouseButton>,
+
+ /// The modifiers that were held down when the mouse was moved.
pub modifiers: Modifiers,
}
@@ -160,16 +216,25 @@ impl InputEvent for MouseMoveEvent {
impl MouseEvent for MouseMoveEvent {}
impl MouseMoveEvent {
+ /// Returns true if the left mouse button is currently held down.
pub fn dragging(&self) -> bool {
self.pressed_button == Some(MouseButton::Left)
}
}
+/// A mouse wheel event from the platform
#[derive(Clone, Debug, Default)]
pub struct ScrollWheelEvent {
+ /// The position of the mouse on the window.
pub position: Point<Pixels>,
+
+ /// The change in scroll wheel position for this event.
pub delta: ScrollDelta,
+
+ /// The modifiers that were held down when the mouse was moved.
pub modifiers: Modifiers,
+
+ /// The phase of the touch event.
pub touch_phase: TouchPhase,
}
@@ -189,9 +254,12 @@ impl Deref for ScrollWheelEvent {
}
}
+/// The scroll delta for a scroll wheel event.
#[derive(Clone, Copy, Debug)]
pub enum ScrollDelta {
+ /// An exact scroll delta in pixels.
Pixels(Point<Pixels>),
+ /// An inexact scroll delta in lines.
Lines(Point<f32>),
}
@@ -202,6 +270,7 @@ impl Default for ScrollDelta {
}
impl ScrollDelta {
+ /// Returns true if this is a precise scroll delta in pixels.
pub fn precise(&self) -> bool {
match self {
ScrollDelta::Pixels(_) => true,
@@ -209,6 +278,7 @@ impl ScrollDelta {
}
}
+ /// Converts this scroll event into exact pixels.
pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
match self {
ScrollDelta::Pixels(delta) => *delta,
@@ -216,6 +286,7 @@ impl ScrollDelta {
}
}
+ /// Combines two scroll deltas into one.
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
match (self, other) {
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
@@ -231,10 +302,15 @@ impl ScrollDelta {
}
}
+/// A mouse exit event from the platform, generated when the mouse leaves the window.
+/// The position generated should be just outside of the window's bounds.
#[derive(Clone, Debug, Default)]
pub struct MouseExitEvent {
+ /// The position of the mouse relative to the window.
pub position: Point<Pixels>,
+ /// The mouse button that was pressed, if any.
pub pressed_button: Option<MouseButton>,
+ /// The modifiers that were held down when the mouse was moved.
pub modifiers: Modifiers,
}
@@ -254,10 +330,12 @@ impl Deref for MouseExitEvent {
}
}
+/// A collection of paths from the platform, such as from a file drop.
#[derive(Debug, Clone, Default)]
pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
impl ExternalPaths {
+ /// Convert this collection of paths into a slice.
pub fn paths(&self) -> &[PathBuf] {
&self.0
}
@@ -269,18 +347,27 @@ impl Render for ExternalPaths {
}
}
+/// A file drop event from the platform, generated when files are dragged and dropped onto the window.
#[derive(Debug, Clone)]
pub enum FileDropEvent {
+ /// The files have entered the window.
Entered {
+ /// The position of the mouse relative to the window.
position: Point<Pixels>,
+ /// The paths of the files that are being dragged.
paths: ExternalPaths,
},
+ /// The files are being dragged over the window
Pending {
+ /// The position of the mouse relative to the window.
position: Point<Pixels>,
},
+ /// The files have been dropped onto the window.
Submit {
+ /// The position of the mouse relative to the window.
position: Point<Pixels>,
},
+ /// The user has stopped dragging the files over the window.
Exited,
}
@@ -292,40 +379,31 @@ impl InputEvent for FileDropEvent {
}
impl MouseEvent for FileDropEvent {}
+/// An enum corresponding to all kinds of platform input events.
#[derive(Clone, Debug)]
pub enum PlatformInput {
+ /// A key was pressed.
KeyDown(KeyDownEvent),
+ /// A key was released.
KeyUp(KeyUpEvent),
+ /// The keyboard modifiers were changed.
ModifiersChanged(ModifiersChangedEvent),
+ /// The mouse was pressed.
MouseDown(MouseDownEvent),
+ /// The mouse was released.
MouseUp(MouseUpEvent),
+ /// The mouse was moved.
MouseMove(MouseMoveEvent),
+ /// The mouse exited the window.
MouseExited(MouseExitEvent),
+ /// The scroll wheel was used.
ScrollWheel(ScrollWheelEvent),
+ /// Files were dragged and dropped onto the window.
FileDrop(FileDropEvent),
}
impl PlatformInput {
- pub fn position(&self) -> Option<Point<Pixels>> {
- match self {
- PlatformInput::KeyDown { .. } => None,
- PlatformInput::KeyUp { .. } => None,
- PlatformInput::ModifiersChanged { .. } => None,
- PlatformInput::MouseDown(event) => Some(event.position),
- PlatformInput::MouseUp(event) => Some(event.position),
- PlatformInput::MouseMove(event) => Some(event.position),
- PlatformInput::MouseExited(event) => Some(event.position),
- PlatformInput::ScrollWheel(event) => Some(event.position),
- PlatformInput::FileDrop(FileDropEvent::Exited) => None,
- PlatformInput::FileDrop(
- FileDropEvent::Entered { position, .. }
- | FileDropEvent::Pending { position, .. }
- | FileDropEvent::Submit { position, .. },
- ) => Some(*position),
- }
- }
-
- pub fn mouse_event(&self) -> Option<&dyn Any> {
+ pub(crate) fn mouse_event(&self) -> Option<&dyn Any> {
match self {
PlatformInput::KeyDown { .. } => None,
PlatformInput::KeyUp { .. } => None,
@@ -339,7 +417,7 @@ impl PlatformInput {
}
}
- pub fn keyboard_event(&self) -> Option<&dyn Any> {
+ pub(crate) fn keyboard_event(&self) -> Option<&dyn Any> {
match self {
PlatformInput::KeyDown(event) => Some(event),
PlatformInput::KeyUp(event) => Some(event),
@@ -1,6 +1,57 @@
+/// KeyDispatch is where GPUI deals with binding actions to key events.
+///
+/// The key pieces to making a key binding work are to define an action,
+/// implement a method that takes that action as a type parameter,
+/// and then to register the action during render on a focused node
+/// with a keymap context:
+///
+/// ```rust
+/// actions!(editor,[Undo, Redo]);;
+///
+/// impl Editor {
+/// fn undo(&mut self, _: &Undo, _cx: &mut ViewContext<Self>) { ... }
+/// fn redo(&mut self, _: &Redo, _cx: &mut ViewContext<Self>) { ... }
+/// }
+///
+/// impl Render for Editor {
+/// fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+/// div()
+/// .track_focus(&self.focus_handle)
+/// .keymap_context("Editor")
+/// .on_action(cx.listener(Editor::undo))
+/// .on_action(cx.listener(Editor::redo))
+/// ...
+/// }
+/// }
+///```
+///
+/// The keybindings themselves are managed independently by calling cx.bind_keys().
+/// (Though mostly when developing Zed itself, you just need to add a new line to
+/// assets/keymaps/default.json).
+///
+/// ```rust
+/// cx.bind_keys([
+/// KeyBinding::new("cmd-z", Editor::undo, Some("Editor")),
+/// KeyBinding::new("cmd-shift-z", Editor::redo, Some("Editor")),
+/// ])
+/// ```
+///
+/// With all of this in place, GPUI will ensure that if you have an Editor that contains
+/// the focus, hitting cmd-z will Undo.
+///
+/// In real apps, it is a little more complicated than this, because typically you have
+/// several nested views that each register keyboard handlers. In this case action matching
+/// bubbles up from the bottom. For example in Zed, the Workspace is the top-level view, which contains Pane's, which contain Editors. If there are conflicting keybindings defined
+/// then the Editor's bindings take precedence over the Pane's bindings, which take precedence over the Workspace.
+///
+/// In GPUI, keybindings are not limited to just single keystrokes, you can define
+/// sequences by separating the keys with a space:
+///
+/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
+///
use crate::{
- Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, KeyMatch,
- Keymap, Keystroke, KeystrokeMatcher, WindowContext,
+ Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
+ KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
};
use collections::FxHashMap;
use parking_lot::Mutex;
@@ -13,7 +64,7 @@ use std::{
};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
-pub struct DispatchNodeId(usize);
+pub(crate) struct DispatchNodeId(usize);
pub(crate) struct DispatchTree {
node_stack: Vec<DispatchNodeId>,
@@ -36,7 +87,7 @@ pub(crate) struct DispatchNode {
parent: Option<DispatchNodeId>,
}
-type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
+type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
#[derive(Clone)]
pub(crate) struct DispatchActionListener {
@@ -272,30 +323,51 @@ impl DispatchTree {
.collect()
}
+ // dispatch_key pushses the next keystroke into any key binding matchers.
+ // any matching bindings are returned in the order that they should be dispatched:
+ // * First by length of binding (so if you have a binding for "b" and "ab", the "ab" binding fires first)
+ // * Secondly by depth in the tree (so if Editor has a binding for "b" and workspace a
+ // binding for "b", the Editor action fires first).
pub fn dispatch_key(
&mut self,
keystroke: &Keystroke,
- context: &[KeyContext],
- ) -> Vec<Box<dyn Action>> {
- if !self.keystroke_matchers.contains_key(context) {
- let keystroke_contexts = context.iter().cloned().collect();
- self.keystroke_matchers.insert(
- keystroke_contexts,
- KeystrokeMatcher::new(self.keymap.clone()),
- );
- }
+ dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+ ) -> KeymatchResult {
+ let mut bindings = SmallVec::<[KeyBinding; 1]>::new();
+ let mut pending = false;
+
+ let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
+ for node_id in dispatch_path {
+ let node = self.node(*node_id);
- let keystroke_matcher = self.keystroke_matchers.get_mut(context).unwrap();
- if let KeyMatch::Some(actions) = keystroke_matcher.match_keystroke(keystroke, context) {
- // Clear all pending keystrokes when an action has been found.
- for keystroke_matcher in self.keystroke_matchers.values_mut() {
- keystroke_matcher.clear_pending();
+ if let Some(context) = node.context.clone() {
+ context_stack.push(context);
}
+ }
- actions
- } else {
- vec![]
+ while !context_stack.is_empty() {
+ let keystroke_matcher = self
+ .keystroke_matchers
+ .entry(context_stack.clone())
+ .or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
+
+ let result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
+ pending = result.pending || pending;
+ for new_binding in result.bindings {
+ match bindings
+ .iter()
+ .position(|el| el.keystrokes.len() < new_binding.keystrokes.len())
+ {
+ Some(idx) => {
+ bindings.insert(idx, new_binding);
+ }
+ None => bindings.push(new_binding),
+ }
+ }
+ context_stack.pop();
}
+
+ KeymatchResult { bindings, pending }
}
pub fn has_pending_keystrokes(&self) -> bool {
@@ -2,6 +2,7 @@ use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
+/// A keybinding and it's associated metadata, from the keymap.
pub struct KeyBinding {
pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
@@ -19,10 +20,12 @@ impl Clone for KeyBinding {
}
impl KeyBinding {
+ /// Construct a new keybinding from the given data.
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
}
+ /// Load a keybinding from the given raw data.
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
let context = if let Some(context) = context {
Some(KeyBindingContextPredicate::parse(context)?)
@@ -42,11 +45,12 @@ impl KeyBinding {
})
}
+ /// Check if the given keystrokes match this binding.
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
// If the binding is completed, push it onto the matches list
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
- KeyMatch::Some(vec![self.action.boxed_clone()])
+ KeyMatch::Matched
} else {
KeyMatch::Pending
}
@@ -55,10 +59,12 @@ impl KeyBinding {
}
}
+ /// Get the keystrokes associated with this binding
pub fn keystrokes(&self) -> &[Keystroke] {
self.keystrokes.as_slice()
}
+ /// Get the action associated with this binding
pub fn action(&self) -> &dyn Action {
self.action.as_ref()
}
@@ -3,6 +3,10 @@ use anyhow::{anyhow, Result};
use smallvec::SmallVec;
use std::fmt;
+/// A datastructure for resolving whether an action should be dispatched
+/// at this point in the element tree. Contains a set of identifiers
+/// and/or key value pairs representing the current context for the
+/// keymap.
#[derive(Clone, Default, Eq, PartialEq, Hash)]
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
@@ -21,6 +25,11 @@ impl<'a> TryFrom<&'a str> for KeyContext {
}
impl KeyContext {
+ /// Parse a key context from a string.
+ /// The key context format is very simple:
+ /// - either a single identifier, such as `StatusBar`
+ /// - or a key value pair, such as `mode = visible`
+ /// - separated by whitespace, such as `StatusBar mode = visible`
pub fn parse(source: &str) -> Result<Self> {
let mut context = Self::default();
let source = skip_whitespace(source);
@@ -53,14 +62,17 @@ impl KeyContext {
Self::parse_expr(source, context)
}
+ /// Check if this context is empty.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
+ /// Clear this context.
pub fn clear(&mut self) {
self.0.clear();
}
+ /// Extend this context with another context.
pub fn extend(&mut self, other: &Self) {
for entry in &other.0 {
if !self.contains(&entry.key) {
@@ -69,6 +81,7 @@ impl KeyContext {
}
}
+ /// Add an identifier to this context, if it's not already in this context.
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
let key = identifier.into();
@@ -77,6 +90,7 @@ impl KeyContext {
}
}
+ /// Set a key value pair in this context, if it's not already set.
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
let key = key.into();
if !self.contains(&key) {
@@ -87,10 +101,12 @@ impl KeyContext {
}
}
+ /// Check if this context contains a given identifier or key.
pub fn contains(&self, key: &str) -> bool {
self.0.iter().any(|entry| entry.key.as_ref() == key)
}
+ /// Get the associated value for a given identifier or key.
pub fn get(&self, key: &str) -> Option<&SharedString> {
self.0
.iter()
@@ -117,20 +133,31 @@ impl fmt::Debug for KeyContext {
}
}
+/// A datastructure for resolving whether an action should be dispatched
+/// Representing a small language for describing which contexts correspond
+/// to which actions.
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum KeyBindingContextPredicate {
+ /// A predicate that will match a given identifier.
Identifier(SharedString),
+ /// A predicate that will match a given key-value pair.
Equal(SharedString, SharedString),
+ /// A predicate that will match a given key-value pair not being present.
NotEqual(SharedString, SharedString),
+ /// A predicate that will match a given predicate appearing below another predicate.
+ /// in the element tree
Child(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
+ /// Predicate that will invert another predicate.
Not(Box<KeyBindingContextPredicate>),
+ /// A predicate that will match if both of its children match.
And(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
+ /// A predicate that will match if either of its children match.
Or(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
@@ -138,6 +165,34 @@ pub enum KeyBindingContextPredicate {
}
impl KeyBindingContextPredicate {
+ /// Parse a string in the same format as the keymap's context field.
+ ///
+ /// A basic equivalence check against a set of identifiers can performed by
+ /// simply writing a string:
+ ///
+ /// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
+ ///
+ /// You can also specify a key-value pair:
+ ///
+ /// `mode == visible` -> A predicate that will match a context with the key `mode`
+ /// with the value `visible`
+ ///
+ /// And a logical operations combining these two checks:
+ ///
+ /// `StatusBar && mode == visible` -> A predicate that will match a context with the
+ /// identifier `StatusBar` and the key `mode`
+ /// with the value `visible`
+ ///
+ ///
+ /// There is also a special child `>` operator that will match a predicate that is
+ /// below another predicate:
+ ///
+ /// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
+ /// and a child context that has the key `mode` with the
+ /// value `visible`
+ ///
+ /// This syntax supports `!=`, `||` and `&&` as logical operators.
+ /// You can also preface an operation or check with a `!` to negate it.
pub fn parse(source: &str) -> Result<Self> {
let source = skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
@@ -148,6 +203,7 @@ impl KeyBindingContextPredicate {
}
}
+ /// Eval a predicate against a set of contexts, arranged from lowest to highest.
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
let Some(context) = contexts.last() else {
return false;
@@ -6,9 +6,12 @@ use std::{
collections::HashMap,
};
+/// An opaque identifier of which version of the keymap is currently active.
+/// The keymap's version is changed whenever bindings are added or removed.
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct KeymapVersion(usize);
+/// A collection of key bindings for the user's application.
#[derive(Default)]
pub struct Keymap {
bindings: Vec<KeyBinding>,
@@ -19,16 +22,19 @@ pub struct Keymap {
}
impl Keymap {
+ /// Create a new keymap with the given bindings.
pub fn new(bindings: Vec<KeyBinding>) -> Self {
let mut this = Self::default();
this.add_bindings(bindings);
this
}
+ /// Get the current version of the keymap.
pub fn version(&self) -> KeymapVersion {
self.version
}
+ /// Add more bindings to the keymap.
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
let no_action_id = (NoAction {}).type_id();
@@ -51,6 +57,7 @@ impl Keymap {
self.version.0 += 1;
}
+ /// Reset this keymap to its initial state.
pub fn clear(&mut self) {
self.bindings.clear();
self.binding_indices_by_action_id.clear();
@@ -77,6 +84,7 @@ impl Keymap {
.filter(move |binding| binding.action().partial_eq(action))
}
+ /// Check if the given binding is enabled, given a certain key context.
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
// If binding has a context predicate, it must match the current context,
if let Some(predicate) = &binding.context_predicate {
@@ -1,13 +1,19 @@
-use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
+use crate::{KeyBinding, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
+use smallvec::SmallVec;
use std::sync::Arc;
-pub struct KeystrokeMatcher {
+pub(crate) struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
+pub struct KeymatchResult {
+ pub bindings: SmallVec<[KeyBinding; 1]>,
+ pub pending: bool,
+}
+
impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
@@ -18,10 +24,6 @@ impl KeystrokeMatcher {
}
}
- pub fn clear_pending(&mut self) {
- self.pending_keystrokes.clear();
- }
-
pub fn has_pending_keystrokes(&self) -> bool {
!self.pending_keystrokes.is_empty()
}
@@ -35,11 +37,11 @@ impl KeystrokeMatcher {
/// - KeyMatch::Complete(matches) =>
/// One or more bindings have received the necessary key presses.
/// Bindings added later will take precedence over earlier bindings.
- pub fn match_keystroke(
+ pub(crate) fn match_keystroke(
&mut self,
keystroke: &Keystroke,
context_stack: &[KeyContext],
- ) -> KeyMatch {
+ ) -> KeymatchResult {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
if keymap.version() != self.keymap_version {
@@ -48,7 +50,7 @@ impl KeystrokeMatcher {
}
let mut pending_key = None;
- let mut found_actions = Vec::new();
+ let mut bindings = SmallVec::new();
for binding in keymap.bindings().rev() {
if !keymap.binding_enabled(binding, context_stack) {
@@ -58,8 +60,8 @@ impl KeystrokeMatcher {
for candidate in keystroke.match_candidates() {
self.pending_keystrokes.push(candidate.clone());
match binding.match_keystrokes(&self.pending_keystrokes) {
- KeyMatch::Some(mut actions) => {
- found_actions.append(&mut actions);
+ KeyMatch::Matched => {
+ bindings.push(binding.clone());
}
KeyMatch::Pending => {
pending_key.get_or_insert(candidate);
@@ -70,399 +72,31 @@ impl KeystrokeMatcher {
}
}
- if !found_actions.is_empty() {
- self.pending_keystrokes.clear();
- return KeyMatch::Some(found_actions);
+ if bindings.len() == 0 && pending_key.is_none() && self.pending_keystrokes.len() > 0 {
+ drop(keymap);
+ self.pending_keystrokes.remove(0);
+ return self.match_keystroke(keystroke, context_stack);
}
- if let Some(pending_key) = pending_key {
+ let pending = if let Some(pending_key) = pending_key {
self.pending_keystrokes.push(pending_key);
- KeyMatch::Pending
+ true
} else {
self.pending_keystrokes.clear();
- KeyMatch::None
- }
+ false
+ };
+
+ KeymatchResult { bindings, pending }
}
}
-#[derive(Debug)]
+/// The result of matching a keystroke against a given keybinding.
+/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
+/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
+/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
+#[derive(Debug, PartialEq)]
pub enum KeyMatch {
None,
Pending,
- Some(Vec<Box<dyn Action>>),
-}
-
-impl KeyMatch {
- pub fn is_some(&self) -> bool {
- matches!(self, KeyMatch::Some(_))
- }
-
- pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
- match self {
- KeyMatch::Some(matches) => Some(matches),
- _ => None,
- }
- }
-}
-
-impl PartialEq for KeyMatch {
- fn eq(&self, other: &Self) -> bool {
- match (self, other) {
- (KeyMatch::None, KeyMatch::None) => true,
- (KeyMatch::Pending, KeyMatch::Pending) => true,
- (KeyMatch::Some(a), KeyMatch::Some(b)) => {
- if a.len() != b.len() {
- return false;
- }
-
- for (a, b) in a.iter().zip(b.iter()) {
- if !a.partial_eq(b.as_ref()) {
- return false;
- }
- }
-
- true
- }
- _ => false,
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
-
- use serde_derive::Deserialize;
-
- use super::*;
- use crate::{self as gpui, KeyBindingContextPredicate, Modifiers};
- use crate::{actions, KeyBinding};
-
- #[test]
- fn test_keymap_and_view_ordering() {
- actions!(test, [EditorAction, ProjectPanelAction]);
-
- let mut editor = KeyContext::default();
- editor.add("Editor");
-
- let mut project_panel = KeyContext::default();
- project_panel.add("ProjectPanel");
-
- // Editor 'deeper' in than project panel
- let dispatch_path = vec![project_panel, editor];
-
- // But editor actions 'higher' up in keymap
- let keymap = Keymap::new(vec![
- KeyBinding::new("left", EditorAction, Some("Editor")),
- KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")),
- ]);
-
- let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
-
- let matches = matcher
- .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path)
- .matches()
- .unwrap();
-
- assert!(matches[0].partial_eq(&EditorAction));
- assert!(matches.get(1).is_none());
- }
-
- #[test]
- fn test_multi_keystroke_match() {
- actions!(test, [B, AB, C, D, DA, E, EF]);
-
- let mut context1 = KeyContext::default();
- context1.add("1");
-
- let mut context2 = KeyContext::default();
- context2.add("2");
-
- let dispatch_path = vec![context2, context1];
-
- let keymap = Keymap::new(vec![
- KeyBinding::new("a b", AB, Some("1")),
- KeyBinding::new("b", B, Some("2")),
- KeyBinding::new("c", C, Some("2")),
- KeyBinding::new("d", D, Some("1")),
- KeyBinding::new("d", D, Some("2")),
- KeyBinding::new("d a", DA, Some("2")),
- ]);
-
- let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
-
- // Binding with pending prefix always takes precedence
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
- KeyMatch::Pending,
- );
- // B alone doesn't match because a was pending, so AB is returned instead
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path),
- KeyMatch::Some(vec![Box::new(AB)]),
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // Without an a prefix, B is dispatched like expected
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]),
- KeyMatch::Some(vec![Box::new(B)]),
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // If a is prefixed, C will not be dispatched because there
- // was a pending binding for it
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
- KeyMatch::Pending,
- );
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path),
- KeyMatch::None,
- );
- assert!(!matcher.has_pending_keystrokes());
-
- // If a single keystroke matches multiple bindings in the tree
- // only one of them is returned.
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path),
- KeyMatch::Some(vec![Box::new(D)]),
- );
- }
-
- #[test]
- fn test_keystroke_parsing() {
- assert_eq!(
- Keystroke::parse("ctrl-p").unwrap(),
- Keystroke {
- key: "p".into(),
- modifiers: Modifiers {
- control: true,
- alt: false,
- shift: false,
- command: false,
- function: false,
- },
- ime_key: None,
- }
- );
-
- assert_eq!(
- Keystroke::parse("alt-shift-down").unwrap(),
- Keystroke {
- key: "down".into(),
- modifiers: Modifiers {
- control: false,
- alt: true,
- shift: true,
- command: false,
- function: false,
- },
- ime_key: None,
- }
- );
-
- assert_eq!(
- Keystroke::parse("shift-cmd--").unwrap(),
- Keystroke {
- key: "-".into(),
- modifiers: Modifiers {
- control: false,
- alt: false,
- shift: true,
- command: true,
- function: false,
- },
- ime_key: None,
- }
- );
- }
-
- #[test]
- fn test_context_predicate_parsing() {
- use KeyBindingContextPredicate::*;
-
- assert_eq!(
- KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
- And(
- Box::new(Identifier("a".into())),
- Box::new(Or(
- Box::new(Equal("b".into(), "c".into())),
- Box::new(NotEqual("d".into(), "e".into())),
- ))
- )
- );
-
- assert_eq!(
- KeyBindingContextPredicate::parse("!a").unwrap(),
- Not(Box::new(Identifier("a".into())),)
- );
- }
-
- #[test]
- fn test_context_predicate_eval() {
- let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
-
- let mut context = KeyContext::default();
- context.add("a");
- assert!(!predicate.eval(&[context]));
-
- let mut context = KeyContext::default();
- context.add("a");
- context.add("b");
- assert!(predicate.eval(&[context]));
-
- let mut context = KeyContext::default();
- context.add("a");
- context.set("c", "x");
- assert!(!predicate.eval(&[context]));
-
- let mut context = KeyContext::default();
- context.add("a");
- context.set("c", "d");
- assert!(predicate.eval(&[context]));
-
- let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
- assert!(predicate.eval(&[KeyContext::default()]));
- }
-
- #[test]
- fn test_context_child_predicate_eval() {
- let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
- let contexts = [
- context_set(&["a", "b"]),
- context_set(&["c", "d"]), // match this context
- context_set(&["e", "f"]),
- ];
-
- assert!(!predicate.eval(&contexts[..=0]));
- assert!(predicate.eval(&contexts[..=1]));
- assert!(!predicate.eval(&contexts[..=2]));
-
- let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap();
- let contexts = [
- context_set(&["a", "b"]),
- context_set(&["c", "d"]),
- context_set(&["e"]),
- context_set(&["a", "b"]),
- context_set(&["c"]),
- context_set(&["e"]), // only match this context
- context_set(&["f"]),
- ];
-
- assert!(!predicate.eval(&contexts[..=0]));
- assert!(!predicate.eval(&contexts[..=1]));
- assert!(!predicate.eval(&contexts[..=2]));
- assert!(!predicate.eval(&contexts[..=3]));
- assert!(!predicate.eval(&contexts[..=4]));
- assert!(predicate.eval(&contexts[..=5]));
- assert!(!predicate.eval(&contexts[..=6]));
-
- fn context_set(names: &[&str]) -> KeyContext {
- let mut keymap = KeyContext::default();
- names.iter().for_each(|name| keymap.add(name.to_string()));
- keymap
- }
- }
-
- #[test]
- fn test_matcher() {
- #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
- pub struct A(pub String);
- impl_actions!(test, [A]);
- actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
-
- #[derive(Clone, Debug, Eq, PartialEq)]
- struct ActionArg {
- a: &'static str,
- }
-
- let keymap = Keymap::new(vec![
- KeyBinding::new("a", A("x".to_string()), Some("a")),
- KeyBinding::new("b", B, Some("a")),
- KeyBinding::new("a b", Ab, Some("a || b")),
- KeyBinding::new("$", Dollar, Some("a")),
- KeyBinding::new("\"", Quote, Some("a")),
- KeyBinding::new("alt-s", Ess, Some("a")),
- KeyBinding::new("ctrl-`", Backtick, Some("a")),
- ]);
-
- let mut context_a = KeyContext::default();
- context_a.add("a");
-
- let mut context_b = KeyContext::default();
- context_b.add("b");
-
- let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
-
- // Basic match
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
- KeyMatch::Some(vec![Box::new(A("x".to_string()))])
- );
- matcher.clear_pending();
-
- // Multi-keystroke match
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]),
- KeyMatch::Pending
- );
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
- KeyMatch::Some(vec![Box::new(Ab)])
- );
- matcher.clear_pending();
-
- // Failed matches don't interfere with matching subsequent keys
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]),
- KeyMatch::None
- );
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
- KeyMatch::Some(vec![Box::new(A("x".to_string()))])
- );
- matcher.clear_pending();
-
- let mut context_c = KeyContext::default();
- context_c.add("c");
-
- assert_eq!(
- matcher.match_keystroke(
- &Keystroke::parse("a").unwrap(),
- &[context_c.clone(), context_b.clone()]
- ),
- KeyMatch::Pending
- );
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
- KeyMatch::Some(vec![Box::new(Ab)])
- );
-
- // handle Czech $ (option + 4 key)
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("alt-รง->$").unwrap(), &[context_a.clone()]),
- KeyMatch::Some(vec![Box::new(Dollar)])
- );
-
- // handle Brazilian quote (quote key then space key)
- assert_eq!(
- matcher.match_keystroke(
- &Keystroke::parse("space->\"").unwrap(),
- &[context_a.clone()]
- ),
- KeyMatch::Some(vec![Box::new(Quote)])
- );
-
- // handle ctrl+` on a brazilian keyboard
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
- KeyMatch::Some(vec![Box::new(Backtick)])
- );
-
- // handle alt-s on a US keyboard
- assert_eq!(
- matcher.match_keystroke(&Keystroke::parse("alt-s->ร").unwrap(), &[context_a.clone()]),
- KeyMatch::Some(vec![Box::new(Ess)])
- );
- }
+ Matched,
}
@@ -6,4 +6,4 @@ mod matcher;
pub use binding::*;
pub use context::*;
pub use keymap::*;
-pub use matcher::*;
+pub(crate) use matcher::*;
@@ -6,10 +6,10 @@ mod mac;
mod test;
use crate::{
- Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
- FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
- Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
- Size, TaskLabel,
+ Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font,
+ FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout,
+ Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
+ Scene, SharedString, Size, TaskLabel, WindowContext,
};
use anyhow::anyhow;
use async_task::Runnable;
@@ -34,9 +34,9 @@ use uuid::Uuid;
pub use app_menu::*;
pub use keystroke::*;
#[cfg(target_os = "macos")]
-pub use mac::*;
+pub(crate) use mac::*;
#[cfg(any(test, feature = "test-support"))]
-pub use test::*;
+pub(crate) use test::*;
use time::UtcOffset;
#[cfg(target_os = "macos")]
@@ -69,11 +69,10 @@ pub(crate) trait Platform: 'static {
fn set_display_link_output_callback(
&self,
display_id: DisplayId,
- callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+ callback: Box<dyn FnMut() + Send>,
);
fn start_display_link(&self, display_id: DisplayId);
fn stop_display_link(&self, display_id: DisplayId);
- // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
fn open_url(&self, url: &str);
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
@@ -149,8 +148,8 @@ pub(crate) trait PlatformWindow {
fn mouse_position(&self) -> Point<Pixels>;
fn modifiers(&self) -> Modifiers;
fn as_any_mut(&mut self) -> &mut dyn Any;
- fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
- fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>>;
+ fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
+ fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
fn activate(&self);
fn set_title(&mut self, title: &str);
@@ -325,30 +324,175 @@ impl From<TileId> for etagere::AllocId {
}
}
-pub trait PlatformInputHandler: 'static {
- fn selected_text_range(&mut self) -> Option<Range<usize>>;
- fn marked_text_range(&mut self) -> Option<Range<usize>>;
- fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String>;
- fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
+pub(crate) struct PlatformInputHandler {
+ cx: AsyncWindowContext,
+ handler: Box<dyn InputHandler>,
+}
+
+impl PlatformInputHandler {
+ pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
+ Self { cx, handler }
+ }
+
+ fn selected_text_range(&mut self) -> Option<Range<usize>> {
+ self.cx
+ .update(|cx| self.handler.selected_text_range(cx))
+ .ok()
+ .flatten()
+ }
+
+ fn marked_text_range(&mut self) -> Option<Range<usize>> {
+ self.cx
+ .update(|cx| self.handler.marked_text_range(cx))
+ .ok()
+ .flatten()
+ }
+
+ fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
+ self.cx
+ .update(|cx| self.handler.text_for_range(range_utf16, cx))
+ .ok()
+ .flatten()
+ }
+
+ fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
+ self.cx
+ .update(|cx| {
+ self.handler
+ .replace_text_in_range(replacement_range, text, cx);
+ })
+ .ok();
+ }
+
+ fn replace_and_mark_text_in_range(
+ &mut self,
+ range_utf16: Option<Range<usize>>,
+ new_text: &str,
+ new_selected_range: Option<Range<usize>>,
+ ) {
+ self.cx
+ .update(|cx| {
+ self.handler.replace_and_mark_text_in_range(
+ range_utf16,
+ new_text,
+ new_selected_range,
+ cx,
+ )
+ })
+ .ok();
+ }
+
+ fn unmark_text(&mut self) {
+ self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
+ }
+
+ fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
+ self.cx
+ .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
+ .ok()
+ .flatten()
+ }
+
+ pub(crate) fn flush_pending_input(&mut self, input: &str, cx: &mut WindowContext) {
+ let Some(range) = self.handler.selected_text_range(cx) else {
+ return;
+ };
+ self.handler.replace_text_in_range(Some(range), &input, cx);
+ }
+}
+
+/// Zed's interface for handling text input from the platform's IME system
+/// This is currently a 1:1 exposure of the NSTextInputClient API:
+///
+/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
+pub trait InputHandler: 'static {
+ /// Get the range of the user's currently selected text, if any
+ /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
+ ///
+ /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
+ fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
+
+ /// Get the range of the currently marked text, if any
+ /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
+ ///
+ /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
+ fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
+
+ /// Get the text for the given document range in UTF-16 characters
+ /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
+ ///
+ /// range_utf16 is in terms of UTF-16 characters
+ fn text_for_range(
+ &mut self,
+ range_utf16: Range<usize>,
+ cx: &mut WindowContext,
+ ) -> Option<String>;
+
+ /// Replace the text in the given document range with the given text
+ /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
+ ///
+ /// replacement_range is in terms of UTF-16 characters
+ fn replace_text_in_range(
+ &mut self,
+ replacement_range: Option<Range<usize>>,
+ text: &str,
+ cx: &mut WindowContext,
+ );
+
+ /// Replace the text in the given document range with the given text,
+ /// and mark the given text as part of of an IME 'composing' state
+ /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
+ ///
+ /// range_utf16 is in terms of UTF-16 characters
+ /// new_selected_range is in terms of UTF-16 characters
fn replace_and_mark_text_in_range(
&mut self,
range_utf16: Option<Range<usize>>,
new_text: &str,
new_selected_range: Option<Range<usize>>,
+ cx: &mut WindowContext,
);
- fn unmark_text(&mut self);
- fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>>;
+
+ /// Remove the IME 'composing' state from the document
+ /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
+ fn unmark_text(&mut self, cx: &mut WindowContext);
+
+ /// Get the bounds of the given document range in screen coordinates
+ /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
+ ///
+ /// This is used for positioning the IME candidate window
+ fn bounds_for_range(
+ &mut self,
+ range_utf16: Range<usize>,
+ cx: &mut WindowContext,
+ ) -> Option<Bounds<Pixels>>;
}
+/// The variables that can be configured when creating a new window
#[derive(Debug)]
pub struct WindowOptions {
+ /// The initial bounds of the window
pub bounds: WindowBounds,
+
+ /// The titlebar configuration of the window
pub titlebar: Option<TitlebarOptions>,
+
+ /// Whether the window should be centered on the screen
pub center: bool,
+
+ /// Whether the window should be focused when created
pub focus: bool,
+
+ /// Whether the window should be shown when created
pub show: bool,
+
+ /// The kind of window to create
pub kind: WindowKind,
+
+ /// Whether the window should be movable by the user
pub is_movable: bool,
+
+ /// The display to create the window on
pub display_id: Option<DisplayId>,
}
@@ -371,46 +515,67 @@ impl Default for WindowOptions {
}
}
+/// The options that can be configured for a window's titlebar
#[derive(Debug, Default)]
pub struct TitlebarOptions {
+ /// The initial title of the window
pub title: Option<SharedString>,
- pub appears_transparent: bool,
- pub traffic_light_position: Option<Point<Pixels>>,
-}
-#[derive(Copy, Clone, Debug)]
-pub enum Appearance {
- Light,
- VibrantLight,
- Dark,
- VibrantDark,
-}
+ /// Whether the titlebar should appear transparent
+ pub appears_transparent: bool,
-impl Default for Appearance {
- fn default() -> Self {
- Self::Light
- }
+ /// The position of the macOS traffic light buttons
+ pub traffic_light_position: Option<Point<Pixels>>,
}
+/// The kind of window to create
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WindowKind {
+ /// A normal application window
Normal,
+
+ /// A window that appears above all other windows, usually used for alerts or popups
+ /// use sparingly!
PopUp,
}
+/// Which bounds algorithm to use for the initial size a window
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub enum WindowBounds {
+ /// The window should be full screen, on macOS this corresponds to the full screen feature
Fullscreen,
+
+ /// Make the window as large as the current display's size.
#[default]
Maximized,
+
+ /// Set the window to the given size in pixels
Fixed(Bounds<GlobalPixels>),
}
+/// The appearance of the window, as defined by the operating system
+/// On macOS, this corresponds to named [NSAppearance](https://developer.apple.com/documentation/appkit/nsappearance)
+/// values
#[derive(Copy, Clone, Debug)]
pub enum WindowAppearance {
+ /// A light appearance
+ ///
+ /// on macOS, this corresponds to the `aqua` appearance
Light,
+
+ /// A light appearance with vibrant colors
+ ///
+ /// on macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance
VibrantLight,
+
+ /// A dark appearance
+ ///
+ /// on macOS, this corresponds to the `darkAqua` appearance
Dark,
+
+ /// A dark appearance with vibrant colors
+ ///
+ /// on macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance
VibrantDark,
}
@@ -420,40 +585,102 @@ impl Default for WindowAppearance {
}
}
+/// The options that can be configured for a file dialog prompt
#[derive(Copy, Clone, Debug)]
pub struct PathPromptOptions {
+ /// Should the prompt allow files to be selected?
pub files: bool,
+ /// Should the prompt allow directories to be selected?
pub directories: bool,
+ /// Should the prompt allow multiple files to be selected?
pub multiple: bool,
}
+/// What kind of prompt styling to show
#[derive(Copy, Clone, Debug)]
pub enum PromptLevel {
+ /// A prompt that is shown when the user should be notified of something
Info,
+
+ /// A prompt that is shown when the user needs to be warned of a potential problem
Warning,
+
+ /// A prompt that is shown when a critical problem has occurred
Critical,
}
/// The style of the cursor (pointer)
#[derive(Copy, Clone, Debug)]
pub enum CursorStyle {
+ /// The default cursor
Arrow,
+
+ /// A text input cursor
+ /// corresponds to the CSS cursor value `text`
IBeam,
+
+ /// A crosshair cursor
+ /// corresponds to the CSS cursor value `crosshair`
Crosshair,
+
+ /// A closed hand cursor
+ /// corresponds to the CSS cursor value `grabbing`
ClosedHand,
+
+ /// An open hand cursor
+ /// corresponds to the CSS cursor value `grab`
OpenHand,
+
+ /// A pointing hand cursor
+ /// corresponds to the CSS cursor value `pointer`
PointingHand,
+
+ /// A resize left cursor
+ /// corresponds to the CSS cursor value `w-resize`
ResizeLeft,
+
+ /// A resize right cursor
+ /// corresponds to the CSS cursor value `e-resize`
ResizeRight,
+
+ /// A resize cursor to the left and right
+ /// corresponds to the CSS cursor value `col-resize`
ResizeLeftRight,
+
+ /// A resize up cursor
+ /// corresponds to the CSS cursor value `n-resize`
ResizeUp,
+
+ /// A resize down cursor
+ /// corresponds to the CSS cursor value `s-resize`
ResizeDown,
+
+ /// A resize cursor directing up and down
+ /// corresponds to the CSS cursor value `row-resize`
ResizeUpDown,
+
+ /// A cursor indicating that something will disappear if moved here
+ /// Does not correspond to a CSS cursor value
DisappearingItem,
+
+ /// A text input cursor for vertical layout
+ /// corresponds to the CSS cursor value `vertical-text`
IBeamCursorForVerticalLayout,
+
+ /// A cursor indicating that the operation is not allowed
+ /// corresponds to the CSS cursor value `not-allowed`
OperationNotAllowed,
+
+ /// A cursor indicating that the operation will result in a link
+ /// corresponds to the CSS cursor value `alias`
DragLink,
+
+ /// A cursor indicating that the operation will result in a copy
+ /// corresponds to the CSS cursor value `copy`
DragCopy,
+
+ /// A cursor indicating that the operation will result in a context menu
+ /// corresponds to the CSS cursor value `context-menu`
ContextualMenu,
}
@@ -463,6 +690,7 @@ impl Default for CursorStyle {
}
}
+/// A datastructure representing a semantic version number
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SemanticVersion {
major: usize,
@@ -501,6 +729,7 @@ impl Display for SemanticVersion {
}
}
+/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
pub(crate) text: String,
@@ -508,6 +737,7 @@ pub struct ClipboardItem {
}
impl ClipboardItem {
+ /// Create a new clipboard item with the given text
pub fn new(text: String) -> Self {
Self {
text,
@@ -515,15 +745,18 @@ impl ClipboardItem {
}
}
+ /// Create a new clipboard item with the given text and metadata
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
self
}
+ /// Get the text of the clipboard item
pub fn text(&self) -> &String {
&self.text
}
+ /// Get the metadata of the clipboard item
pub fn metadata<T>(&self) -> Option<T>
where
T: for<'a> Deserialize<'a>,
@@ -1,30 +1,49 @@
use crate::{Action, AppContext, Platform};
use util::ResultExt;
+/// A menu of the application, either a main menu or a submenu
pub struct Menu<'a> {
+ /// The name of the menu
pub name: &'a str,
+
+ /// The items in the menu
pub items: Vec<MenuItem<'a>>,
}
+/// The different kinds of items that can be in a menu
pub enum MenuItem<'a> {
+ /// A separator between items
Separator,
+
+ /// A submenu
Submenu(Menu<'a>),
+
+ /// An action that can be performed
Action {
+ /// The name of this menu item
name: &'a str,
+
+ /// the action to perform when this menu item is selected
action: Box<dyn Action>,
+
+ /// The OS Action that corresponds to this action, if any
+ /// See [`OsAction`] for more information
os_action: Option<OsAction>,
},
}
impl<'a> MenuItem<'a> {
+ /// Creates a new menu item that is a separator
pub fn separator() -> Self {
Self::Separator
}
+ /// Creates a new menu item that is a submenu
pub fn submenu(menu: Menu<'a>) -> Self {
Self::Submenu(menu)
}
+ /// Creates a new menu item that invokes an action
pub fn action(name: &'a str, action: impl Action) -> Self {
Self::Action {
name,
@@ -33,6 +52,7 @@ impl<'a> MenuItem<'a> {
}
}
+ /// Creates a new menu item that invokes an action and has an OS action
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
Self::Action {
name,
@@ -42,13 +62,31 @@ impl<'a> MenuItem<'a> {
}
}
+// TODO: As part of the global selections refactor, these should
+// be moved to GPUI-provided actions that make this association
+// without leaking the platform details to GPUI users
+
+/// OS actions are actions that are recognized by the operating system
+/// This allows the operating system to provide specialized behavior for
+/// these actions
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum OsAction {
+ /// The 'cut' action
Cut,
+
+ /// The 'copy' action
Copy,
+
+ /// The 'paste' action
Paste,
+
+ /// The 'select all' action
SelectAll,
+
+ /// The 'undo' action
Undo,
+
+ /// The 'redo' action
Redo,
}
@@ -3,51 +3,60 @@ use serde::Deserialize;
use smallvec::SmallVec;
use std::fmt::Write;
+/// A keystroke and associated metadata generated by the platform
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke {
+ /// the state of the modifier keys at the time the keystroke was generated
pub modifiers: Modifiers,
+
/// key is the character printed on the key that was pressed
/// e.g. for option-s, key is "s"
pub key: String,
+
/// ime_key is the character inserted by the IME engine when that key was pressed.
/// e.g. for option-s, ime_key is "ร"
pub ime_key: Option<String>,
}
impl Keystroke {
- // When matching a key we cannot know whether the user intended to type
- // the ime_key or the key. On some non-US keyboards keys we use in our
- // bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
- // and on some keyboards the IME handler converts a sequence of keys into a
- // specific character (for example `"` is typed as `" space` on a brazilian keyboard).
- pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
+ /// When matching a key we cannot know whether the user intended to type
+ /// the ime_key or the key itself. On some non-US keyboards keys we use in our
+ /// bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
+ /// and on some keyboards the IME handler converts a sequence of keys into a
+ /// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
+ ///
+ /// This method generates a list of potential keystroke candidates that could be matched
+ /// against when resolving a keybinding.
+ pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
let mut possibilities = SmallVec::new();
match self.ime_key.as_ref() {
- None => possibilities.push(self.clone()),
Some(ime_key) => {
- possibilities.push(Keystroke {
- modifiers: Modifiers {
- control: self.modifiers.control,
- alt: false,
- shift: false,
- command: false,
- function: false,
- },
- key: ime_key.to_string(),
- ime_key: None,
- });
+ if ime_key != &self.key {
+ possibilities.push(Keystroke {
+ modifiers: Modifiers {
+ control: self.modifiers.control,
+ alt: false,
+ shift: false,
+ command: false,
+ function: false,
+ },
+ key: ime_key.to_string(),
+ ime_key: None,
+ });
+ }
possibilities.push(Keystroke {
ime_key: None,
..self.clone()
});
}
+ None => possibilities.push(self.clone()),
}
possibilities
}
/// key syntax is:
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
- /// ime_key is only used for generating test events,
+ /// ime_key syntax is only used for generating test events,
/// when matching a key with an ime_key set will be matched without it.
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut control = false;
@@ -135,16 +144,29 @@ impl std::fmt::Display for Keystroke {
}
}
+/// The state of the modifier keys at some point in time
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Modifiers {
+ /// The control key
pub control: bool,
+
+ /// The alt key
+ /// Sometimes also known as the 'meta' key
pub alt: bool,
+
+ /// The shift key
pub shift: bool,
+
+ /// The command key, on macos
+ /// the windows key, on windows
pub command: bool,
+
+ /// The function key
pub function: bool,
}
impl Modifiers {
+ /// Returns true if any modifier key is pressed
pub fn modified(&self) -> bool {
self.control || self.alt || self.shift || self.command || self.function
}
@@ -21,13 +21,13 @@ use metal_renderer::*;
use objc::runtime::{BOOL, NO, YES};
use std::ops::Range;
-pub use dispatcher::*;
-pub use display::*;
-pub use display_linker::*;
-pub use metal_atlas::*;
-pub use platform::*;
-pub use text_system::*;
-pub use window::*;
+pub(crate) use dispatcher::*;
+pub(crate) use display::*;
+pub(crate) use display_linker::*;
+pub(crate) use metal_atlas::*;
+pub(crate) use platform::*;
+pub(crate) use text_system::*;
+pub(crate) use window::*;
trait BoolExt {
fn to_objc(self) -> BOOL;
@@ -24,7 +24,7 @@ pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
}
-pub struct MacDispatcher {
+pub(crate) struct MacDispatcher {
parker: Arc<Mutex<Parker>>,
}
@@ -3,18 +3,15 @@ use anyhow::Result;
use cocoa::{
appkit::NSScreen,
base::{id, nil},
- foundation::{NSDictionary, NSString},
+ foundation::{NSDictionary, NSPoint, NSRect, NSSize, NSString},
};
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
-use core_graphics::{
- display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
- geometry::{CGPoint, CGRect, CGSize},
-};
+use core_graphics::display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList};
use objc::{msg_send, sel, sel_impl};
use uuid::Uuid;
#[derive(Debug)]
-pub struct MacDisplay(pub(crate) CGDirectDisplayID);
+pub(crate) struct MacDisplay(pub(crate) CGDirectDisplayID);
unsafe impl Send for MacDisplay {}
@@ -24,11 +21,6 @@ impl MacDisplay {
Self::all().find(|screen| screen.id() == id)
}
- /// Get the screen with the given persistent [`Uuid`].
- pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
- Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
- }
-
/// Get the primary screen - the one with the menu bar, and whose bottom left
/// corner is at the origin of the AppKit coordinate system.
pub fn primary() -> Self {
@@ -77,14 +69,14 @@ extern "C" {
fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
}
-/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
+/// Convert the given rectangle from Cocoa's coordinate space to GPUI's coordinate space.
///
-/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
+/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
-/// screen, with the Y axis pointing downwards.
-pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
+/// screen, with the Y axis pointing downwards (matching CoreGraphics)
+pub(crate) fn global_bounds_from_ns_rect(rect: NSRect) -> Bounds<GlobalPixels> {
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
Bounds {
@@ -101,22 +93,22 @@ pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
}
}
-/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
+/// Convert the given rectangle from GPUI's coordinate system to Cocoa's native coordinate space.
///
-/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
+/// Cocoa's coordinate space has its origin at the bottom left of the primary screen,
/// with the Y axis pointing upwards.
///
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
-/// screen, with the Y axis pointing downwards.
-pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
+/// screen, with the Y axis pointing downwards (matching CoreGraphics)
+pub(crate) fn global_bounds_to_ns_rect(bounds: Bounds<GlobalPixels>) -> NSRect {
let primary_screen_height = MacDisplay::primary().bounds().size.height;
- CGRect::new(
- &CGPoint::new(
+ NSRect::new(
+ NSPoint::new(
bounds.origin.x.into(),
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
),
- &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
+ NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
)
}
@@ -155,8 +147,20 @@ impl PlatformDisplay for MacDisplay {
fn bounds(&self) -> Bounds<GlobalPixels> {
unsafe {
- let native_bounds = CGDisplayBounds(self.0);
- display_bounds_from_native(native_bounds)
+ // CGDisplayBounds is in "global display" coordinates, where 0 is
+ // the top left of the primary display.
+ let bounds = CGDisplayBounds(self.0);
+
+ Bounds {
+ origin: point(
+ GlobalPixels(bounds.origin.x as f32),
+ GlobalPixels(bounds.origin.y as f32),
+ ),
+ size: size(
+ GlobalPixels(bounds.size.width as f32),
+ GlobalPixels(bounds.size.height as f32),
+ ),
+ }
}
}
}
@@ -7,8 +7,6 @@ use std::{
use crate::DisplayId;
use collections::HashMap;
use parking_lot::Mutex;
-pub use sys::CVSMPTETime as SmtpeTime;
-pub use sys::CVTimeStamp as VideoTimestamp;
pub(crate) struct MacDisplayLinker {
links: HashMap<DisplayId, MacDisplayLink>,
@@ -27,13 +25,13 @@ impl MacDisplayLinker {
}
}
-type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>>;
+type OutputCallback = Mutex<Box<dyn FnMut() + Send>>;
impl MacDisplayLinker {
pub fn set_output_callback(
&mut self,
display_id: DisplayId,
- output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+ output_callback: Box<dyn FnMut() + Send>,
) {
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
let callback = Arc::new(Mutex::new(output_callback));
@@ -81,11 +79,11 @@ unsafe extern "C" fn trampoline(
_flags_out: *mut i64,
user_data: *mut c_void,
) -> i32 {
- if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
+ if let Some((_current_time, _output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
let output_callback: Weak<OutputCallback> =
Weak::from_raw(user_data as *mut OutputCallback);
if let Some(output_callback) = output_callback.upgrade() {
- (output_callback.lock())(current_time, output_time)
+ (output_callback.lock())()
}
mem::forget(output_callback);
}
@@ -126,7 +124,7 @@ mod sys {
#[repr(C)]
#[derive(Clone, Copy)]
- pub struct CVTimeStamp {
+ pub(crate) struct CVTimeStamp {
pub version: u32,
pub video_time_scale: i32,
pub video_time: i64,
@@ -154,7 +152,7 @@ mod sys {
#[repr(C)]
#[derive(Clone, Copy, Default)]
- pub struct CVSMPTETime {
+ pub(crate) struct CVSMPTETime {
pub subframes: i16,
pub subframe_divisor: i16,
pub counter: u32,
@@ -83,7 +83,10 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
}
impl PlatformInput {
- pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
+ pub(crate) unsafe fn from_native(
+ native_event: id,
+ window_height: Option<Pixels>,
+ ) -> Option<Self> {
let event_type = native_event.eventType();
// Filter out event types that aren't in the NSEventType enum.
@@ -10,10 +10,10 @@ use metal::Device;
use parking_lot::Mutex;
use std::borrow::Cow;
-pub struct MetalAtlas(Mutex<MetalAtlasState>);
+pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
impl MetalAtlas {
- pub fn new(device: Device) -> Self {
+ pub(crate) fn new(device: Device) -> Self {
MetalAtlas(Mutex::new(MetalAtlasState {
device: AssertSend(device),
monochrome_textures: Default::default(),
@@ -3,7 +3,7 @@ use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
- PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
+ PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
};
use anyhow::anyhow;
use block::ConcreteBlock;
@@ -139,9 +139,9 @@ unsafe fn build_classes() {
}
}
-pub struct MacPlatform(Mutex<MacPlatformState>);
+pub(crate) struct MacPlatform(Mutex<MacPlatformState>);
-pub struct MacPlatformState {
+pub(crate) struct MacPlatformState {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
text_system: Arc<MacTextSystem>,
@@ -169,7 +169,7 @@ impl Default for MacPlatform {
}
impl MacPlatform {
- pub fn new() -> Self {
+ pub(crate) fn new() -> Self {
let dispatcher = Arc::new(MacDispatcher::new());
Self(Mutex::new(MacPlatformState {
background_executor: BackgroundExecutor::new(dispatcher.clone()),
@@ -475,10 +475,6 @@ impl Platform for MacPlatform {
}
}
- // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
- // Box::new(StatusItem::add(self.fonts()))
- // }
-
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
MacDisplay::all()
.map(|screen| Rc::new(screen) as Rc<_>)
@@ -504,7 +500,7 @@ impl Platform for MacPlatform {
fn set_display_link_output_callback(
&self,
display_id: DisplayId,
- callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
+ callback: Box<dyn FnMut() + Send>,
) {
self.0
.lock()
@@ -41,7 +41,7 @@ use super::open_type;
#[allow(non_upper_case_globals)]
const kCGImageAlphaOnly: u32 = 7;
-pub struct MacTextSystem(RwLock<MacTextSystemState>);
+pub(crate) struct MacTextSystem(RwLock<MacTextSystemState>);
struct MacTextSystemState {
memory_source: MemSource,
@@ -54,7 +54,7 @@ struct MacTextSystemState {
}
impl MacTextSystem {
- pub fn new() -> Self {
+ pub(crate) fn new() -> Self {
Self(RwLock::new(MacTextSystemState {
memory_source: MemSource::empty(),
system_source: SystemSource::new(),
@@ -1,9 +1,9 @@
-use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
+use super::{global_bounds_from_ns_rect, ns_string, MacDisplay, MetalRenderer, NSRange};
use crate::{
- display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
- FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers,
- ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
- PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
+ global_bounds_to_ns_rect, platform::PlatformInputHandler, point, px, size, AnyWindowHandle,
+ Bounds, ExternalPaths, FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent,
+ Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
+ MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
};
use block::ConcreteBlock;
@@ -220,7 +220,7 @@ unsafe fn build_classes() {
};
}
-pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
+pub(crate) fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
point(
px(position.x as f32),
// MacOS screen coordinates are relative to bottom left
@@ -327,7 +327,7 @@ struct MacWindowState {
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
close_callback: Option<Box<dyn FnOnce()>>,
appearance_changed_callback: Option<Box<dyn FnMut()>>,
- input_handler: Option<Box<dyn PlatformInputHandler>>,
+ input_handler: Option<PlatformInputHandler>,
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
last_key_equivalent: Option<KeyDownEvent>,
synthetic_drag_counter: usize,
@@ -411,10 +411,8 @@ impl MacWindowState {
}
fn frame(&self) -> Bounds<GlobalPixels> {
- unsafe {
- let frame = NSWindow::frame(self.native_window);
- display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
- }
+ let frame = unsafe { NSWindow::frame(self.native_window) };
+ global_bounds_from_ns_rect(frame)
}
fn content_size(&self) -> Size<Pixels> {
@@ -448,7 +446,7 @@ impl MacWindowState {
unsafe impl Send for MacWindowState {}
-pub struct MacWindow(Arc<Mutex<MacWindowState>>);
+pub(crate) struct MacWindow(Arc<Mutex<MacWindowState>>);
impl MacWindow {
pub fn open(
@@ -516,25 +514,6 @@ impl MacWindow {
NSArray::arrayWithObject(nil, NSFilenamesPboardType)
];
- let screen = native_window.screen();
- match options.bounds {
- WindowBounds::Fullscreen => {
- native_window.toggleFullScreen_(nil);
- }
- WindowBounds::Maximized => {
- native_window.setFrame_display_(screen.visibleFrame(), YES);
- }
- WindowBounds::Fixed(bounds) => {
- let display_bounds = display.bounds();
- let frame = if bounds.intersects(&display_bounds) {
- display_bounds_to_native(bounds)
- } else {
- display_bounds_to_native(display_bounds)
- };
- native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
- }
- }
-
let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view);
@@ -656,6 +635,27 @@ impl MacWindow {
native_window.orderFront_(nil);
}
+ let screen = native_window.screen();
+ match options.bounds {
+ WindowBounds::Fullscreen => {
+ // We need to toggle full screen asynchronously as doing so may
+ // call back into the platform handlers.
+ window.toggle_full_screen()
+ }
+ WindowBounds::Maximized => {
+ native_window.setFrame_display_(screen.visibleFrame(), YES);
+ }
+ WindowBounds::Fixed(bounds) => {
+ let display_bounds = display.bounds();
+ let frame = if bounds.intersects(&display_bounds) {
+ global_bounds_to_ns_rect(bounds)
+ } else {
+ global_bounds_to_ns_rect(display_bounds)
+ };
+ native_window.setFrame_display_(frame, YES);
+ }
+ }
+
window.0.lock().move_traffic_light();
pool.drain();
@@ -764,11 +764,11 @@ impl PlatformWindow for MacWindow {
self
}
- fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
+ fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.0.as_ref().lock().input_handler = Some(input_handler);
}
- fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>> {
+ fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.0.as_ref().lock().input_handler.take()
}
@@ -1003,9 +1003,21 @@ impl PlatformWindow for MacWindow {
}
fn get_scale_factor(native_window: id) -> f32 {
- unsafe {
+ let factor = unsafe {
let screen: id = msg_send![native_window, screen];
NSScreen::backingScaleFactor(screen) as f32
+ };
+
+ // We are not certain what triggers this, but it seems that sometimes
+ // this method would return 0 (https://github.com/zed-industries/community/issues/2422)
+ // It seems most likely that this would happen if the window has no screen
+ // (if it is off-screen), though we'd expect to see viewDidChangeBackingProperties before
+ // it was rendered for real.
+ // Regardless, attempt to avoid the issue here.
+ if factor == 0.0 {
+ 2.
+ } else {
+ factor
}
}
@@ -1542,9 +1554,7 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
replacement_range,
text: text.to_string(),
});
- if text.to_string().to_ascii_lowercase() != pending_key_down.0.keystroke.key {
- pending_key_down.0.keystroke.ime_key = Some(text.to_string());
- }
+ pending_key_down.0.keystroke.ime_key = Some(text.to_string());
window_state.lock().pending_key_down = Some(pending_key_down);
}
}
@@ -1761,13 +1771,13 @@ fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id)
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
where
- F: FnOnce(&mut dyn PlatformInputHandler) -> R,
+ F: FnOnce(&mut PlatformInputHandler) -> R,
{
let window_state = unsafe { get_window_state(window) };
let mut lock = window_state.as_ref().lock();
if let Some(mut input_handler) = lock.input_handler.take() {
drop(lock);
- let result = f(input_handler.as_mut());
+ let result = f(&mut input_handler);
window_state.lock().input_handler = Some(input_handler);
Some(result)
} else {
@@ -8,7 +8,7 @@ use objc::{msg_send, sel, sel_impl};
use std::ffi::CStr;
impl WindowAppearance {
- pub unsafe fn from_native(appearance: id) -> Self {
+ pub(crate) unsafe fn from_native(appearance: id) -> Self {
let name: id = msg_send![appearance, name];
if name == NSAppearanceNameVibrantLight {
Self::VibrantLight
@@ -3,7 +3,7 @@ mod display;
mod platform;
mod window;
-pub use dispatcher::*;
-pub use display::*;
-pub use platform::*;
-pub use window::*;
+pub(crate) use dispatcher::*;
+pub(crate) use display::*;
+pub(crate) use platform::*;
+pub(crate) use window::*;
@@ -18,6 +18,7 @@ use util::post_inc;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TestDispatcherId(usize);
+#[doc(hidden)]
pub struct TestDispatcher {
id: TestDispatcherId,
state: Arc<Mutex<TestDispatcherState>>,
@@ -3,7 +3,7 @@ use anyhow::{Ok, Result};
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
#[derive(Debug)]
-pub struct TestDisplay {
+pub(crate) struct TestDisplay {
id: DisplayId,
uuid: uuid::Uuid,
bounds: Bounds<GlobalPixels>,
@@ -15,7 +15,7 @@ use std::{
};
/// TestPlatform implements the Platform trait for use in tests.
-pub struct TestPlatform {
+pub(crate) struct TestPlatform {
background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor,
@@ -178,20 +178,9 @@ impl Platform for TestPlatform {
fn set_display_link_output_callback(
&self,
_display_id: DisplayId,
- mut callback: Box<dyn FnMut(&crate::VideoTimestamp, &crate::VideoTimestamp) + Send>,
+ mut callback: Box<dyn FnMut() + Send>,
) {
- let timestamp = crate::VideoTimestamp {
- version: 0,
- video_time_scale: 0,
- video_time: 0,
- host_time: 0,
- rate_scalar: 0.0,
- video_refresh_period: 0,
- smpte_time: crate::SmtpeTime::default(),
- flags: 0,
- reserved: 0,
- };
- callback(×tamp, ×tamp)
+ callback()
}
fn start_display_link(&self, _display_id: DisplayId) {}
@@ -10,7 +10,7 @@ use std::{
sync::{self, Arc},
};
-pub struct TestWindowState {
+pub(crate) struct TestWindowState {
pub(crate) bounds: WindowBounds,
pub(crate) handle: AnyWindowHandle,
display: Rc<dyn PlatformDisplay>,
@@ -23,11 +23,11 @@ pub struct TestWindowState {
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>,
- input_handler: Option<Box<dyn PlatformInputHandler>>,
+ input_handler: Option<PlatformInputHandler>,
}
#[derive(Clone)]
-pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
+pub(crate) struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
impl TestWindow {
pub fn new(
@@ -96,7 +96,19 @@ impl TestWindow {
result
}
- pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
+ pub fn simulate_keystroke(&mut self, mut keystroke: Keystroke, is_held: bool) {
+ if keystroke.ime_key.is_none()
+ && !keystroke.modifiers.command
+ && !keystroke.modifiers.control
+ && !keystroke.modifiers.function
+ {
+ keystroke.ime_key = Some(if keystroke.modifiers.shift {
+ keystroke.key.to_ascii_uppercase().clone()
+ } else {
+ keystroke.key.clone()
+ })
+ }
+
if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
@@ -112,14 +124,12 @@ impl TestWindow {
);
};
drop(lock);
- let text = keystroke.ime_key.unwrap_or(keystroke.key);
- input_handler.replace_text_in_range(None, &text);
+ if let Some(text) = keystroke.ime_key.as_ref() {
+ input_handler.replace_text_in_range(None, &text);
+ }
self.0.lock().input_handler = Some(input_handler);
}
- pub fn edited(&self) -> bool {
- self.0.lock().edited
- }
}
impl PlatformWindow for TestWindow {
@@ -163,11 +173,11 @@ impl PlatformWindow for TestWindow {
self
}
- fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
+ fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.0.lock().input_handler = Some(input_handler);
}
- fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>> {
+ fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.0.lock().input_handler.take()
}
@@ -269,12 +279,12 @@ impl PlatformWindow for TestWindow {
}
}
-pub struct TestAtlasState {
+pub(crate) struct TestAtlasState {
next_id: u32,
tiles: HashMap<AtlasKey, AtlasTile>,
}
-pub struct TestAtlas(Mutex<TestAtlasState>);
+pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
impl TestAtlas {
pub fn new() -> Self {
@@ -1,5 +1,9 @@
+//! The GPUI prelude is a collection of traits and types that are widely used
+//! throughout the library. It is recommended to import this prelude into your
+//! application to avoid having to import each trait individually.
+
pub use crate::{
- BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement,
- IntoElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, Styled,
- VisualContext,
+ util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement,
+ InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce,
+ StatefulInteractiveElement, Styled, VisualContext,
};
@@ -10,12 +10,12 @@ pub(crate) type PointF = Point<f32>;
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
-pub type LayerId = u32;
-pub type DrawOrder = u32;
+pub(crate) type LayerId = u32;
+pub(crate) type DrawOrder = u32;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
-pub struct ViewId {
+pub(crate) struct ViewId {
low_bits: u32,
high_bits: u32,
}
@@ -38,7 +38,7 @@ impl From<ViewId> for EntityId {
}
#[derive(Default)]
-pub struct Scene {
+pub(crate) struct Scene {
layers_by_order: BTreeMap<StackingOrder, LayerId>,
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
pub(crate) shadows: Vec<Shadow>,
@@ -153,49 +153,49 @@ impl Scene {
for shadow in prev_scene.shadows.drain(..) {
if views.contains(&shadow.view_id.into()) {
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
- self.insert(&order, shadow);
+ self.insert(order, shadow);
}
}
for quad in prev_scene.quads.drain(..) {
if views.contains(&quad.view_id.into()) {
let order = &prev_scene.orders_by_layer[&quad.layer_id];
- self.insert(&order, quad);
+ self.insert(order, quad);
}
}
for path in prev_scene.paths.drain(..) {
if views.contains(&path.view_id.into()) {
let order = &prev_scene.orders_by_layer[&path.layer_id];
- self.insert(&order, path);
+ self.insert(order, path);
}
}
for underline in prev_scene.underlines.drain(..) {
if views.contains(&underline.view_id.into()) {
let order = &prev_scene.orders_by_layer[&underline.layer_id];
- self.insert(&order, underline);
+ self.insert(order, underline);
}
}
for sprite in prev_scene.monochrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
- self.insert(&order, sprite);
+ self.insert(order, sprite);
}
}
for sprite in prev_scene.polychrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
- self.insert(&order, sprite);
+ self.insert(order, sprite);
}
}
for surface in prev_scene.surfaces.drain(..) {
if views.contains(&surface.view_id.into()) {
let order = &prev_scene.orders_by_layer[&surface.layer_id];
- self.insert(&order, surface);
+ self.insert(order, surface);
}
}
}
@@ -429,7 +429,7 @@ impl<'a> Iterator for BatchIterator<'a> {
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
-pub enum PrimitiveKind {
+pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
@@ -495,7 +495,7 @@ pub(crate) enum PrimitiveBatch<'a> {
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
-pub struct Quad {
+pub(crate) struct Quad {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -527,7 +527,7 @@ impl From<Quad> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
-pub struct Underline {
+pub(crate) struct Underline {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -558,7 +558,7 @@ impl From<Underline> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
-pub struct Shadow {
+pub(crate) struct Shadow {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -655,7 +655,7 @@ impl From<PolychromeSprite> for Primitive {
}
#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Surface {
+pub(crate) struct Surface {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -685,6 +685,7 @@ impl From<Surface> for Primitive {
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct PathId(pub(crate) usize);
+/// A line made up of a series of vertices and control points.
#[derive(Debug)]
pub struct Path<P: Clone + Default + Debug> {
pub(crate) id: PathId,
@@ -701,6 +702,7 @@ pub struct Path<P: Clone + Default + Debug> {
}
impl Path<Pixels> {
+ /// Create a new path with the given starting point.
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
@@ -720,6 +722,7 @@ impl Path<Pixels> {
}
}
+ /// Scale this path by the given factor.
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
@@ -740,6 +743,7 @@ impl Path<Pixels> {
}
}
+ /// Draw a straight line from the current point to the given point.
pub fn line_to(&mut self, to: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
@@ -751,6 +755,7 @@ impl Path<Pixels> {
self.current = to;
}
+ /// Draw a curve from the current point to the given point, using the given control point.
pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
self.contour_count += 1;
if self.contour_count > 1 {
@@ -833,7 +838,7 @@ impl From<Path<ScaledPixels>> for Primitive {
#[derive(Clone, Debug)]
#[repr(C)]
-pub struct PathVertex<P: Clone + Default + Debug> {
+pub(crate) struct PathVertex<P: Clone + Default + Debug> {
pub(crate) xy_position: Point<P>,
pub(crate) st_position: Point<f32>,
pub(crate) content_mask: ContentMask<P>,
@@ -850,4 +855,4 @@ impl PathVertex<Pixels> {
}
#[derive(Copy, Clone, Debug)]
-pub struct AtlasId(pub(crate) usize);
+pub(crate) struct AtlasId(pub(crate) usize);
@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, sync::Arc};
use util::arc_cow::ArcCow;
+/// A shared string is an immutable string that can be cheaply cloned in GPUI
+/// tasks. Essentially an abstraction over an Arc<str> and &'static str,
#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
pub struct SharedString(ArcCow<'static, str>);
@@ -1,10 +1,10 @@
use std::{iter, mem, ops::Range};
use crate::{
- black, phi, point, quad, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds,
- ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement,
- Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba,
- SharedString, Size, SizeRefinement, Styled, TextRun, WindowContext,
+ black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement,
+ CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font, FontFeatures,
+ FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size,
+ SizeRefinement, Styled, TextRun,
};
use collections::HashSet;
use refineable::{Cascade, Refineable};
@@ -110,7 +110,7 @@ pub struct Style {
/// The mouse cursor style shown when the mouse pointer is over an element.
pub mouse_cursor: Option<CursorStyle>,
- pub z_index: Option<u8>,
+ pub z_index: Option<u16>,
#[cfg(debug_assertions)]
pub debug: bool,
@@ -308,61 +308,12 @@ impl Style {
}
}
- pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
- where
- C: BorrowAppContext,
- F: FnOnce(&mut C) -> R,
- {
- if self.text.is_some() {
- cx.with_text_style(Some(self.text.clone()), f)
- } else {
- f(cx)
- }
- }
-
- /// Apply overflow to content mask
- pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
- where
- C: BorrowWindow,
- F: FnOnce(&mut C) -> R,
- {
- let current_mask = cx.content_mask();
-
- let min = current_mask.bounds.origin;
- let max = current_mask.bounds.lower_right();
-
- let mask_bounds = match (
- self.overflow.x == Overflow::Visible,
- self.overflow.y == Overflow::Visible,
- ) {
- // x and y both visible
- (true, true) => return f(cx),
- // x visible, y hidden
- (true, false) => Bounds::from_corners(
- point(min.x, bounds.origin.y),
- point(max.x, bounds.lower_right().y),
- ),
- // x hidden, y visible
- (false, true) => Bounds::from_corners(
- point(bounds.origin.x, min.y),
- point(bounds.lower_right().x, max.y),
- ),
- // both hidden
- (false, false) => bounds,
- };
- let mask = ContentMask {
- bounds: mask_bounds,
- };
-
- cx.with_content_mask(Some(mask), f)
- }
-
/// Paints the background of an element styled with this style.
pub fn paint(
&self,
bounds: Bounds<Pixels>,
- cx: &mut WindowContext,
- continuation: impl FnOnce(&mut WindowContext),
+ cx: &mut ElementContext,
+ continuation: impl FnOnce(&mut ElementContext),
) {
#[cfg(debug_assertions)]
if self.debug_below {
@@ -386,7 +337,7 @@ impl Style {
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.map_or(false, |color| !color.is_transparent()) {
- cx.with_z_index(0, |cx| {
+ cx.with_z_index(1, |cx| {
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
@@ -399,12 +350,12 @@ impl Style {
});
}
- cx.with_z_index(0, |cx| {
+ cx.with_z_index(2, |cx| {
continuation(cx);
});
if self.is_border_visible() {
- cx.with_z_index(0, |cx| {
+ cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
@@ -1,18 +1,18 @@
use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
- DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length,
- Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
+ DefiniteLength, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, Position,
+ SharedString, StyleRefinement, Visibility, WhiteSpace,
};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec};
-use taffy::style::Overflow;
+use taffy::style::{Display, Overflow};
pub trait Styled: Sized {
fn style(&mut self) -> &mut StyleRefinement;
gpui_macros::style_helpers!();
- fn z_index(mut self, z_index: u8) -> Self {
+ fn z_index(mut self, z_index: u16) -> Self {
self.style().z_index = Some(z_index);
self
}
@@ -66,18 +66,24 @@ pub trait Styled: Sized {
self
}
+ /// Sets the behavior of content that overflows the container to be hidden.
+ /// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self.style().overflow.y = Some(Overflow::Hidden);
self
}
- fn overflow_hidden_x(mut self) -> Self {
+ /// Sets the behavior of content that overflows the container on the X axis to be hidden.
+ /// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
+ fn overflow_x_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self
}
- fn overflow_hidden_y(mut self) -> Self {
+ /// Sets the behavior of content that overflows the container on the Y axis to be hidden.
+ /// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
+ fn overflow_y_hidden(mut self) -> Self {
self.style().overflow.y = Some(Overflow::Hidden);
self
}
@@ -301,6 +307,13 @@ pub trait Styled: Sized {
self
}
+ /// Sets the initial size of flex items for this element.
+ /// [Docs](https://tailwindcss.com/docs/flex-basis)
+ fn flex_basis(mut self, basis: impl Into<Length>) -> Self {
+ self.style().flex_basis = Some(basis.into());
+ self
+ }
+
/// Sets the element to allow a flex item to grow to fill any available space.
/// [Docs](https://tailwindcss.com/docs/flex-grow)
fn flex_grow(mut self) -> Self {
@@ -308,6 +321,20 @@ pub trait Styled: Sized {
self
}
+ /// Sets the element to allow a flex item to shrink if needed.
+ /// [Docs](https://tailwindcss.com/docs/flex-shrink)
+ fn flex_shrink(mut self) -> Self {
+ self.style().flex_shrink = Some(1.);
+ self
+ }
+
+ /// Sets the element to prevent a flex item from shrinking.
+ /// [Docs](https://tailwindcss.com/docs/flex-shrink#dont-shrink)
+ fn flex_shrink_0(mut self) -> Self {
+ self.style().flex_shrink = Some(0.);
+ self
+ }
+
/// Sets the element to align flex items to the start of the container's cross axis.
/// [Docs](https://tailwindcss.com/docs/align-items#start)
fn items_start(mut self) -> Self {
@@ -147,12 +147,17 @@ where
}
}
+/// A handle to a subscription created by GPUI. When dropped, the subscription
+/// is cancelled and the callback will no longer be invoked.
#[must_use]
pub struct Subscription {
unsubscribe: Option<Box<dyn FnOnce() + 'static>>,
}
impl Subscription {
+ /// Detaches the subscription from this handle. The callback will
+ /// continue to be invoked until the views or models it has been
+ /// subscribed to are dropped
pub fn detach(mut self) {
self.unsubscribe.take();
}
@@ -3,12 +3,12 @@ use anyhow::anyhow;
use std::{hash::Hash, sync::Arc};
#[derive(Clone, PartialEq, Hash, Eq)]
-pub struct RenderSvgParams {
+pub(crate) struct RenderSvgParams {
pub(crate) path: SharedString,
pub(crate) size: Size<DevicePixels>,
}
-pub struct SvgRenderer {
+pub(crate) struct SvgRenderer {
asset_source: Arc<dyn AssetSource>,
}
@@ -229,6 +229,7 @@ impl TaffyLayoutEngine {
}
}
+/// A unique identifier for a layout node, generated when requesting a layout from Taffy
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct LayoutId(NodeId);
@@ -440,6 +441,7 @@ where
}
}
+/// The space available for an element to be laid out in
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels
@@ -34,6 +34,9 @@ use std::{
panic::{self, RefUnwindSafe},
};
+/// Run the given test function with the configured parameters.
+/// This is intended for use with the `gpui::test` macro
+/// and generally should not be used directly.
pub fn run_test(
mut num_iterations: u64,
max_retries: usize,
@@ -78,6 +81,7 @@ pub fn run_test(
}
}
+/// A test struct for converting an observation callback into a stream.
pub struct Observation<T> {
rx: channel::Receiver<T>,
_subscription: Subscription,
@@ -377,7 +377,7 @@ impl TextSystem {
Ok(lines)
}
- pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
+ pub(crate) fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
self.line_layout_cache.finish_frame(reused_views)
}
@@ -1,6 +1,6 @@
use crate::{
- black, fill, point, px, size, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result,
- SharedString, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
+ black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result,
+ SharedString, UnderlineStyle, WrapBoundary, WrappedLineLayout,
};
use derive_more::{Deref, DerefMut};
use smallvec::SmallVec;
@@ -24,6 +24,7 @@ pub struct ShapedLine {
}
impl ShapedLine {
+ /// The length of the line in utf-8 bytes.
pub fn len(&self) -> usize {
self.layout.len
}
@@ -32,7 +33,7 @@ impl ShapedLine {
&self,
origin: Point<Pixels>,
line_height: Pixels,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Result<()> {
paint_line(
origin,
@@ -65,7 +66,7 @@ impl WrappedLine {
&self,
origin: Point<Pixels>,
line_height: Pixels,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> Result<()> {
paint_line(
origin,
@@ -86,7 +87,7 @@ fn paint_line(
line_height: Pixels,
decoration_runs: &[DecorationRun],
wrap_boundaries: &[WrapBoundary],
- cx: &mut WindowContext<'_>,
+ cx: &mut ElementContext<'_>,
) -> Result<()> {
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + layout.ascent);
@@ -9,6 +9,58 @@ use smol::future::FutureExt;
pub use util::*;
+/// A helper trait for building complex objects with imperative conditionals in a fluent style.
+pub trait FluentBuilder {
+ /// Imperatively modify self with the given closure.
+ fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
+ where
+ Self: Sized,
+ {
+ f(self)
+ }
+
+ /// Conditionally modify self with the given closure.
+ fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+ where
+ Self: Sized,
+ {
+ self.map(|this| if condition { then(this) } else { this })
+ }
+
+ /// Conditionally unwrap and modify self with the given closure, if the given option is Some.
+ fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
+ where
+ Self: Sized,
+ {
+ self.map(|this| {
+ if let Some(value) = option {
+ then(this, value)
+ } else {
+ this
+ }
+ })
+ }
+
+ /// Conditionally modify self with one closure or another
+ fn when_else(
+ self,
+ condition: bool,
+ then: impl FnOnce(Self) -> Self,
+ otherwise: impl FnOnce(Self) -> Self,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.map(|this| {
+ if condition {
+ then(this)
+ } else {
+ otherwise(this)
+ }
+ })
+ }
+}
+
#[cfg(any(test, feature = "test-support"))]
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
where
@@ -1,10 +1,8 @@
-#![deny(missing_docs)]
-
use crate::{
- seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
- Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
- IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style, TextStyle,
- ViewContext, VisualContext, WeakModel, WindowContext,
+ seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
+ ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
+ FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
+ TextStyle, ViewContext, VisualContext, WeakModel,
};
use anyhow::{Context, Result};
use std::{
@@ -25,6 +23,7 @@ impl<V> Sealed for View<V> {}
#[doc(hidden)]
pub struct AnyViewState {
root_style: Style,
+ next_stacking_order_id: u16,
cache_key: Option<ViewCacheKey>,
element: Option<AnyElement>,
}
@@ -64,7 +63,7 @@ impl<V: 'static> View<V> {
Entity::downgrade(self)
}
- /// Update the view's state with the given function, which is passed a mutable reference and a context.
+ /// Updates the view's state with the given function, which is passed a mutable reference and a context.
pub fn update<C, R>(
&self,
cx: &mut C,
@@ -96,7 +95,7 @@ impl<V: Render> Element for View<V> {
fn request_layout(
&mut self,
_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
@@ -105,7 +104,7 @@ impl<V: Render> Element for View<V> {
})
}
- fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
}
}
@@ -156,7 +155,7 @@ impl<V: 'static> WeakView<V> {
Entity::upgrade_from(self)
}
- /// Update this view's state if it hasn't been released.
+ /// Updates this view's state if it hasn't been released.
/// Returns an error if this view has been released.
pub fn update<C, R>(
&self,
@@ -204,7 +203,7 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
- request_layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
+ request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
cache: bool,
}
@@ -252,7 +251,7 @@ impl AnyView {
&self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
cx.paint_view(self.entity_id(), |cx| {
cx.with_absolute_element_offset(origin, |cx| {
@@ -280,7 +279,7 @@ impl Element for AnyView {
fn request_layout(
&mut self,
state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
if self.cache {
@@ -294,6 +293,7 @@ impl Element for AnyView {
let root_style = cx.layout_style(layout_id).unwrap().clone();
let state = AnyViewState {
root_style,
+ next_stacking_order_id: 0,
cache_key: None,
element: Some(element),
};
@@ -301,7 +301,7 @@ impl Element for AnyView {
})
}
- fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+ fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
cx.paint_view(self.entity_id(), |cx| {
if !self.cache {
state.element.take().unwrap().paint(cx);
@@ -316,14 +316,19 @@ impl Element for AnyView {
&& !cx.window.dirty_views.contains(&self.entity_id())
&& !cx.window.refreshing
{
- cx.reuse_view();
+ cx.reuse_view(state.next_stacking_order_id);
return;
}
}
- let mut element = (self.request_layout)(self, cx).1;
- element.draw(bounds.origin, bounds.size.into(), cx);
+ if let Some(mut element) = state.element.take() {
+ 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_frame.next_stacking_order_id;
state.cache_key = Some(ViewCacheKey {
bounds,
stacking_order: cx.stacking_order().clone(),
@@ -361,7 +366,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,
- layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
+ layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
}
impl AnyWeakView {
@@ -400,11 +405,11 @@ impl std::fmt::Debug for AnyWeakView {
}
mod any_view {
- use crate::{AnyElement, AnyView, IntoElement, LayoutId, Render, WindowContext};
+ use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render};
pub(crate) fn request_layout<V: 'static + Render>(
view: &AnyView,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, AnyElement) {
let view = view.clone().downcast::<V>().unwrap();
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
@@ -1,33 +1,26 @@
-#![deny(missing_docs)]
-
use crate::{
- px, size, transparent_black, Action, AnyDrag, AnyTooltip, AnyView, AppContext, Arena,
- AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
- DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect,
- Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla,
- ImageData, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeystrokeEvent, LayoutId,
- Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent,
- MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
- PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render,
- RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow,
- SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task,
- Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions,
- SUBPIXEL_VARIANTS,
+ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
+ AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
+ DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
+ GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
+ Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
+ MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
+ PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
+ TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, WindowOptions,
};
use anyhow::{anyhow, Context as _, Result};
-use collections::{FxHashMap, FxHashSet};
+use collections::FxHashSet;
use derive_more::{Deref, DerefMut};
use futures::{
channel::{mpsc, oneshot},
StreamExt,
};
-use media::core_video::CVImageBuffer;
use parking_lot::RwLock;
use slotmap::SlotMap;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
- borrow::{Borrow, BorrowMut, Cow},
+ borrow::{Borrow, BorrowMut},
cell::RefCell,
collections::hash_map::Entry,
fmt::{Debug, Display},
@@ -40,33 +33,30 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
+ time::Duration,
};
-use util::{post_inc, ResultExt};
+use util::{measure, ResultExt};
+
+mod element_cx;
+pub use element_cx::*;
-const ACTIVE_DRAG_Z_INDEX: u8 = 1;
+const ACTIVE_DRAG_Z_INDEX: u16 = 1;
/// A global stacking order, which is created by stacking successive z-index values.
/// Each z-index will always be interpreted in the context of its parent z-index.
-#[derive(Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
-pub struct StackingOrder {
- #[deref]
- #[deref_mut]
- context_stack: SmallVec<[u8; 64]>,
- id: u32,
+#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
+pub struct StackingOrder(SmallVec<[StackingContext; 64]>);
+
+/// A single entry in a primitive's z-index stacking order
+#[derive(Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
+pub struct StackingContext {
+ pub(crate) z_index: u16,
+ pub(crate) id: u16,
}
-impl std::fmt::Debug for StackingOrder {
+impl std::fmt::Debug for StackingContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let mut stacks = self.context_stack.iter().peekable();
- write!(f, "[({}): ", self.id)?;
- while let Some(z_index) = stacks.next() {
- write!(f, "{z_index}")?;
- if stacks.peek().is_some() {
- write!(f, "->")?;
- }
- }
- write!(f, "]")?;
- Ok(())
+ write!(f, "{{{}.{}}} ", self.z_index, self.id)
}
}
@@ -100,7 +90,7 @@ impl DispatchPhase {
}
type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
-type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
+
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> bool + 'static>;
struct FocusEvent {
@@ -260,8 +250,8 @@ pub struct Window {
pub(crate) platform_window: Box<dyn PlatformWindow>,
display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
- rem_size: Pixels,
- viewport_size: Size<Pixels>,
+ pub(crate) rem_size: Pixels,
+ pub(crate) viewport_size: Size<Pixels>,
layout_engine: Option<TaffyLayoutEngine>,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
@@ -284,138 +274,54 @@ pub struct Window {
activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
+ pending_input: Option<PendingInput>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) focus_invalidated: bool,
}
-pub(crate) struct ElementStateBox {
- inner: Box<dyn Any>,
- parent_view_id: EntityId,
- #[cfg(debug_assertions)]
- type_name: &'static str,
-}
-
-struct RequestedInputHandler {
- view_id: EntityId,
- handler: Option<Box<dyn PlatformInputHandler>>,
-}
-
-struct TooltipRequest {
- view_id: EntityId,
- tooltip: AnyTooltip,
-}
-
-pub(crate) struct Frame {
+#[derive(Default, Debug)]
+struct PendingInput {
+ keystrokes: SmallVec<[Keystroke; 1]>,
+ bindings: SmallVec<[KeyBinding; 1]>,
focus: Option<FocusId>,
- window_active: bool,
- pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
- mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, EntityId, AnyMouseListener)>>,
- 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_id: u32,
- next_root_z_index: u8,
- content_mask_stack: Vec<ContentMask<Pixels>>,
- element_offset_stack: Vec<Point<Pixels>>,
- requested_input_handler: Option<RequestedInputHandler>,
- tooltip_request: Option<TooltipRequest>,
- cursor_styles: FxHashMap<EntityId, CursorStyle>,
- requested_cursor_style: Option<CursorStyle>,
- pub(crate) view_stack: Vec<EntityId>,
- pub(crate) reused_views: FxHashSet<EntityId>,
-
- #[cfg(any(test, feature = "test-support"))]
- pub(crate) debug_bounds: collections::FxHashMap<String, Bounds<Pixels>>,
+ timer: Option<Task<()>>,
}
-impl Frame {
- fn new(dispatch_tree: DispatchTree) -> Self {
- Frame {
- focus: None,
- window_active: false,
- element_states: FxHashMap::default(),
- mouse_listeners: FxHashMap::default(),
- dispatch_tree,
- scene: Scene::default(),
- depth_map: Vec::new(),
- z_index_stack: StackingOrder::default(),
- next_stacking_order_id: 0,
- next_root_z_index: 0,
- content_mask_stack: Vec::new(),
- element_offset_stack: Vec::new(),
- requested_input_handler: None,
- tooltip_request: None,
- cursor_styles: FxHashMap::default(),
- requested_cursor_style: None,
- view_stack: Vec::new(),
- reused_views: FxHashSet::default(),
+impl PendingInput {
+ fn is_noop(&self) -> bool {
+ self.bindings.is_empty() && (self.keystrokes.iter().all(|k| k.ime_key.is_none()))
+ }
- #[cfg(any(test, feature = "test-support"))]
- debug_bounds: FxHashMap::default(),
- }
+ fn input(&self) -> String {
+ self.keystrokes
+ .iter()
+ .flat_map(|k| k.ime_key.clone())
+ .collect::<Vec<String>>()
+ .join("")
}
- fn clear(&mut self) {
- self.element_states.clear();
- self.mouse_listeners.values_mut().for_each(Vec::clear);
- self.dispatch_tree.clear();
- self.depth_map.clear();
- self.next_stacking_order_id = 0;
- self.next_root_z_index = 0;
- self.reused_views.clear();
- self.scene.clear();
- self.requested_input_handler.take();
- self.tooltip_request.take();
- self.cursor_styles.clear();
- self.requested_cursor_style.take();
- debug_assert_eq!(self.view_stack.len(), 0);
- }
-
- fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
- self.focus
- .map(|focus_id| self.dispatch_tree.focus_path(focus_id))
- .unwrap_or_default()
- }
-
- fn finish(&mut self, prev_frame: &mut Self) {
- // Reuse mouse listeners that didn't change since the last frame.
- for (type_id, listeners) in &mut prev_frame.mouse_listeners {
- let next_listeners = self.mouse_listeners.entry(*type_id).or_default();
- for (order, view_id, listener) in listeners.drain(..) {
- if self.reused_views.contains(&view_id) {
- next_listeners.push((order, view_id, listener));
- }
- }
+ fn used_by_binding(&self, binding: &KeyBinding) -> bool {
+ if self.keystrokes.is_empty() {
+ return true;
}
-
- // Reuse entries in the depth map that didn't change since the last frame.
- for (order, view_id, bounds) in prev_frame.depth_map.drain(..) {
- if self.reused_views.contains(&view_id) {
- match self
- .depth_map
- .binary_search_by(|(level, _, _)| order.cmp(level))
- {
- Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)),
- }
- }
- }
-
- // Retain element states for views that didn't change since the last frame.
- for (element_id, state) in prev_frame.element_states.drain() {
- if self.reused_views.contains(&state.parent_view_id) {
- self.element_states.entry(element_id).or_insert(state);
+ let keystroke = &self.keystrokes[0];
+ for candidate in keystroke.match_candidates() {
+ if binding.match_keystrokes(&[candidate]) == KeyMatch::Pending {
+ return true;
}
}
-
- // Reuse geometry that didn't change since the last frame.
- self.scene
- .reuse_views(&self.reused_views, &mut prev_frame.scene);
- self.scene.finish();
+ false
}
}
+pub(crate) struct ElementStateBox {
+ pub(crate) inner: Box<dyn Any>,
+ pub(crate) parent_view_id: EntityId,
+ #[cfg(debug_assertions)]
+ pub(crate) type_name: &'static str,
+}
+
impl Window {
pub(crate) fn new(
handle: AnyWindowHandle,
@@ -434,7 +340,9 @@ impl Window {
platform_window.on_request_frame(Box::new({
let mut cx = cx.to_async();
move || {
- handle.update(&mut cx, |_, cx| cx.draw()).log_err();
+ measure("frame duration", || {
+ handle.update(&mut cx, |_, cx| cx.draw()).log_err();
+ })
}
}));
platform_window.on_resize(Box::new({
@@ -508,6 +416,7 @@ impl Window {
activation_observers: SubscriberSet::new(),
focus: None,
focus_enabled: true,
+ pending_input: None,
#[cfg(any(test, feature = "test-support"))]
focus_invalidated: false,
@@ -721,7 +630,7 @@ impl<'a> WindowContext<'a> {
subscription
}
- /// Create an `AsyncWindowContext`, which has a static lifetime and can be held across
+ /// Creates an [`AsyncWindowContext`], which has a static lifetime and can be held across
/// await points in async code.
pub fn to_async(&self) -> AsyncWindowContext {
AsyncWindowContext::new(self.app.to_async(), self.window.handle)
@@ -737,7 +646,7 @@ impl<'a> WindowContext<'a> {
let (tx, mut rx) = mpsc::unbounded::<()>();
self.platform.set_display_link_output_callback(
display_id,
- Box::new(move |_current_time, _output_time| _ = tx.unbounded_send(())),
+ Box::new(move || _ = tx.unbounded_send(())),
);
let consumer_task = self.app.spawn(|cx| async move {
@@ -794,7 +703,7 @@ impl<'a> WindowContext<'a> {
.spawn(|app| f(AsyncWindowContext::new(app, self.window.handle)))
}
- /// Update the global of the given type. The given closure is given simultaneous mutable
+ /// Updates the global of the given type. The given closure is given simultaneous mutable
/// access both to the global and the context.
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
@@ -806,80 +715,6 @@ impl<'a> WindowContext<'a> {
result
}
- #[must_use]
- /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which
- /// layout is being requested, along with the layout ids of any children. This method is called during
- /// calls to the `Element::layout` trait method and enables any element to participate in layout.
- pub fn request_layout(
- &mut self,
- style: &Style,
- children: impl IntoIterator<Item = LayoutId>,
- ) -> LayoutId {
- self.app.layout_id_buffer.clear();
- self.app.layout_id_buffer.extend(children);
- let rem_size = self.rem_size();
-
- self.window.layout_engine.as_mut().unwrap().request_layout(
- style,
- rem_size,
- &self.app.layout_id_buffer,
- )
- }
-
- /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
- /// this variant takes a function that is invoked during layout so you can use arbitrary logic to
- /// determine the element's size. One place this is used internally is when measuring text.
- ///
- /// The given closure is invoked at layout time with the known dimensions and available space and
- /// returns a `Size`.
- pub fn request_measured_layout<
- F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
- + 'static,
- >(
- &mut self,
- style: Style,
- measure: F,
- ) -> LayoutId {
- let rem_size = self.rem_size();
- self.window
- .layout_engine
- .as_mut()
- .unwrap()
- .request_measured_layout(style, rem_size, measure)
- }
-
- pub(crate) fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> {
- self.window
- .layout_engine
- .as_ref()
- .unwrap()
- .requested_style(layout_id)
- }
-
- /// Compute the layout for the given id within the given available space.
- /// This method is called for its side effect, typically by the framework prior to painting.
- /// After calling it, you can request the bounds of the given layout node id or any descendant.
- pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
- let mut layout_engine = self.window.layout_engine.take().unwrap();
- layout_engine.compute_layout(layout_id, available_space, self);
- self.window.layout_engine = Some(layout_engine);
- }
-
- /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
- /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
- /// in order to pass your element its `Bounds` automatically.
- pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Bounds<Pixels> {
- let mut bounds = self
- .window
- .layout_engine
- .as_mut()
- .unwrap()
- .layout_bounds(layout_id)
- .map(Into::into);
- bounds.origin += self.element_offset();
- bounds
- }
-
fn window_bounds_changed(&mut self) {
self.window.scale_factor = self.window.platform_window.scale_factor();
self.window.viewport_size = self.window.platform_window.content_size();
@@ -913,7 +748,7 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.zoom();
}
- /// Update the window's title at the platform level.
+ /// Updates the window's title at the platform level.
pub fn set_window_title(&mut self, title: &str) {
self.window.platform_window.set_title(title);
}
@@ -975,67 +810,6 @@ impl<'a> WindowContext<'a> {
self.window.default_prevented
}
- /// Register a mouse event listener on the window for the next frame. The type of event
- /// is determined by the first parameter of the given listener. When the next frame is rendered
- /// the listener will be cleared.
- pub fn on_mouse_event<Event: MouseEvent>(
- &mut self,
- mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static,
- ) {
- let view_id = self.parent_view_id();
- let order = self.window.next_frame.z_index_stack.clone();
- self.window
- .next_frame
- .mouse_listeners
- .entry(TypeId::of::<Event>())
- .or_default()
- .push((
- order,
- view_id,
- Box::new(
- move |event: &dyn Any, phase: DispatchPhase, cx: &mut WindowContext<'_>| {
- handler(event.downcast_ref().unwrap(), phase, cx)
- },
- ),
- ))
- }
-
- /// Register a key event listener on the window for the next frame. The type of event
- /// is determined by the first parameter of the given listener. When the next frame is rendered
- /// the listener will be cleared.
- ///
- /// This is a fairly low-level method, so prefer using event handlers on elements unless you have
- /// a specific need to register a global listener.
- pub fn on_key_event<Event: KeyEvent>(
- &mut self,
- listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
- ) {
- self.window.next_frame.dispatch_tree.on_key_event(Rc::new(
- move |event: &dyn Any, phase, cx: &mut WindowContext<'_>| {
- if let Some(event) = event.downcast_ref::<Event>() {
- listener(event, phase, cx)
- }
- },
- ));
- }
-
- /// Register an action listener on the window for the next frame. The type of action
- /// is determined by the first parameter of the given listener. When the next frame is rendered
- /// the listener will be cleared.
- ///
- /// This is a fairly low-level method, so prefer using action handlers on elements unless you have
- /// a specific need to register a global listener.
- pub fn on_action(
- &mut self,
- action_type: TypeId,
- listener: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
- ) {
- self.window
- .next_frame
- .dispatch_tree
- .on_action(action_type, Rc::new(listener));
- }
-
/// Determine whether the given action is available along the dispatch path to the currently focused element.
pub fn is_action_available(&self, action: &dyn Action) -> bool {
let target = self
@@ -1063,42 +837,40 @@ impl<'a> WindowContext<'a> {
self.window.modifiers
}
- /// Update the cursor style at the platform level.
- pub fn set_cursor_style(&mut self, style: CursorStyle) {
- let view_id = self.parent_view_id();
- self.window.next_frame.cursor_styles.insert(view_id, style);
- self.window.next_frame.requested_cursor_style = Some(style);
- }
-
- /// Set a tooltip to be rendered for the upcoming frame
- pub fn set_tooltip(&mut self, tooltip: AnyTooltip) {
- let view_id = self.parent_view_id();
- self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip });
- }
-
- /// 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>) {
- 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)) {
- Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)),
- }
- }
-
/// Returns true if there is no opaque layer containing the given point
- /// on top of the given level. Layers whose level is an extension of the
- /// level are not considered to be on top of the level.
- pub fn was_top_layer(&self, point: &Point<Pixels>, level: &StackingOrder) -> bool {
- for (opaque_level, _, bounds) in self.window.rendered_frame.depth_map.iter() {
- if level >= opaque_level {
- break;
+ /// 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 was_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.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.
+ return true;
}
- if bounds.contains(point) && !opaque_level.starts_with(level) {
+ 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 the opaque layer is an extension of the queried layer, we don't want
+ // to consider the opaque layer to be on top and so we ignore it.
+ // - Else, we will bail early and say that the queried layer wasn't the top one.
+ let opaque_layer_is_extension_of_queried_layer = opaque_layer.len() >= layer.len()
+ && opaque_layer
+ .iter()
+ .zip(layer.iter())
+ .all(|(a, b)| a.z_index == b.z_index);
+ if !opaque_layer_is_extension_of_queried_layer {
return false;
}
}
+
true
}
@@ -1111,11 +883,16 @@ impl<'a> WindowContext<'a> {
if level >= opaque_level {
break;
}
- if opaque_level.starts_with(&[ACTIVE_DRAG_Z_INDEX]) {
+
+ if opaque_level
+ .first()
+ .map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX)
+ .unwrap_or(false)
+ {
continue;
}
- if bounds.contains(point) && !opaque_level.starts_with(level) {
+ if bounds.contains(point) {
return false;
}
}
@@ -1127,371 +904,6 @@ impl<'a> WindowContext<'a> {
&self.window.next_frame.z_index_stack
}
- /// Paint one or more drop shadows into the scene for the next frame at the current z-index.
- pub fn paint_shadows(
- &mut self,
- bounds: Bounds<Pixels>,
- corner_radii: Corners<Pixels>,
- shadows: &[BoxShadow],
- ) {
- let scale_factor = self.scale_factor();
- let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
- for shadow in shadows {
- let mut shadow_bounds = bounds;
- shadow_bounds.origin += shadow.offset;
- shadow_bounds.dilate(shadow.spread_radius);
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Shadow {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: shadow_bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- corner_radii: corner_radii.scale(scale_factor),
- color: shadow.color,
- blur_radius: shadow.blur_radius.scale(scale_factor),
- },
- );
- }
- }
-
- /// Paint one or more quads into the scene for the next frame at the current stacking context.
- /// Quads are colored rectangular regions with an optional background, border, and corner radius.
- /// see [`fill`], [`outline`], and [`quad`] to construct this type.
- pub fn paint_quad(&mut self, quad: PaintQuad) {
- let scale_factor = self.scale_factor();
- let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Quad {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: quad.bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- background: quad.background,
- border_color: quad.border_color,
- corner_radii: quad.corner_radii.scale(scale_factor),
- border_widths: quad.border_widths.scale(scale_factor),
- },
- );
- }
-
- /// Paint the given `Path` into the scene for the next frame at the current z-index.
- pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
- let scale_factor = self.scale_factor();
- let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- path.content_mask = content_mask;
- path.color = color.into();
- path.view_id = view_id.into();
- let window = &mut *self.window;
- window
- .next_frame
- .scene
- .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.
- pub fn paint_underline(
- &mut self,
- origin: Point<Pixels>,
- width: Pixels,
- style: &UnderlineStyle,
- ) {
- let scale_factor = self.scale_factor();
- let height = if style.wavy {
- style.thickness * 3.
- } else {
- style.thickness
- };
- let bounds = Bounds {
- origin,
- size: size(width, height),
- };
- let content_mask = self.content_mask();
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Underline {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds: bounds.scale(scale_factor),
- content_mask: content_mask.scale(scale_factor),
- thickness: style.thickness.scale(scale_factor),
- color: style.color.unwrap_or_default(),
- wavy: style.wavy,
- },
- );
- }
-
- /// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
- /// The y component of the origin is the baseline of the glyph.
- pub fn paint_glyph(
- &mut self,
- origin: Point<Pixels>,
- font_id: FontId,
- glyph_id: GlyphId,
- font_size: Pixels,
- color: Hsla,
- ) -> Result<()> {
- let scale_factor = self.scale_factor();
- let glyph_origin = origin.scale(scale_factor);
- let subpixel_variant = Point {
- x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
- y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
- };
- let params = RenderGlyphParams {
- font_id,
- glyph_id,
- font_size,
- subpixel_variant,
- scale_factor,
- is_emoji: false,
- };
-
- let raster_bounds = self.text_system().raster_bounds(¶ms)?;
- if !raster_bounds.is_zero() {
- let tile =
- self.window
- .sprite_atlas
- .get_or_insert_with(¶ms.clone().into(), &mut || {
- let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
- Ok((size, Cow::Owned(bytes)))
- })?;
- let bounds = Bounds {
- origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
- size: tile.bounds.size.map(Into::into),
- };
- let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- MonochromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds,
- content_mask,
- color,
- tile,
- },
- );
- }
- Ok(())
- }
-
- /// Paint an emoji glyph into the scene for the next frame at the current z-index.
- /// The y component of the origin is the baseline of the glyph.
- pub fn paint_emoji(
- &mut self,
- origin: Point<Pixels>,
- font_id: FontId,
- glyph_id: GlyphId,
- font_size: Pixels,
- ) -> Result<()> {
- let scale_factor = self.scale_factor();
- let glyph_origin = origin.scale(scale_factor);
- let params = RenderGlyphParams {
- font_id,
- glyph_id,
- font_size,
- // We don't render emojis with subpixel variants.
- subpixel_variant: Default::default(),
- scale_factor,
- is_emoji: true,
- };
-
- let raster_bounds = self.text_system().raster_bounds(¶ms)?;
- if !raster_bounds.is_zero() {
- let tile =
- self.window
- .sprite_atlas
- .get_or_insert_with(¶ms.clone().into(), &mut || {
- let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
- Ok((size, Cow::Owned(bytes)))
- })?;
- let bounds = Bounds {
- origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
- size: tile.bounds.size.map(Into::into),
- };
- let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
-
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- PolychromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds,
- corner_radii: Default::default(),
- content_mask,
- tile,
- grayscale: false,
- },
- );
- }
- Ok(())
- }
-
- /// Paint a monochrome SVG into the scene for the next frame at the current stacking context.
- pub fn paint_svg(
- &mut self,
- bounds: Bounds<Pixels>,
- path: SharedString,
- color: Hsla,
- ) -> Result<()> {
- let scale_factor = self.scale_factor();
- let bounds = bounds.scale(scale_factor);
- // Render the SVG at twice the size to get a higher quality result.
- let params = RenderSvgParams {
- path,
- size: bounds
- .size
- .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
- };
-
- let tile =
- self.window
- .sprite_atlas
- .get_or_insert_with(¶ms.clone().into(), &mut || {
- let bytes = self.svg_renderer.render(¶ms)?;
- Ok((params.size, Cow::Owned(bytes)))
- })?;
- let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- MonochromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds,
- content_mask,
- color,
- tile,
- },
- );
-
- Ok(())
- }
-
- /// Paint an image into the scene for the next frame at the current z-index.
- pub fn paint_image(
- &mut self,
- bounds: Bounds<Pixels>,
- corner_radii: Corners<Pixels>,
- data: Arc<ImageData>,
- grayscale: bool,
- ) -> Result<()> {
- let scale_factor = self.scale_factor();
- let bounds = bounds.scale(scale_factor);
- let params = RenderImageParams { image_id: data.id };
-
- let tile = self
- .window
- .sprite_atlas
- .get_or_insert_with(¶ms.clone().into(), &mut || {
- Ok((data.size(), Cow::Borrowed(data.as_bytes())))
- })?;
- let content_mask = self.content_mask().scale(scale_factor);
- let corner_radii = corner_radii.scale(scale_factor);
- let view_id = self.parent_view_id();
-
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- PolychromeSprite {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds,
- content_mask,
- corner_radii,
- tile,
- grayscale,
- },
- );
- Ok(())
- }
-
- /// Paint a surface into the scene for the next frame at the current z-index.
- pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
- let scale_factor = self.scale_factor();
- let bounds = bounds.scale(scale_factor);
- let content_mask = self.content_mask().scale(scale_factor);
- let view_id = self.parent_view_id();
- let window = &mut *self.window;
- window.next_frame.scene.insert(
- &window.next_frame.z_index_stack,
- Surface {
- view_id: view_id.into(),
- layer_id: 0,
- order: 0,
- bounds,
- content_mask,
- image_buffer,
- },
- );
- }
-
- pub(crate) fn reuse_view(&mut self) {
- let view_id = self.parent_view_id();
- let grafted_view_ids = self
- .window
- .next_frame
- .dispatch_tree
- .reuse_view(view_id, &mut self.window.rendered_frame.dispatch_tree);
- for view_id in grafted_view_ids {
- assert!(self.window.next_frame.reused_views.insert(view_id));
-
- // Reuse the previous input handler requested during painting of the reused view.
- if self
- .window
- .rendered_frame
- .requested_input_handler
- .as_ref()
- .map_or(false, |requested| requested.view_id == view_id)
- {
- self.window.next_frame.requested_input_handler =
- self.window.rendered_frame.requested_input_handler.take();
- }
-
- // Reuse the tooltip previously requested during painting of the reused view.
- if self
- .window
- .rendered_frame
- .tooltip_request
- .as_ref()
- .map_or(false, |requested| requested.view_id == view_id)
- {
- self.window.next_frame.tooltip_request =
- self.window.rendered_frame.tooltip_request.take();
- }
-
- // Reuse the cursor styles previously requested during painting of the reused view.
- if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) {
- self.window.next_frame.cursor_styles.insert(view_id, style);
- self.window.next_frame.requested_cursor_style = Some(style);
- }
- }
- }
-
/// Draw pixels to the display for this window based on the contents of its scene.
pub(crate) fn draw(&mut self) {
self.window.dirty = false;
@@ -1504,44 +916,55 @@ impl<'a> WindowContext<'a> {
if let Some(requested_handler) = self.window.rendered_frame.requested_input_handler.as_mut()
{
- requested_handler.handler = self.window.platform_window.take_input_handler();
+ let input_handler = self.window.platform_window.take_input_handler();
+ requested_handler.handler = input_handler;
}
let root_view = self.window.root_view.take().unwrap();
-
- self.with_z_index(0, |cx| {
- cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
- for (action_type, action_listeners) in &cx.app.global_action_listeners {
- for action_listener in action_listeners.iter().cloned() {
- cx.window.next_frame.dispatch_tree.on_action(
- *action_type,
- Rc::new(move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| {
- action_listener(action, phase, cx)
- }),
- )
+ self.with_element_context(|cx| {
+ cx.with_z_index(0, |cx| {
+ cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
+ // We need to use cx.cx here so we can utilize borrow splitting
+ for (action_type, action_listeners) in &cx.cx.app.global_action_listeners {
+ for action_listener in action_listeners.iter().cloned() {
+ cx.cx.window.next_frame.dispatch_tree.on_action(
+ *action_type,
+ Rc::new(
+ move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| {
+ action_listener(action, phase, cx)
+ },
+ ),
+ )
+ }
}
- }
- let available_space = cx.window.viewport_size.map(Into::into);
- root_view.draw(Point::default(), available_space, cx);
+ let available_space = cx.window.viewport_size.map(Into::into);
+ root_view.draw(Point::default(), available_space, cx);
+ })
})
});
if let Some(active_drag) = self.app.active_drag.take() {
- self.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);
- active_drag.view.draw(offset, available_space, cx);
+ 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);
+ active_drag.view.draw(offset, available_space, cx);
+ })
});
self.active_drag = Some(active_drag);
} else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() {
- self.with_z_index(1, |cx| {
- let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
- tooltip_request.tooltip.view.draw(
- tooltip_request.tooltip.cursor_offset,
- available_space,
- cx,
- );
+ self.with_element_context(|cx| {
+ cx.with_z_index(1, |cx| {
+ let available_space =
+ size(AvailableSpace::MinContent, AvailableSpace::MinContent);
+ tooltip_request.tooltip.view.draw(
+ tooltip_request.tooltip.cursor_offset,
+ available_space,
+ cx,
+ );
+ })
});
self.window.next_frame.tooltip_request = Some(tooltip_request);
}
@@ -0,0 +1,1136 @@
+use std::{
+ any::{Any, TypeId},
+ borrow::{Borrow, BorrowMut, Cow},
+ mem,
+ rc::Rc,
+ sync::Arc,
+};
+
+use anyhow::Result;
+use collections::{FxHashMap, FxHashSet};
+use derive_more::{Deref, DerefMut};
+use media::core_video::CVImageBuffer;
+use smallvec::SmallVec;
+use util::post_inc;
+
+use crate::{
+ prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask,
+ Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox,
+ EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
+ InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
+ Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
+ RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
+ StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, Window,
+ WindowContext, SUBPIXEL_VARIANTS,
+};
+
+type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
+
+pub(crate) struct RequestedInputHandler {
+ pub(crate) view_id: EntityId,
+ pub(crate) handler: Option<PlatformInputHandler>,
+}
+
+pub(crate) struct TooltipRequest {
+ pub(crate) view_id: EntityId,
+ pub(crate) tooltip: AnyTooltip,
+}
+
+pub(crate) struct Frame {
+ pub(crate) focus: Option<FocusId>,
+ pub(crate) window_active: bool,
+ pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
+ pub(crate) mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, EntityId, AnyMouseListener)>>,
+ 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_id: 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>,
+ pub(crate) tooltip_request: Option<TooltipRequest>,
+ pub(crate) cursor_styles: FxHashMap<EntityId, CursorStyle>,
+ pub(crate) requested_cursor_style: Option<CursorStyle>,
+ pub(crate) view_stack: Vec<EntityId>,
+ pub(crate) reused_views: FxHashSet<EntityId>,
+
+ #[cfg(any(test, feature = "test-support"))]
+ pub(crate) debug_bounds: collections::FxHashMap<String, Bounds<Pixels>>,
+}
+
+impl Frame {
+ pub(crate) fn new(dispatch_tree: DispatchTree) -> Self {
+ Frame {
+ focus: None,
+ window_active: false,
+ element_states: FxHashMap::default(),
+ mouse_listeners: FxHashMap::default(),
+ dispatch_tree,
+ scene: Scene::default(),
+ depth_map: Vec::new(),
+ z_index_stack: StackingOrder::default(),
+ next_stacking_order_id: 0,
+ next_root_z_index: 0,
+ content_mask_stack: Vec::new(),
+ element_offset_stack: Vec::new(),
+ requested_input_handler: None,
+ tooltip_request: None,
+ cursor_styles: FxHashMap::default(),
+ requested_cursor_style: None,
+ view_stack: Vec::new(),
+ reused_views: FxHashSet::default(),
+
+ #[cfg(any(test, feature = "test-support"))]
+ debug_bounds: FxHashMap::default(),
+ }
+ }
+
+ pub(crate) fn clear(&mut self) {
+ self.element_states.clear();
+ self.mouse_listeners.values_mut().for_each(Vec::clear);
+ self.dispatch_tree.clear();
+ self.depth_map.clear();
+ self.next_stacking_order_id = 0;
+ self.next_root_z_index = 0;
+ self.reused_views.clear();
+ self.scene.clear();
+ self.requested_input_handler.take();
+ self.tooltip_request.take();
+ self.cursor_styles.clear();
+ self.requested_cursor_style.take();
+ debug_assert_eq!(self.view_stack.len(), 0);
+ }
+
+ pub(crate) fn focus_path(&self) -> SmallVec<[FocusId; 8]> {
+ self.focus
+ .map(|focus_id| self.dispatch_tree.focus_path(focus_id))
+ .unwrap_or_default()
+ }
+
+ pub(crate) fn finish(&mut self, prev_frame: &mut Self) {
+ // Reuse mouse listeners that didn't change since the last frame.
+ for (type_id, listeners) in &mut prev_frame.mouse_listeners {
+ let next_listeners = self.mouse_listeners.entry(*type_id).or_default();
+ for (order, view_id, listener) in listeners.drain(..) {
+ if self.reused_views.contains(&view_id) {
+ next_listeners.push((order, view_id, listener));
+ }
+ }
+ }
+
+ // Reuse entries in the depth map that didn't change since the last frame.
+ for (order, view_id, bounds) in prev_frame.depth_map.drain(..) {
+ if self.reused_views.contains(&view_id) {
+ match self
+ .depth_map
+ .binary_search_by(|(level, _, _)| order.cmp(level))
+ {
+ Ok(i) | Err(i) => self.depth_map.insert(i, (order, view_id, bounds)),
+ }
+ }
+ }
+
+ // Retain element states for views that didn't change since the last frame.
+ for (element_id, state) in prev_frame.element_states.drain() {
+ if self.reused_views.contains(&state.parent_view_id) {
+ self.element_states.entry(element_id).or_insert(state);
+ }
+ }
+
+ // Reuse geometry that didn't change since the last frame.
+ self.scene
+ .reuse_views(&self.reused_views, &mut prev_frame.scene);
+ self.scene.finish();
+ }
+}
+
+/// This context is used for assisting in the implementation of the element trait
+#[derive(Deref, DerefMut)]
+pub struct ElementContext<'a> {
+ pub(crate) cx: WindowContext<'a>,
+}
+
+impl<'a> WindowContext<'a> {
+ pub fn with_element_context<R>(&mut self, f: impl FnOnce(&mut ElementContext) -> R) -> R {
+ f(&mut ElementContext {
+ cx: WindowContext::new(self.app, self.window),
+ })
+ }
+}
+
+impl<'a> Borrow<AppContext> for ElementContext<'a> {
+ fn borrow(&self) -> &AppContext {
+ self.cx.app
+ }
+}
+
+impl<'a> BorrowMut<AppContext> for ElementContext<'a> {
+ fn borrow_mut(&mut self) -> &mut AppContext {
+ self.cx.borrow_mut()
+ }
+}
+
+impl<'a> Borrow<WindowContext<'a>> for ElementContext<'a> {
+ fn borrow(&self) -> &WindowContext<'a> {
+ &self.cx
+ }
+}
+
+impl<'a> BorrowMut<WindowContext<'a>> for ElementContext<'a> {
+ fn borrow_mut(&mut self) -> &mut WindowContext<'a> {
+ &mut self.cx
+ }
+}
+
+impl<'a> Borrow<Window> for ElementContext<'a> {
+ fn borrow(&self) -> &Window {
+ self.cx.window
+ }
+}
+
+impl<'a> BorrowMut<Window> for ElementContext<'a> {
+ fn borrow_mut(&mut self) -> &mut Window {
+ self.cx.borrow_mut()
+ }
+}
+
+impl<'a> Context for ElementContext<'a> {
+ type Result<T> = <WindowContext<'a> as Context>::Result<T>;
+
+ fn new_model<T: 'static>(
+ &mut self,
+ build_model: impl FnOnce(&mut crate::ModelContext<'_, T>) -> T,
+ ) -> Self::Result<crate::Model<T>> {
+ self.cx.new_model(build_model)
+ }
+
+ fn update_model<T, R>(
+ &mut self,
+ handle: &crate::Model<T>,
+ update: impl FnOnce(&mut T, &mut crate::ModelContext<'_, T>) -> R,
+ ) -> Self::Result<R>
+ where
+ T: 'static,
+ {
+ self.cx.update_model(handle, update)
+ }
+
+ fn read_model<T, R>(
+ &self,
+ handle: &crate::Model<T>,
+ read: impl FnOnce(&T, &AppContext) -> R,
+ ) -> Self::Result<R>
+ where
+ T: 'static,
+ {
+ self.cx.read_model(handle, read)
+ }
+
+ fn update_window<T, F>(&mut self, window: crate::AnyWindowHandle, f: F) -> Result<T>
+ where
+ F: FnOnce(crate::AnyView, &mut WindowContext<'_>) -> T,
+ {
+ self.cx.update_window(window, f)
+ }
+
+ fn read_window<T, R>(
+ &self,
+ window: &crate::WindowHandle<T>,
+ read: impl FnOnce(crate::View<T>, &AppContext) -> R,
+ ) -> Result<R>
+ where
+ T: 'static,
+ {
+ self.cx.read_window(window, read)
+ }
+}
+
+impl<'a> VisualContext for ElementContext<'a> {
+ fn new_view<V>(
+ &mut self,
+ build_view: impl FnOnce(&mut crate::ViewContext<'_, V>) -> V,
+ ) -> Self::Result<crate::View<V>>
+ where
+ V: 'static + Render,
+ {
+ self.cx.new_view(build_view)
+ }
+
+ fn update_view<V: 'static, R>(
+ &mut self,
+ view: &crate::View<V>,
+ update: impl FnOnce(&mut V, &mut crate::ViewContext<'_, V>) -> R,
+ ) -> Self::Result<R> {
+ self.cx.update_view(view, update)
+ }
+
+ fn replace_root_view<V>(
+ &mut self,
+ build_view: impl FnOnce(&mut crate::ViewContext<'_, V>) -> V,
+ ) -> Self::Result<crate::View<V>>
+ where
+ V: 'static + Render,
+ {
+ self.cx.replace_root_view(build_view)
+ }
+
+ fn focus_view<V>(&mut self, view: &crate::View<V>) -> Self::Result<()>
+ where
+ V: crate::FocusableView,
+ {
+ self.cx.focus_view(view)
+ }
+
+ fn dismiss_view<V>(&mut self, view: &crate::View<V>) -> Self::Result<()>
+ where
+ V: crate::ManagedView,
+ {
+ self.cx.dismiss_view(view)
+ }
+}
+
+impl<'a> ElementContext<'a> {
+ pub(crate) fn reuse_view(&mut self, next_stacking_order_id: u16) {
+ let view_id = self.parent_view_id();
+ let grafted_view_ids = self
+ .cx
+ .window
+ .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));
+
+ // Reuse the previous input handler requested during painting of the reused view.
+ if self
+ .window
+ .rendered_frame
+ .requested_input_handler
+ .as_ref()
+ .map_or(false, |requested| requested.view_id == view_id)
+ {
+ self.window.next_frame.requested_input_handler =
+ self.window.rendered_frame.requested_input_handler.take();
+ }
+
+ // Reuse the tooltip previously requested during painting of the reused view.
+ if self
+ .window
+ .rendered_frame
+ .tooltip_request
+ .as_ref()
+ .map_or(false, |requested| requested.view_id == view_id)
+ {
+ self.window.next_frame.tooltip_request =
+ self.window.rendered_frame.tooltip_request.take();
+ }
+
+ // Reuse the cursor styles previously requested during painting of the reused view.
+ if let Some(style) = self.window.rendered_frame.cursor_styles.remove(&view_id) {
+ self.window.next_frame.cursor_styles.insert(view_id, style);
+ self.window.next_frame.requested_cursor_style = Some(style);
+ }
+ }
+
+ debug_assert!(next_stacking_order_id >= self.window.next_frame.next_stacking_order_id);
+ self.window.next_frame.next_stacking_order_id = next_stacking_order_id;
+ }
+
+ pub fn with_text_style<F, R>(&mut self, style: Option<TextStyleRefinement>, f: F) -> R
+ where
+ F: FnOnce(&mut Self) -> R,
+ {
+ if let Some(style) = style {
+ self.push_text_style(style);
+ let result = f(self);
+ self.pop_text_style();
+ result
+ } else {
+ f(self)
+ }
+ }
+
+ /// Updates the cursor style at the platform level.
+ pub fn set_cursor_style(&mut self, style: CursorStyle) {
+ let view_id = self.parent_view_id();
+ self.window.next_frame.cursor_styles.insert(view_id, style);
+ self.window.next_frame.requested_cursor_style = Some(style);
+ }
+
+ /// Sets a tooltip to be rendered for the upcoming frame
+ pub fn set_tooltip(&mut self, tooltip: AnyTooltip) {
+ let view_id = self.parent_view_id();
+ self.window.next_frame.tooltip_request = Some(TooltipRequest { view_id, tooltip });
+ }
+
+ /// Pushes the given element id onto the global stack and invokes the given closure
+ /// with a `GlobalElementId`, which disambiguates the given id in the context of its ancestor
+ /// ids. Because elements are discarded and recreated on each frame, the `GlobalElementId` is
+ /// used to associate state with identified elements across separate frames.
+ pub fn with_element_id<R>(
+ &mut self,
+ id: Option<impl Into<ElementId>>,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ if let Some(id) = id.map(Into::into) {
+ let window = self.window_mut();
+ window.element_id_stack.push(id);
+ let result = f(self);
+ let window: &mut Window = self.borrow_mut();
+ window.element_id_stack.pop();
+ result
+ } else {
+ f(self)
+ }
+ }
+
+ /// Invoke the given function with the given content mask after intersecting it
+ /// with the current mask.
+ pub fn with_content_mask<R>(
+ &mut self,
+ mask: Option<ContentMask<Pixels>>,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ if let Some(mask) = mask {
+ let mask = mask.intersect(&self.content_mask());
+ self.window_mut().next_frame.content_mask_stack.push(mask);
+ let result = f(self);
+ self.window_mut().next_frame.content_mask_stack.pop();
+ result
+ } else {
+ f(self)
+ }
+ }
+
+ /// Invoke the given function with the content mask reset to that
+ /// of the window.
+ pub fn break_content_mask<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
+ let mask = ContentMask {
+ bounds: Bounds {
+ origin: Point::default(),
+ size: self.window().viewport_size,
+ },
+ };
+
+ let new_root_z_index = post_inc(&mut self.window_mut().next_frame.next_root_z_index);
+ let new_stacking_order_id =
+ post_inc(&mut self.window_mut().next_frame.next_stacking_order_id);
+ let new_context = StackingContext {
+ z_index: new_root_z_index,
+ id: new_stacking_order_id,
+ };
+
+ let old_stacking_order = mem::take(&mut self.window_mut().next_frame.z_index_stack);
+
+ 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().next_frame.z_index_stack = old_stacking_order;
+
+ result
+ }
+
+ /// Called during painting to invoke the given closure in a new stacking context. The given
+ /// z-index is interpreted relative to the previous call to `stack`.
+ 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(&mut self.window_mut().next_frame.next_stacking_order_id);
+ let new_context = StackingContext {
+ z_index,
+ id: new_stacking_order_id,
+ };
+
+ self.window_mut().next_frame.z_index_stack.push(new_context);
+ let result = f(self);
+ self.window_mut().next_frame.z_index_stack.pop();
+
+ result
+ }
+
+ /// Updates the global element offset relative to the current offset. This is used to implement
+ /// scrolling.
+ pub fn with_element_offset<R>(
+ &mut self,
+ offset: Point<Pixels>,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ if offset.is_zero() {
+ return f(self);
+ };
+
+ let abs_offset = self.element_offset() + offset;
+ self.with_absolute_element_offset(abs_offset, f)
+ }
+
+ /// Updates the global element offset based on the given offset. This is used to implement
+ /// drag handles and other manual painting of elements.
+ pub fn with_absolute_element_offset<R>(
+ &mut self,
+ offset: Point<Pixels>,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ self.window_mut()
+ .next_frame
+ .element_offset_stack
+ .push(offset);
+ let result = f(self);
+ self.window_mut().next_frame.element_offset_stack.pop();
+ result
+ }
+
+ /// Obtain the current element offset.
+ pub fn element_offset(&self) -> Point<Pixels> {
+ self.window()
+ .next_frame
+ .element_offset_stack
+ .last()
+ .copied()
+ .unwrap_or_default()
+ }
+
+ /// Obtain the current content mask.
+ pub fn content_mask(&self) -> ContentMask<Pixels> {
+ self.window()
+ .next_frame
+ .content_mask_stack
+ .last()
+ .cloned()
+ .unwrap_or_else(|| ContentMask {
+ bounds: Bounds {
+ origin: Point::default(),
+ size: self.window().viewport_size,
+ },
+ })
+ }
+
+ /// The size of an em for the base font of the application. Adjusting this value allows the
+ /// UI to scale, just like zooming a web page.
+ pub fn rem_size(&self) -> Pixels {
+ self.window().rem_size
+ }
+
+ /// Updates or initializes state for an element with the given id that lives across multiple
+ /// frames. If an element with this ID existed in the rendered frame, its state will be passed
+ /// to the given closure. The state returned by the closure will be stored so it can be referenced
+ /// when drawing the next frame.
+ pub fn with_element_state<S, R>(
+ &mut self,
+ id: ElementId,
+ f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
+ ) -> R
+ where
+ S: 'static,
+ {
+ self.with_element_id(Some(id), |cx| {
+ let global_id = cx.window().element_id_stack.clone();
+
+ if let Some(any) = cx
+ .window_mut()
+ .next_frame
+ .element_states
+ .remove(&global_id)
+ .or_else(|| {
+ cx.window_mut()
+ .rendered_frame
+ .element_states
+ .remove(&global_id)
+ })
+ {
+ let ElementStateBox {
+ inner,
+ parent_view_id,
+ #[cfg(debug_assertions)]
+ type_name
+ } = any;
+ // Using the extra inner option to avoid needing to reallocate a new box.
+ let mut state_box = inner
+ .downcast::<Option<S>>()
+ .map_err(|_| {
+ #[cfg(debug_assertions)]
+ {
+ anyhow::anyhow!(
+ "invalid element state type for id, requested_type {:?}, actual type: {:?}",
+ std::any::type_name::<S>(),
+ type_name
+ )
+ }
+
+ #[cfg(not(debug_assertions))]
+ {
+ anyhow::anyhow!(
+ "invalid element state type for id, requested_type {:?}",
+ std::any::type_name::<S>(),
+ )
+ }
+ })
+ .unwrap();
+
+ // Actual: Option<AnyElement> <- View
+ // Requested: () <- AnyElement
+ let state = state_box
+ .take()
+ .expect("element state is already on the stack");
+ let (result, state) = f(Some(state), cx);
+ state_box.replace(state);
+ cx.window_mut()
+ .next_frame
+ .element_states
+ .insert(global_id, ElementStateBox {
+ inner: state_box,
+ parent_view_id,
+ #[cfg(debug_assertions)]
+ type_name
+ });
+ result
+ } else {
+ let (result, state) = f(None, cx);
+ let parent_view_id = cx.parent_view_id();
+ cx.window_mut()
+ .next_frame
+ .element_states
+ .insert(global_id,
+ ElementStateBox {
+ inner: Box::new(Some(state)),
+ parent_view_id,
+ #[cfg(debug_assertions)]
+ type_name: std::any::type_name::<S>()
+ }
+
+ );
+ result
+ }
+ })
+ }
+ /// Paint one or more drop shadows into the scene for the next frame at the current z-index.
+ pub fn paint_shadows(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ corner_radii: Corners<Pixels>,
+ shadows: &[BoxShadow],
+ ) {
+ let scale_factor = self.scale_factor();
+ let content_mask = self.content_mask();
+ let view_id = self.parent_view_id();
+ let window = &mut *self.window;
+ for shadow in shadows {
+ let mut shadow_bounds = bounds;
+ shadow_bounds.origin += shadow.offset;
+ shadow_bounds.dilate(shadow.spread_radius);
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ Shadow {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds: shadow_bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ corner_radii: corner_radii.scale(scale_factor),
+ color: shadow.color,
+ blur_radius: shadow.blur_radius.scale(scale_factor),
+ },
+ );
+ }
+ }
+
+ /// Paint one or more quads into the scene for the next frame at the current stacking context.
+ /// Quads are colored rectangular regions with an optional background, border, and corner radius.
+ /// see [`fill`], [`outline`], and [`quad`] to construct this type.
+ pub fn paint_quad(&mut self, quad: PaintQuad) {
+ let scale_factor = self.scale_factor();
+ let content_mask = self.content_mask();
+ let view_id = self.parent_view_id();
+
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ Quad {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds: quad.bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ background: quad.background,
+ border_color: quad.border_color,
+ corner_radii: quad.corner_radii.scale(scale_factor),
+ border_widths: quad.border_widths.scale(scale_factor),
+ },
+ );
+ }
+
+ /// Paint the given `Path` into the scene for the next frame at the current z-index.
+ pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
+ let scale_factor = self.scale_factor();
+ let content_mask = self.content_mask();
+ let view_id = self.parent_view_id();
+
+ path.content_mask = content_mask;
+ path.color = color.into();
+ path.view_id = view_id.into();
+ let window = &mut *self.window;
+ window
+ .next_frame
+ .scene
+ .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.
+ pub fn paint_underline(
+ &mut self,
+ origin: Point<Pixels>,
+ width: Pixels,
+ style: &UnderlineStyle,
+ ) {
+ let scale_factor = self.scale_factor();
+ let height = if style.wavy {
+ style.thickness * 3.
+ } else {
+ style.thickness
+ };
+ let bounds = Bounds {
+ origin,
+ size: size(width, height),
+ };
+ let content_mask = self.content_mask();
+ let view_id = self.parent_view_id();
+
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ Underline {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds: bounds.scale(scale_factor),
+ content_mask: content_mask.scale(scale_factor),
+ thickness: style.thickness.scale(scale_factor),
+ color: style.color.unwrap_or_default(),
+ wavy: style.wavy,
+ },
+ );
+ }
+
+ /// Paint a monochrome (non-emoji) glyph into the scene for the next frame at the current z-index.
+ /// The y component of the origin is the baseline of the glyph.
+ pub fn paint_glyph(
+ &mut self,
+ origin: Point<Pixels>,
+ font_id: FontId,
+ glyph_id: GlyphId,
+ font_size: Pixels,
+ color: Hsla,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let glyph_origin = origin.scale(scale_factor);
+ let subpixel_variant = Point {
+ x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+ y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+ };
+ let params = RenderGlyphParams {
+ font_id,
+ glyph_id,
+ font_size,
+ subpixel_variant,
+ scale_factor,
+ is_emoji: false,
+ };
+
+ let raster_bounds = self.text_system().raster_bounds(¶ms)?;
+ if !raster_bounds.is_zero() {
+ let tile =
+ self.window
+ .sprite_atlas
+ .get_or_insert_with(¶ms.clone().into(), &mut || {
+ let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
+ Ok((size, Cow::Owned(bytes)))
+ })?;
+ let bounds = Bounds {
+ origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
+ size: tile.bounds.size.map(Into::into),
+ };
+ let content_mask = self.content_mask().scale(scale_factor);
+ let view_id = self.parent_view_id();
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ MonochromeSprite {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds,
+ content_mask,
+ color,
+ tile,
+ },
+ );
+ }
+ Ok(())
+ }
+
+ /// Paint an emoji glyph into the scene for the next frame at the current z-index.
+ /// The y component of the origin is the baseline of the glyph.
+ pub fn paint_emoji(
+ &mut self,
+ origin: Point<Pixels>,
+ font_id: FontId,
+ glyph_id: GlyphId,
+ font_size: Pixels,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let glyph_origin = origin.scale(scale_factor);
+ let params = RenderGlyphParams {
+ font_id,
+ glyph_id,
+ font_size,
+ // We don't render emojis with subpixel variants.
+ subpixel_variant: Default::default(),
+ scale_factor,
+ is_emoji: true,
+ };
+
+ let raster_bounds = self.text_system().raster_bounds(¶ms)?;
+ if !raster_bounds.is_zero() {
+ let tile =
+ self.window
+ .sprite_atlas
+ .get_or_insert_with(¶ms.clone().into(), &mut || {
+ let (size, bytes) = self.text_system().rasterize_glyph(¶ms)?;
+ Ok((size, Cow::Owned(bytes)))
+ })?;
+ let bounds = Bounds {
+ origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
+ size: tile.bounds.size.map(Into::into),
+ };
+ let content_mask = self.content_mask().scale(scale_factor);
+ let view_id = self.parent_view_id();
+ let window = &mut *self.window;
+
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ PolychromeSprite {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds,
+ corner_radii: Default::default(),
+ content_mask,
+ tile,
+ grayscale: false,
+ },
+ );
+ }
+ Ok(())
+ }
+
+ /// Paint a monochrome SVG into the scene for the next frame at the current stacking context.
+ pub fn paint_svg(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ path: SharedString,
+ color: Hsla,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let bounds = bounds.scale(scale_factor);
+ // Render the SVG at twice the size to get a higher quality result.
+ let params = RenderSvgParams {
+ path,
+ size: bounds
+ .size
+ .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
+ };
+
+ let tile =
+ self.window
+ .sprite_atlas
+ .get_or_insert_with(¶ms.clone().into(), &mut || {
+ let bytes = self.svg_renderer.render(¶ms)?;
+ Ok((params.size, Cow::Owned(bytes)))
+ })?;
+ let content_mask = self.content_mask().scale(scale_factor);
+ let view_id = self.parent_view_id();
+
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ MonochromeSprite {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds,
+ content_mask,
+ color,
+ tile,
+ },
+ );
+
+ Ok(())
+ }
+
+ /// Paint an image into the scene for the next frame at the current z-index.
+ pub fn paint_image(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ corner_radii: Corners<Pixels>,
+ data: Arc<ImageData>,
+ grayscale: bool,
+ ) -> Result<()> {
+ let scale_factor = self.scale_factor();
+ let bounds = bounds.scale(scale_factor);
+ let params = RenderImageParams { image_id: data.id };
+
+ let tile = self
+ .window
+ .sprite_atlas
+ .get_or_insert_with(¶ms.clone().into(), &mut || {
+ Ok((data.size(), Cow::Borrowed(data.as_bytes())))
+ })?;
+ let content_mask = self.content_mask().scale(scale_factor);
+ let corner_radii = corner_radii.scale(scale_factor);
+ let view_id = self.parent_view_id();
+
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ PolychromeSprite {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds,
+ content_mask,
+ corner_radii,
+ tile,
+ grayscale,
+ },
+ );
+ Ok(())
+ }
+
+ /// Paint a surface into the scene for the next frame at the current z-index.
+ pub fn paint_surface(&mut self, bounds: Bounds<Pixels>, image_buffer: CVImageBuffer) {
+ let scale_factor = self.scale_factor();
+ let bounds = bounds.scale(scale_factor);
+ let content_mask = self.content_mask().scale(scale_factor);
+ let view_id = self.parent_view_id();
+ let window = &mut *self.window;
+ window.next_frame.scene.insert(
+ &window.next_frame.z_index_stack,
+ Surface {
+ view_id: view_id.into(),
+ layer_id: 0,
+ order: 0,
+ bounds,
+ content_mask,
+ image_buffer,
+ },
+ );
+ }
+
+ #[must_use]
+ /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which
+ /// layout is being requested, along with the layout ids of any children. This method is called during
+ /// calls to the `Element::layout` trait method and enables any element to participate in layout.
+ pub fn request_layout(
+ &mut self,
+ style: &Style,
+ children: impl IntoIterator<Item = LayoutId>,
+ ) -> LayoutId {
+ self.app.layout_id_buffer.clear();
+ self.app.layout_id_buffer.extend(children);
+ let rem_size = self.rem_size();
+
+ self.cx
+ .window
+ .layout_engine
+ .as_mut()
+ .unwrap()
+ .request_layout(style, rem_size, &self.cx.app.layout_id_buffer)
+ }
+
+ /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
+ /// this variant takes a function that is invoked during layout so you can use arbitrary logic to
+ /// determine the element's size. One place this is used internally is when measuring text.
+ ///
+ /// The given closure is invoked at layout time with the known dimensions and available space and
+ /// returns a `Size`.
+ pub fn request_measured_layout<
+ F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+ + 'static,
+ >(
+ &mut self,
+ style: Style,
+ measure: F,
+ ) -> LayoutId {
+ let rem_size = self.rem_size();
+ self.window
+ .layout_engine
+ .as_mut()
+ .unwrap()
+ .request_measured_layout(style, rem_size, measure)
+ }
+
+ /// Compute the layout for the given id within the given available space.
+ /// This method is called for its side effect, typically by the framework prior to painting.
+ /// After calling it, you can request the bounds of the given layout node id or any descendant.
+ pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
+ let mut layout_engine = self.window.layout_engine.take().unwrap();
+ layout_engine.compute_layout(layout_id, available_space, self);
+ self.window.layout_engine = Some(layout_engine);
+ }
+
+ /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
+ /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
+ /// in order to pass your element its `Bounds` automatically.
+ pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Bounds<Pixels> {
+ let mut bounds = self
+ .window
+ .layout_engine
+ .as_mut()
+ .unwrap()
+ .layout_bounds(layout_id)
+ .map(Into::into);
+ bounds.origin += self.element_offset();
+ bounds
+ }
+
+ pub(crate) fn layout_style(&self, layout_id: LayoutId) -> Option<&Style> {
+ self.window
+ .layout_engine
+ .as_ref()
+ .unwrap()
+ .requested_style(layout_id)
+ }
+
+ /// 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>) {
+ 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)) {
+ Ok(i) | Err(i) => depth_map.insert(i, (stacking_order, view_id, bounds)),
+ }
+ }
+
+ /// Invoke the given function with the given focus handle present on the key dispatch stack.
+ /// If you want an element to participate in key dispatch, use this method to push its key context and focus handle into the stack during paint.
+ pub fn with_key_dispatch<R>(
+ &mut self,
+ context: Option<KeyContext>,
+ focus_handle: Option<FocusHandle>,
+ f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
+ ) -> R {
+ let window = &mut self.window;
+ let focus_id = focus_handle.as_ref().map(|handle| handle.id);
+ window
+ .next_frame
+ .dispatch_tree
+ .push_node(context.clone(), focus_id, None);
+
+ let result = f(focus_handle, self);
+
+ self.window.next_frame.dispatch_tree.pop_node();
+
+ result
+ }
+
+ /// Invoke the given function with the given view id present on the view stack.
+ /// This is a fairly low-level method used to layout views.
+ pub fn with_view_id<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) {
+ return f(self);
+ } else {
+ self.window.next_frame.view_stack.push(view_id);
+ let result = f(self);
+ self.window.next_frame.view_stack.pop();
+ result
+ }
+ })
+ }
+
+ /// Invoke the given function with the given view id present on the view stack.
+ /// 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) {
+ return f(self);
+ } else {
+ self.window.next_frame.view_stack.push(view_id);
+ self.window
+ .next_frame
+ .dispatch_tree
+ .push_node(None, None, Some(view_id));
+ let result = f(self);
+ self.window.next_frame.dispatch_tree.pop_node();
+ self.window.next_frame.view_stack.pop();
+ result
+ }
+ })
+ }
+
+ /// Sets an input handler, such as [`ElementInputHandler`][element_input_handler], which interfaces with the
+ /// platform to receive textual input with proper integration with concerns such
+ /// as IME interactions. This handler will be active for the upcoming frame until the following frame is
+ /// rendered.
+ ///
+ /// [element_input_handler]: crate::ElementInputHandler
+ pub fn handle_input(&mut self, focus_handle: &FocusHandle, input_handler: impl InputHandler) {
+ if focus_handle.is_focused(self) {
+ let view_id = self.parent_view_id();
+ self.window.next_frame.requested_input_handler = Some(RequestedInputHandler {
+ view_id,
+ handler: Some(PlatformInputHandler::new(
+ self.to_async(),
+ Box::new(input_handler),
+ )),
+ })
+ }
+ }
+
+ /// Register a mouse event listener on the window for the next frame. The type of event
+ /// is determined by the first parameter of the given listener. When the next frame is rendered
+ /// the listener will be cleared.
+ pub fn on_mouse_event<Event: MouseEvent>(
+ &mut self,
+ mut handler: impl FnMut(&Event, DispatchPhase, &mut ElementContext) + 'static,
+ ) {
+ let view_id = self.parent_view_id();
+ let order = self.window.next_frame.z_index_stack.clone();
+ self.window
+ .next_frame
+ .mouse_listeners
+ .entry(TypeId::of::<Event>())
+ .or_default()
+ .push((
+ order,
+ view_id,
+ Box::new(
+ move |event: &dyn Any, phase: DispatchPhase, cx: &mut ElementContext<'_>| {
+ handler(event.downcast_ref().unwrap(), phase, cx)
+ },
+ ),
+ ))
+ }
+
+ /// Register a key event listener on the window for the next frame. The type of event
+ /// is determined by the first parameter of the given listener. When the next frame is rendered
+ /// the listener will be cleared.
+ ///
+ /// This is a fairly low-level method, so prefer using event handlers on elements unless you have
+ /// a specific need to register a global listener.
+ pub fn on_key_event<Event: KeyEvent>(
+ &mut self,
+ listener: impl Fn(&Event, DispatchPhase, &mut ElementContext) + 'static,
+ ) {
+ self.window.next_frame.dispatch_tree.on_key_event(Rc::new(
+ move |event: &dyn Any, phase, cx: &mut ElementContext<'_>| {
+ if let Some(event) = event.downcast_ref::<Event>() {
+ listener(event, phase, cx)
+ }
+ },
+ ));
+ }
+}
@@ -102,7 +102,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut WindowContext) {
cx.spawn(|mut cx| async move {
let (journal_dir, entry_path) = create_entry.await?;
let (workspace, _) = cx
- .update(|_, cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
+ .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
.await?;
let opened = workspace
@@ -10,7 +10,7 @@ use crate::{
markdown::parse_markdown,
outline::OutlineItem,
syntax_map::{
- SyntaxLayerInfo, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
+ SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
SyntaxSnapshot, ToTreeSitterPoint,
},
CodeLabel, LanguageScope, Outline,
@@ -42,7 +42,13 @@ use std::{
};
use sum_tree::TreeMap;
use text::operation_queue::OperationQueue;
-pub use text::{Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, *};
+use text::*;
+pub use text::{
+ Anchor, Bias, Buffer as TextBuffer, BufferSnapshot as TextBufferSnapshot, Edit, OffsetRangeExt,
+ OffsetUtf16, Patch, Point, PointUtf16, Rope, RopeFingerprint, Selection, SelectionGoal,
+ Subscription, TextDimension, TextSummary, ToOffset, ToOffsetUtf16, ToPoint, ToPointUtf16,
+ Transaction, TransactionId, Unclipped,
+};
use theme::SyntaxTheme;
#[cfg(any(test, feature = "test-support"))]
use util::RandomCharIter;
@@ -54,15 +60,22 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
pub use lsp::DiagnosticSeverity;
lazy_static! {
+ /// A label for the background task spawned by the buffer to compute
+ /// a diff against the contents of its file.
pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
}
+/// Indicate whether a [Buffer] has permissions to edit.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Capability {
+ /// The buffer is a mutable replica.
ReadWrite,
+ /// The buffer is a read-only replica.
ReadOnly,
}
+/// An in-memory representation of a source code file, including its text,
+/// syntax trees, git status, and diagnostics.
pub struct Buffer {
text: TextBuffer,
diff_base: Option<String>,
@@ -99,9 +112,11 @@ pub struct Buffer {
capability: Capability,
}
+/// An immutable, cheaply cloneable representation of a fixed
+/// state of a buffer.
pub struct BufferSnapshot {
text: text::BufferSnapshot,
- pub git_diff: git::diff::BufferDiff,
+ git_diff: git::diff::BufferDiff,
pub(crate) syntax: SyntaxSnapshot,
file: Option<Arc<dyn File>>,
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
@@ -114,25 +129,37 @@ pub struct BufferSnapshot {
parse_count: usize,
}
+/// The kind and amount of indentation in a particular line. For now,
+/// assumes that indentation is all the same character.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct IndentSize {
+ /// The number of bytes that comprise the indentation.
pub len: u32,
+ /// The kind of whitespace used for indentation.
pub kind: IndentKind,
}
+/// A whitespace character that's used for indentation.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum IndentKind {
+ /// An ASCII space character.
#[default]
Space,
+ /// An ASCII tab character.
Tab,
}
+/// The shape of a selection cursor.
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub enum CursorShape {
+ /// A vertical bar
#[default]
Bar,
+ /// A block that surrounds the following character
Block,
+ /// An underline that runs along the following character
Underscore,
+ /// A box drawn around the following character
Hollow,
}
@@ -144,25 +171,40 @@ struct SelectionSet {
lamport_timestamp: clock::Lamport,
}
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct GroupId {
- source: Arc<str>,
- id: usize,
-}
-
+/// A diagnostic associated with a certain range of a buffer.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Diagnostic {
+ /// The name of the service that produced this diagnostic.
pub source: Option<String>,
+ /// A machine-readable code that identifies this diagnostic.
pub code: Option<String>,
+ /// Whether this diagnostic is a hint, warning, or error.
pub severity: DiagnosticSeverity,
+ /// The human-readable message associated with this diagnostic.
pub message: String,
+ /// An id that identifies the group to which this diagnostic belongs.
+ ///
+ /// When a language server produces a diagnostic with
+ /// one or more associated diagnostics, those diagnostics are all
+ /// assigned a single group id.
pub group_id: usize,
- pub is_valid: bool,
+ /// Whether this diagnostic is the primary diagnostic for its group.
+ ///
+ /// In a given group, the primary diagnostic is the top-level diagnostic
+ /// returned by the language server. The non-primary diagnostics are the
+ /// associated diagnostics.
pub is_primary: bool,
+ /// Whether this diagnostic is considered to originate from an analysis of
+ /// files on disk, as opposed to any unsaved buffer contents. This is a
+ /// property of a given diagnostic source, and is configured for a given
+ /// language server via the [LspAdapter::disk_based_diagnostic_sources] method
+ /// for the language server.
pub is_disk_based: bool,
+ /// Whether this diagnostic marks unnecessary code.
pub is_unnecessary: bool,
}
+/// TODO - move this into the `project` crate and make it private.
pub async fn prepare_completion_documentation(
documentation: &lsp::Documentation,
language_registry: &Arc<LanguageRegistry>,
@@ -194,77 +236,127 @@ pub async fn prepare_completion_documentation(
}
}
+/// Documentation associated with a [Completion].
#[derive(Clone, Debug)]
pub enum Documentation {
+ /// There is no documentation for this completion.
Undocumented,
+ /// A single line of documentation.
SingleLine(String),
+ /// Multiple lines of plain text documentation.
MultiLinePlainText(String),
+ /// Markdown documentation.
MultiLineMarkdown(ParsedMarkdown),
}
+/// A completion provided by a language server
#[derive(Clone, Debug)]
pub struct Completion {
+ /// The range of the buffer that will be replaced.
pub old_range: Range<Anchor>,
+ /// The new text that will be inserted.
pub new_text: String,
+ /// A label for this completion that is shown in the menu.
pub label: CodeLabel,
+ /// The id of the language server that produced this completion.
pub server_id: LanguageServerId,
+ /// The documentation for this completion.
pub documentation: Option<Documentation>,
+ /// The raw completion provided by the language server.
pub lsp_completion: lsp::CompletionItem,
}
+/// A code action provided by a language server.
#[derive(Clone, Debug)]
pub struct CodeAction {
+ /// The id of the language server that produced this code action.
pub server_id: LanguageServerId,
+ /// The range of the buffer where this code action is applicable.
pub range: Range<Anchor>,
+ /// The raw code action provided by the language server.
pub lsp_action: lsp::CodeAction,
}
+/// An operation used to synchronize this buffer with its other replicas.
#[derive(Clone, Debug, PartialEq)]
pub enum Operation {
+ /// A text operation.
Buffer(text::Operation),
+ /// An update to the buffer's diagnostics.
UpdateDiagnostics {
+ /// The id of the language server that produced the new diagnostics.
server_id: LanguageServerId,
+ /// The diagnostics.
diagnostics: Arc<[DiagnosticEntry<Anchor>]>,
+ /// The buffer's lamport timestamp.
lamport_timestamp: clock::Lamport,
},
+ /// An update to the most recent selections in this buffer.
UpdateSelections {
+ /// The selections.
selections: Arc<[Selection<Anchor>]>,
+ /// The buffer's lamport timestamp.
lamport_timestamp: clock::Lamport,
+ /// Whether the selections are in 'line mode'.
line_mode: bool,
+ /// The [CursorShape] associated with these selections.
cursor_shape: CursorShape,
},
+ /// An update to the characters that should trigger autocompletion
+ /// for this buffer.
UpdateCompletionTriggers {
+ /// The characters that trigger autocompletion.
triggers: Vec<String>,
+ /// The buffer's lamport timestamp.
lamport_timestamp: clock::Lamport,
},
}
+/// An event that occurs in a buffer.
#[derive(Clone, Debug, PartialEq)]
pub enum Event {
+ /// The buffer was changed in a way that must be
+ /// propagated to its other replicas.
Operation(Operation),
+ /// The buffer was edited.
Edited,
+ /// The buffer's `dirty` bit changed.
DirtyChanged,
+ /// The buffer was saved.
Saved,
+ /// The buffer's file was changed on disk.
FileHandleChanged,
+ /// The buffer was reloaded.
Reloaded,
+ /// The buffer's diff_base changed.
DiffBaseChanged,
+ /// The buffer's language was changed.
LanguageChanged,
+ /// The buffer's syntax trees were updated.
Reparsed,
+ /// The buffer's diagnostics were updated.
DiagnosticsUpdated,
+ /// The buffer gained or lost editing capabilities.
CapabilityChanged,
+ /// The buffer was explicitly requested to close.
Closed,
}
+/// The file associated with a buffer.
pub trait File: Send + Sync {
+ /// Returns the [LocalFile] associated with this file, if the
+ /// file is local.
fn as_local(&self) -> Option<&dyn LocalFile>;
+ /// Returns whether this file is local.
fn is_local(&self) -> bool {
self.as_local().is_some()
}
+ /// Returns the file's mtime.
fn mtime(&self) -> SystemTime;
/// Returns the path of this file relative to the worktree's root directory.
@@ -283,19 +375,25 @@ pub trait File: Send + Sync {
/// This is needed for looking up project-specific settings.
fn worktree_id(&self) -> usize;
+ /// Returns whether the file has been deleted.
fn is_deleted(&self) -> bool;
+ /// Converts this file into an [Any] trait object.
fn as_any(&self) -> &dyn Any;
+ /// Converts this file into a protobuf message.
fn to_proto(&self) -> rpc::proto::File;
}
+/// The file associated with a buffer, in the case where the file is on the local disk.
pub trait LocalFile: File {
/// Returns the absolute path of this file.
fn abs_path(&self, cx: &AppContext) -> PathBuf;
+ /// Loads the file's contents from disk.
fn load(&self, cx: &AppContext) -> Task<Result<String>>;
+ /// Called when the buffer is reloaded from disk.
fn buffer_reloaded(
&self,
buffer_id: u64,
@@ -307,6 +405,10 @@ pub trait LocalFile: File {
);
}
+/// The auto-indent behavior associated with an editing operation.
+/// For some editing operations, each affected line of text has its
+/// indentation recomputed. For other operations, the entire block
+/// of edited text is adjusted uniformly.
#[derive(Clone, Debug)]
pub enum AutoindentMode {
/// Indent each line of inserted text.
@@ -354,6 +456,8 @@ struct BufferChunkHighlights<'a> {
highlight_maps: Vec<HighlightMap>,
}
+/// An iterator that yields chunks of a buffer's text, along with their
+/// syntax highlights and diagnostic status.
pub struct BufferChunks<'a> {
range: Range<usize>,
chunks: text::Chunks<'a>,
@@ -366,16 +470,26 @@ pub struct BufferChunks<'a> {
highlights: Option<BufferChunkHighlights<'a>>,
}
+/// A chunk of a buffer's text, along with its syntax highlight and
+/// diagnostic status.
#[derive(Clone, Copy, Debug, Default)]
pub struct Chunk<'a> {
+ /// The text of the chunk.
pub text: &'a str,
+ /// The syntax highlighting style of the chunk.
pub syntax_highlight_id: Option<HighlightId>,
+ /// The highlight style that has been applied to this chunk in
+ /// the editor.
pub highlight_style: Option<HighlightStyle>,
+ /// The severity of diagnostic associated with this chunk, if any.
pub diagnostic_severity: Option<DiagnosticSeverity>,
+ /// Whether this chunk of text is marked as unnecessary.
pub is_unnecessary: bool,
+ /// Whether this chunk of text was originally a tab character.
pub is_tab: bool,
}
+/// A set of edits to a given version of a buffer, computed asynchronously.
pub struct Diff {
pub(crate) base_version: clock::Global,
line_ending: LineEnding,
@@ -390,24 +504,19 @@ pub(crate) struct DiagnosticEndpoint {
is_unnecessary: bool,
}
+/// A class of characters, used for characterizing a run of text.
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug)]
pub enum CharKind {
+ /// Whitespace.
Whitespace,
+ /// Punctuation.
Punctuation,
+ /// Word.
Word,
}
-impl CharKind {
- pub fn coerce_punctuation(self, treat_punctuation_as_word: bool) -> Self {
- if treat_punctuation_as_word && self == CharKind::Punctuation {
- CharKind::Word
- } else {
- self
- }
- }
-}
-
impl Buffer {
+ /// Create a new buffer with the given base text.
pub fn new<T: Into<String>>(replica_id: ReplicaId, id: u64, base_text: T) -> Self {
Self::build(
TextBuffer::new(replica_id, id, base_text.into()),
@@ -417,6 +526,7 @@ impl Buffer {
)
}
+ /// Create a new buffer that is a replica of a remote buffer.
pub fn remote(
remote_id: u64,
replica_id: ReplicaId,
@@ -431,6 +541,8 @@ impl Buffer {
)
}
+ /// Create a new buffer that is a replica of a remote buffer, populating its
+ /// state from the given protobuf message.
pub fn from_proto(
replica_id: ReplicaId,
capability: Capability,
@@ -457,6 +569,7 @@ impl Buffer {
Ok(this)
}
+ /// Serialize the buffer's state to a protobuf message.
pub fn to_proto(&self) -> proto::BufferState {
proto::BufferState {
id: self.remote_id(),
@@ -470,6 +583,7 @@ impl Buffer {
}
}
+ /// Serialize as protobufs all of the changes to the buffer since the given version.
pub fn serialize_ops(
&self,
since: Option<clock::Global>,
@@ -516,19 +630,23 @@ impl Buffer {
})
}
+ /// Assign a language to the buffer, returning the buffer.
pub fn with_language(mut self, language: Arc<Language>, cx: &mut ModelContext<Self>) -> Self {
self.set_language(Some(language), cx);
self
}
+ /// Returns the [Capability] of this buffer.
pub fn capability(&self) -> Capability {
self.capability
}
+ /// Whether this buffer can only be read.
pub fn read_only(&self) -> bool {
self.capability == Capability::ReadOnly
}
+ /// Builds a [Buffer] with the given underlying [TextBuffer], diff base, [File] and [Capability].
pub fn build(
buffer: TextBuffer,
diff_base: Option<String>,
@@ -573,6 +691,8 @@ impl Buffer {
}
}
+ /// Retrieve a snapshot of the buffer's current state. This is computationally
+ /// cheap, and allows reading from the buffer on a background thread.
pub fn snapshot(&self) -> BufferSnapshot {
let text = self.text.snapshot();
let mut syntax_map = self.syntax_map.lock();
@@ -595,30 +715,38 @@ impl Buffer {
}
}
- pub fn as_text_snapshot(&self) -> &text::BufferSnapshot {
+ #[cfg(test)]
+ pub(crate) fn as_text_snapshot(&self) -> &text::BufferSnapshot {
&self.text
}
+ /// Retrieve a snapshot of the buffer's raw text, without any
+ /// language-related state like the syntax tree or diagnostics.
pub fn text_snapshot(&self) -> text::BufferSnapshot {
self.text.snapshot()
}
+ /// The file associated with the buffer, if any.
pub fn file(&self) -> Option<&Arc<dyn File>> {
self.file.as_ref()
}
+ /// The version of the buffer that was last saved or reloaded from disk.
pub fn saved_version(&self) -> &clock::Global {
&self.saved_version
}
+ /// The fingerprint of the buffer's text when the buffer was last saved or reloaded from disk.
pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
self.file_fingerprint
}
+ /// The mtime of the buffer's file when the buffer was last saved or reloaded from disk.
pub fn saved_mtime(&self) -> SystemTime {
self.saved_mtime
}
+ /// Assign a language to the buffer.
pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
self.syntax_map.lock().clear();
self.language = language;
@@ -626,17 +754,21 @@ impl Buffer {
cx.emit(Event::LanguageChanged);
}
+ /// Assign a language registry to the buffer. This allows the buffer to retrieve
+ /// other languages if parts of the buffer are written in different languages.
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
self.syntax_map
.lock()
.set_language_registry(language_registry);
}
+ /// Assign the buffer a new [Capability].
pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext<Self>) {
self.capability = capability;
cx.emit(Event::CapabilityChanged)
}
+ /// This method is called to signal that the buffer has been saved.
pub fn did_save(
&mut self,
version: clock::Global,
@@ -651,6 +783,7 @@ impl Buffer {
cx.notify();
}
+ /// Reloads the contents of the buffer from disk.
pub fn reload(
&mut self,
cx: &mut ModelContext<Self>,
@@ -699,6 +832,7 @@ impl Buffer {
rx
}
+ /// This method is called to signal that the buffer has been reloaded.
pub fn did_reload(
&mut self,
version: clock::Global,
@@ -725,6 +859,8 @@ impl Buffer {
cx.notify();
}
+ /// Updates the [File] backing this buffer. This should be called when
+ /// the file has changed or has been deleted.
pub fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) {
let mut file_changed = false;
@@ -762,16 +898,20 @@ impl Buffer {
}
}
+ /// Returns the current diff base, see [Buffer::set_diff_base].
pub fn diff_base(&self) -> Option<&str> {
self.diff_base.as_deref()
}
+ /// Sets the text that will be used to compute a Git diff
+ /// against the buffer text.
pub fn set_diff_base(&mut self, diff_base: Option<String>, cx: &mut ModelContext<Self>) {
self.diff_base = diff_base;
self.git_diff_recalc(cx);
cx.emit(Event::DiffBaseChanged);
}
+ /// Recomputes the Git diff status.
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
let snapshot = self.snapshot();
@@ -792,14 +932,12 @@ impl Buffer {
}))
}
- pub fn close(&mut self, cx: &mut ModelContext<Self>) {
- cx.emit(Event::Closed);
- }
-
+ /// Returns the primary [Language] assigned to this [Buffer].
pub fn language(&self) -> Option<&Arc<Language>> {
self.language.as_ref()
}
+ /// Returns the [Language] at the given location.
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<Arc<Language>> {
let offset = position.to_offset(self);
self.syntax_map
@@ -810,31 +948,39 @@ impl Buffer {
.or_else(|| self.language.clone())
}
+ /// The number of times the buffer was parsed.
pub fn parse_count(&self) -> usize {
self.parse_count
}
+ /// The number of times selections were updated.
pub fn selections_update_count(&self) -> usize {
self.selections_update_count
}
+ /// The number of times diagnostics were updated.
pub fn diagnostics_update_count(&self) -> usize {
self.diagnostics_update_count
}
+ /// The number of times the underlying file was updated.
pub fn file_update_count(&self) -> usize {
self.file_update_count
}
+ /// The number of times the git diff status was updated.
pub fn git_diff_update_count(&self) -> usize {
self.git_diff_update_count
}
+ /// Whether the buffer is being parsed in the background.
#[cfg(any(test, feature = "test-support"))]
pub fn is_parsing(&self) -> bool {
self.parsing_in_background
}
+ /// Indicates whether the buffer contains any regions that may be
+ /// written in a language that hasn't been loaded yet.
pub fn contains_unknown_injections(&self) -> bool {
self.syntax_map.lock().contains_unknown_injections()
}
@@ -941,6 +1087,7 @@ impl Buffer {
cx.notify();
}
+ /// Assign to the buffer a set of diagnostics created by a given language server.
pub fn update_diagnostics(
&mut self,
server_id: LanguageServerId,
@@ -1170,9 +1317,9 @@ impl Buffer {
self.edit(edits, None, cx);
}
- // Create a minimal edit that will cause the the given row to be indented
- // with the given size. After applying this edit, the length of the line
- // will always be at least `new_size.len`.
+ /// Create a minimal edit that will cause the the given row to be indented
+ /// with the given size. After applying this edit, the length of the line
+ /// will always be at least `new_size.len`.
pub fn edit_for_indent_size_adjustment(
row: u32,
current_size: IndentSize,
@@ -1207,6 +1354,8 @@ impl Buffer {
}
}
+ /// Spawns a background task that asynchronously computes a `Diff` between the buffer's text
+ /// and the given new text.
pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
let old_text = self.as_rope().clone();
let base_version = self.version();
@@ -1278,7 +1427,7 @@ impl Buffer {
})
}
- /// Spawn a background task that searches the buffer for any whitespace
+ /// Spawns a background task that searches the buffer for any whitespace
/// at the ends of a lines, and returns a `Diff` that removes that whitespace.
pub fn remove_trailing_whitespace(&self, cx: &AppContext) -> Task<Diff> {
let old_text = self.as_rope().clone();
@@ -1298,7 +1447,7 @@ impl Buffer {
})
}
- /// Ensure that the buffer ends with a single newline character, and
+ /// Ensures that the buffer ends with a single newline character, and
/// no other whitespace.
pub fn ensure_final_newline(&mut self, cx: &mut ModelContext<Self>) {
let len = self.len();
@@ -1319,7 +1468,7 @@ impl Buffer {
self.edit([(offset..len, "\n")], None, cx);
}
- /// Apply a diff to the buffer. If the buffer has changed since the given diff was
+ /// Applies a diff to the buffer. If the buffer has changed since the given diff was
/// calculated, then adjust the diff to account for those changes, and discard any
/// parts of the diff that conflict with those changes.
pub fn apply_diff(&mut self, diff: Diff, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
@@ -1358,11 +1507,14 @@ impl Buffer {
self.end_transaction(cx)
}
+ /// Checks if the buffer has unsaved changes.
pub fn is_dirty(&self) -> bool {
self.file_fingerprint != self.as_rope().fingerprint()
|| self.file.as_ref().map_or(false, |file| file.is_deleted())
}
+ /// Checks if the buffer and its file have both changed since the buffer
+ /// was last saved or reloaded.
pub fn has_conflict(&self) -> bool {
self.file_fingerprint != self.as_rope().fingerprint()
&& self
@@ -1371,14 +1523,23 @@ impl Buffer {
.map_or(false, |file| file.mtime() > self.saved_mtime)
}
+ /// Gets a [`Subscription`] that tracks all of the changes to the buffer's text.
pub fn subscribe(&mut self) -> Subscription {
self.text.subscribe()
}
+ /// Starts a transaction, if one is not already in-progress. When undoing or
+ /// redoing edits, all of the edits performed within a transaction are undone
+ /// or redone together.
pub fn start_transaction(&mut self) -> Option<TransactionId> {
self.start_transaction_at(Instant::now())
}
+ /// Starts a transaction, providing the current time. Subsequent transactions
+ /// that occur within a short period of time will be grouped together. This
+ /// is controlled by the buffer's undo grouping duration.
+ ///
+ /// See [`Buffer::set_group_interval`].
pub fn start_transaction_at(&mut self, now: Instant) -> Option<TransactionId> {
self.transaction_depth += 1;
if self.was_dirty_before_starting_transaction.is_none() {
@@ -1387,10 +1548,16 @@ impl Buffer {
self.text.start_transaction_at(now)
}
+ /// Terminates the current transaction, if this is the outermost transaction.
pub fn end_transaction(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
self.end_transaction_at(Instant::now(), cx)
}
+ /// Terminates the current transaction, providing the current time. Subsequent transactions
+ /// that occur within a short period of time will be grouped together. This
+ /// is controlled by the buffer's undo grouping duration.
+ ///
+ /// See [`Buffer::set_group_interval`].
pub fn end_transaction_at(
&mut self,
now: Instant,
@@ -1411,26 +1578,33 @@ impl Buffer {
}
}
+ /// Manually add a transaction to the buffer's undo history.
pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {
self.text.push_transaction(transaction, now);
}
+ /// Prevent the last transaction from being grouped with any subsequent transactions,
+ /// even if they occur with the buffer's undo grouping duration.
pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
self.text.finalize_last_transaction()
}
+ /// Manually group all changes since a given transaction.
pub fn group_until_transaction(&mut self, transaction_id: TransactionId) {
self.text.group_until_transaction(transaction_id);
}
+ /// Manually remove a transaction from the buffer's undo history
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
self.text.forget_transaction(transaction_id);
}
+ /// Manually merge two adjacent transactions in the buffer's undo history.
pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) {
self.text.merge_transactions(transaction, destination);
}
+ /// Waits for the buffer to receive operations with the given timestamps.
pub fn wait_for_edits(
&mut self,
edit_ids: impl IntoIterator<Item = clock::Lamport>,
@@ -1438,6 +1612,7 @@ impl Buffer {
self.text.wait_for_edits(edit_ids)
}
+ /// Waits for the buffer to receive the operations necessary for resolving the given anchors.
pub fn wait_for_anchors(
&mut self,
anchors: impl IntoIterator<Item = Anchor>,
@@ -1445,14 +1620,18 @@ impl Buffer {
self.text.wait_for_anchors(anchors)
}
+ /// Waits for the buffer to receive operations up to the given version.
pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future<Output = Result<()>> {
self.text.wait_for_version(version)
}
+ /// Forces all futures returned by [`Buffer::wait_for_version`], [`Buffer::wait_for_edits`], or
+ /// [`Buffer::wait_for_version`] to resolve with an error.
pub fn give_up_waiting(&mut self) {
self.text.give_up_waiting();
}
+ /// Stores a set of selections that should be broadcasted to all of the buffer's replicas.
pub fn set_active_selections(
&mut self,
selections: Arc<[Selection<Anchor>]>,
@@ -1481,6 +1660,8 @@ impl Buffer {
);
}
+ /// Clears the selections, so that other replicas of the buffer do not see any selections for
+ /// this replica.
pub fn remove_active_selections(&mut self, cx: &mut ModelContext<Self>) {
if self
.remote_selections
@@ -1491,6 +1672,7 @@ impl Buffer {
}
}
+ /// Replaces the buffer's entire text.
pub fn set_text<T>(&mut self, text: T, cx: &mut ModelContext<Self>) -> Option<clock::Lamport>
where
T: Into<Arc<str>>,
@@ -1499,6 +1681,15 @@ impl Buffer {
self.edit([(0..self.len(), text)], None, cx)
}
+ /// Applies the given edits to the buffer. Each edit is specified as a range of text to
+ /// delete, and a string of text to insert at that location.
+ ///
+ /// If an [`AutoindentMode`] is provided, then the buffer will enqueue an auto-indent
+ /// request for the edited ranges, which will be processed when the buffer finishes
+ /// parsing.
+ ///
+ /// Parsing takes place at the end of a transaction, and may compute synchronously
+ /// or asynchronously, depending on the changes.
pub fn edit<I, S, T>(
&mut self,
edits_iter: I,
@@ -1632,6 +1823,7 @@ impl Buffer {
cx.notify();
}
+ /// Applies the given remote operations to the buffer.
pub fn apply_ops<I: IntoIterator<Item = Operation>>(
&mut self,
ops: I,
@@ -1779,11 +1971,13 @@ impl Buffer {
cx.emit(Event::Operation(operation));
}
+ /// Removes the selections for a given peer.
pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext<Self>) {
self.remote_selections.remove(&replica_id);
cx.notify();
}
+ /// Undoes the most recent transaction.
pub fn undo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
let was_dirty = self.is_dirty();
let old_version = self.version.clone();
@@ -1797,6 +1991,7 @@ impl Buffer {
}
}
+ /// Manually undoes a specific transaction in the buffer's undo history.
pub fn undo_transaction(
&mut self,
transaction_id: TransactionId,
@@ -1813,6 +2008,7 @@ impl Buffer {
}
}
+ /// Manually undoes all changes after a given transaction in the buffer's undo history.
pub fn undo_to_transaction(
&mut self,
transaction_id: TransactionId,
@@ -1832,6 +2028,7 @@ impl Buffer {
undone
}
+ /// Manually redoes a specific transaction in the buffer's redo history.
pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
let was_dirty = self.is_dirty();
let old_version = self.version.clone();
@@ -1845,6 +2042,7 @@ impl Buffer {
}
}
+ /// Manually undoes all changes until a given transaction in the buffer's redo history.
pub fn redo_to_transaction(
&mut self,
transaction_id: TransactionId,
@@ -1864,6 +2062,7 @@ impl Buffer {
redone
}
+ /// Override current completion triggers with the user-provided completion triggers.
pub fn set_completion_triggers(&mut self, triggers: Vec<String>, cx: &mut ModelContext<Self>) {
self.completion_triggers = triggers.clone();
self.completion_triggers_timestamp = self.text.lamport_clock.tick();
@@ -1877,11 +2076,14 @@ impl Buffer {
cx.notify();
}
+ /// Returns a list of strings which trigger a completion menu for this language.
+ /// Usually this is driven by LSP server which returns a list of trigger characters for completions.
pub fn completion_triggers(&self) -> &[String] {
&self.completion_triggers
}
}
+#[doc(hidden)]
#[cfg(any(test, feature = "test-support"))]
impl Buffer {
pub fn edit_via_marked_text(
@@ -1954,10 +2156,12 @@ impl Deref for Buffer {
}
impl BufferSnapshot {
+ /// Returns [`IndentSize`] for a given line that respects user settings and /// language preferences.
pub fn indent_size_for_line(&self, row: u32) -> IndentSize {
indent_size_for_line(self, row)
}
-
+ /// Returns [`IndentSize`] for a given position that respects user settings
+ /// and language preferences.
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
let settings = language_settings(self.language_at(position), self.file(), cx);
if settings.hard_tabs {
@@ -1967,6 +2171,8 @@ impl BufferSnapshot {
}
}
+ /// Retrieve the suggested indent size for all of the given rows. The unit of indentation
+ /// is passed in as `single_indent_size`.
pub fn suggested_indents(
&self,
rows: impl Iterator<Item = u32>,
@@ -2213,6 +2419,10 @@ impl BufferSnapshot {
None
}
+ /// Iterates over chunks of text in the given range of the buffer. Text is chunked
+ /// in an arbitrary way due to being stored in a [`rope::Rope`]. The text is also
+ /// returned in chunks where each chunk has a single syntax highlighting style and
+ /// diagnostic status.
pub fn chunks<T: ToOffset>(&self, range: Range<T>, language_aware: bool) -> BufferChunks {
let range = range.start.to_offset(self)..range.end.to_offset(self);
@@ -2249,7 +2459,9 @@ impl BufferSnapshot {
BufferChunks::new(self.text.as_rope(), range, syntax, diagnostic_endpoints)
}
- pub fn for_each_line(&self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) {
+ /// Invokes the given callback for each line of text in the given range of the buffer.
+ /// Uses callback to avoid allocating a string for each line.
+ fn for_each_line(&self, range: Range<Point>, mut callback: impl FnMut(u32, &str)) {
let mut line = String::new();
let mut row = range.start.row;
for chunk in self
@@ -2268,11 +2480,12 @@ impl BufferSnapshot {
}
}
- pub fn syntax_layers(&self) -> impl Iterator<Item = SyntaxLayerInfo> + '_ {
+ /// Iterates over every [`SyntaxLayer`] in the buffer.
+ pub fn syntax_layers(&self) -> impl Iterator<Item = SyntaxLayer> + '_ {
self.syntax.layers_for_range(0..self.len(), &self.text)
}
- pub fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayerInfo> {
+ fn syntax_layer_at<D: ToOffset>(&self, position: D) -> Option<SyntaxLayer> {
let offset = position.to_offset(self);
self.syntax
.layers_for_range(offset..offset, &self.text)
@@ -2280,12 +2493,14 @@ impl BufferSnapshot {
.last()
}
+ /// Returns the [Language] at the given location.
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
self.syntax_layer_at(position)
.map(|info| info.language)
.or(self.language.as_ref())
}
+ /// Returns the settings for the language at the given location.
pub fn settings_at<'a, D: ToOffset>(
&self,
position: D,
@@ -2294,6 +2509,7 @@ impl BufferSnapshot {
language_settings(self.language_at(position), self.file.as_ref(), cx)
}
+ /// Returns the [LanguageScope] at the given location.
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
let offset = position.to_offset(self);
let mut scope = None;
@@ -2338,6 +2554,8 @@ impl BufferSnapshot {
})
}
+ /// Returns a tuple of the range and character kind of the word
+ /// surrounding the given position.
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
let mut start = start.to_offset(self);
let mut end = start;
@@ -2370,6 +2588,7 @@ impl BufferSnapshot {
(start..end, word_kind)
}
+ /// Returns the range for the closes syntax node enclosing the given range.
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
let range = range.start.to_offset(self)..range.end.to_offset(self);
let mut result: Option<Range<usize>> = None;
@@ -2438,11 +2657,19 @@ impl BufferSnapshot {
result
}
+ /// Returns the outline for the buffer.
+ ///
+ /// This method allows passing an optional [SyntaxTheme] to
+ /// syntax-highlight the returned symbols.
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
self.outline_items_containing(0..self.len(), true, theme)
.map(Outline::new)
}
+ /// Returns all the symbols that contain the given position.
+ ///
+ /// This method allows passing an optional [SyntaxTheme] to
+ /// syntax-highlight the returned symbols.
pub fn symbols_containing<T: ToOffset>(
&self,
position: T,
@@ -9,20 +9,36 @@ use std::{
use sum_tree::{self, Bias, SumTree};
use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
+/// A set of diagnostics associated with a given buffer, provided
+/// by a single language server.
+///
+/// The diagnostics are stored in a [SumTree], which allows this struct
+/// to be cheaply copied, and allows for efficient retrieval of the
+/// diagnostics that intersect a given range of the buffer.
#[derive(Clone, Debug, Default)]
pub struct DiagnosticSet {
diagnostics: SumTree<DiagnosticEntry<Anchor>>,
}
+/// A single diagnostic in a set. Generic over its range type, because
+/// the diagnostics are stored internally as [Anchor]s, but can be
+/// resolved to different coordinates types like [usize] byte offsets or
+/// [Point]s.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticEntry<T> {
+ /// The range of the buffer where the diagnostic applies.
pub range: Range<T>,
+ /// The information about the diagnostic.
pub diagnostic: Diagnostic,
}
+/// A group of related diagnostics, ordered by their start position
+/// in the buffer.
#[derive(Debug)]
pub struct DiagnosticGroup<T> {
+ /// The diagnostics.
pub entries: Vec<DiagnosticEntry<T>>,
+ /// The index into `entries` where the primary diagnostic is stored.
pub primary_ix: usize,
}
@@ -36,7 +52,8 @@ pub struct Summary {
}
impl<T> DiagnosticEntry<T> {
- // Used to provide diagnostic context to lsp codeAction request
+ /// Returns a raw LSP diagnostic ssed to provide diagnostic context to lsp
+ /// codeAction request
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
let code = self
.diagnostic
@@ -53,6 +70,8 @@ impl<T> DiagnosticEntry<T> {
}
impl DiagnosticSet {
+ /// Constructs a [DiagnosticSet] from a sequence of entries, ordered by
+ /// their position in the buffer.
pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
where
I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
@@ -62,6 +81,7 @@ impl DiagnosticSet {
}
}
+ /// Constructs a [DiagnosticSet] from a sequence of entries in an arbitrary order.
pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
where
I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
@@ -80,14 +100,18 @@ impl DiagnosticSet {
}
}
+ /// Returns the number of diagnostics in the set.
pub fn len(&self) -> usize {
self.diagnostics.summary().count
}
+ /// Returns an iterator over the diagnostic entries in the set.
pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
self.diagnostics.iter()
}
+ /// Returns an iterator over the diagnostic entries that intersect the
+ /// given range of the buffer.
pub fn range<'a, T, O>(
&'a self,
range: Range<T>,
@@ -134,6 +158,7 @@ impl DiagnosticSet {
})
}
+ /// Adds all of this set's diagnostic groups to the given output vector.
pub fn groups(
&self,
language_server_id: LanguageServerId,
@@ -169,10 +194,12 @@ impl DiagnosticSet {
.range
.start
.cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
- .then_with(|| id_a.cmp(&id_b))
+ .then_with(|| id_a.cmp(id_b))
});
}
+ /// Returns all of the diagnostics in a particular diagnostic group,
+ /// in order of their position in the buffer.
pub fn group<'a, O: FromAnchor>(
&'a self,
group_id: usize,
@@ -183,6 +210,7 @@ impl DiagnosticSet {
.map(|entry| entry.resolve(buffer))
}
}
+
impl sum_tree::Item for DiagnosticEntry<Anchor> {
type Summary = Summary;
@@ -198,6 +226,7 @@ impl sum_tree::Item for DiagnosticEntry<Anchor> {
}
impl DiagnosticEntry<Anchor> {
+ /// Converts the [DiagnosticEntry] to a different buffer coordinate type.
pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
DiagnosticEntry {
range: O::from_anchor(&self.range.start, buffer)
@@ -11,7 +11,7 @@ pub struct HighlightId(pub u32);
const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
impl HighlightMap {
- pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
+ pub(crate) fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
// For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
@@ -51,7 +51,7 @@ impl HighlightMap {
}
impl HighlightId {
- pub fn is_default(&self) -> bool {
+ pub(crate) fn is_default(&self) -> bool {
*self == DEFAULT_SYNTAX_HIGHLIGHT_ID
}
@@ -1,3 +1,11 @@
+//! The `language` crate provides a large chunk of Zed's language-related
+//! features (the other big contributors being project and lsp crates that revolve around LSP features).
+//! Namely, this crate:
+//! - Provides [`Language`], [`Grammar`] and [`LanguageRegistry`] types that
+//! use Tree-sitter to provide syntax highlighting to the editor; note though that `language` doesn't perform the highlighting by itself. It only maps ranges in a buffer to colors. Treesitter is also used for buffer outlines (lists of symbols in a buffer)
+//! - Exposes [`LanguageConfig`] that describes how constructs (like brackets or line comments) should be handled by the editor for a source file of a particular language.
+//!
+//! Notably we do *not* assign a single language to a single file; in real world a single file can consist of multiple programming languages - HTML is a good example of that - and `language` crate tends to reflect that status quo in it's API.
mod buffer;
mod diagnostic_set;
mod highlight_map;
@@ -54,10 +62,13 @@ pub use buffer::*;
pub use diagnostic_set::DiagnosticEntry;
pub use lsp::LanguageServerId;
pub use outline::{Outline, OutlineItem};
-pub use syntax_map::{OwnedSyntaxLayerInfo, SyntaxLayerInfo};
+pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
pub use text::LineEnding;
pub use tree_sitter::{Parser, Tree};
+/// Initializes the `language` crate.
+///
+/// This should be called before making use of items from the create.
pub fn init(cx: &mut AppContext) {
language_settings::init(cx);
}
@@ -90,7 +101,9 @@ thread_local! {
}
lazy_static! {
- pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
+ pub(crate) static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
+ /// A shared grammar for plain text, exposed for reuse by downstream crates.
+ #[doc(hidden)]
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig {
name: "Plain Text".into(),
@@ -100,10 +113,14 @@ lazy_static! {
));
}
+/// Types that represent a position in a buffer, and can be converted into
+/// an LSP position, to send to a language server.
pub trait ToLspPosition {
+ /// Converts the value into an LSP position.
fn to_lsp_position(self) -> lsp::Position;
}
+/// A name of a language server.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct LanguageServerName(pub Arc<str>);
@@ -239,6 +256,8 @@ impl CachedLspAdapter {
}
}
+/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
+// e.g. to display a notification or fetch data from the web.
pub trait LspAdapterDelegate: Send + Sync {
fn show_notification(&self, message: &str, cx: &mut AppContext);
fn http_client(&self) -> Arc<dyn HttpClient>;
@@ -284,6 +303,10 @@ pub trait LspAdapter: 'static + Send + Sync {
delegate: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary>;
+ /// Returns true if a language server can be reinstalled.
+ /// If language server initialization fails, a reinstallation will be attempted unless the value returned from this method is false.
+ /// Implementations that rely on software already installed on user's system
+ /// should have [`can_be_reinstalled`] return false.
fn can_be_reinstalled(&self) -> bool {
true
}
@@ -295,6 +318,9 @@ pub trait LspAdapter: 'static + Send + Sync {
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+ /// A callback called for each [`lsp_types::CompletionItem`] obtained from LSP server.
+ /// Some LspAdapter implementations might want to modify the obtained item to
+ /// change how it's displayed.
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
async fn label_for_completion(
@@ -314,6 +340,7 @@ pub trait LspAdapter: 'static + Send + Sync {
None
}
+ /// Returns initialization options that are going to be sent to a LSP server as a part of [`lsp_types::InitializeParams`]
async fn initialization_options(&self) -> Option<Value> {
None
}
@@ -322,6 +349,7 @@ pub trait LspAdapter: 'static + Send + Sync {
futures::future::ready(serde_json::json!({})).boxed()
}
+ /// Returns a list of code actions supported by a given LspAdapter
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
Some(vec![
CodeActionKind::EMPTY,
@@ -351,43 +379,69 @@ pub trait LspAdapter: 'static + Send + Sync {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CodeLabel {
+ /// The text to display.
pub text: String,
+ /// Syntax highlighting runs.
pub runs: Vec<(Range<usize>, HighlightId)>,
+ /// The portion of the text that should be used in fuzzy filtering.
pub filter_range: Range<usize>,
}
#[derive(Clone, Deserialize)]
pub struct LanguageConfig {
+ /// Human-readable name of the language.
pub name: Arc<str>,
+ // The name of the grammar in a WASM bundle (experimental).
pub grammar_name: Option<Arc<str>>,
+ /// 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`.
pub path_suffixes: Vec<String>,
+ /// List of bracket types in a language.
pub brackets: BracketPairConfig,
+ /// A regex pattern that determines whether the language should be assigned to a file or not.
#[serde(default, deserialize_with = "deserialize_regex")]
pub first_line_pattern: Option<Regex>,
+ /// If set to true, auto indentation uses last non empty line to determine
+ /// the indentation level for a new line.
#[serde(default = "auto_indent_using_last_non_empty_line_default")]
pub auto_indent_using_last_non_empty_line: bool,
+ /// A regex that is used to determine whether the indentation level should be
+ /// increased in the following line.
#[serde(default, deserialize_with = "deserialize_regex")]
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")]
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
+ /// bracket is inserted.
#[serde(default)]
pub autoclose_before: String,
- #[serde(default)]
- pub line_comment: Option<Arc<str>>,
+ /// A placeholder used internally by Semantic Index.
#[serde(default)]
pub collapsed_placeholder: String,
+ /// A line comment string that is inserted in e.g. `toggle comments` action.
+ #[serde(default)]
+ pub line_comment: Option<Arc<str>>,
+ /// Starting and closing characters of a block comment.
#[serde(default)]
pub block_comment: Option<(Arc<str>, Arc<str>)>,
+ /// A list of language servers that are allowed to run on subranges of a given language.
#[serde(default)]
pub scope_opt_in_language_servers: Vec<String>,
#[serde(default)]
pub overrides: HashMap<String, LanguageConfigOverride>,
+ /// A list of characters that Zed should treat as word characters for the
+ /// purpose of features that operate on word boundaries, like 'move to next word end'
+ /// or a whole-word search in buffer search.
#[serde(default)]
pub word_characters: HashSet<char>,
+ /// The name of a Prettier parser that should be used for this language.
#[serde(default)]
pub prettier_parser_name: Option<String>,
}
+/// Tree-sitter language queries for a given language.
#[derive(Debug, Default)]
pub struct LanguageQueries {
pub highlights: Option<Cow<'static, str>>,
@@ -399,6 +453,9 @@ pub struct LanguageQueries {
pub overrides: Option<Cow<'static, str>>,
}
+/// Represents a language for the given range. Some languages (e.g. HTML)
+/// interleave several languages together, thus a single buffer might actually contain
+/// several nested scopes.
#[derive(Clone, Debug)]
pub struct LanguageScope {
language: Arc<Language>,
@@ -458,9 +515,9 @@ impl Default for LanguageConfig {
block_comment: Default::default(),
scope_opt_in_language_servers: Default::default(),
overrides: Default::default(),
- collapsed_placeholder: Default::default(),
word_characters: Default::default(),
prettier_parser_name: None,
+ collapsed_placeholder: Default::default(),
}
}
}
@@ -478,6 +535,7 @@ fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D
}
}
+#[doc(hidden)]
#[cfg(any(test, feature = "test-support"))]
pub struct FakeLspAdapter {
pub name: &'static str,
@@ -489,9 +547,16 @@ pub struct FakeLspAdapter {
pub prettier_plugins: Vec<&'static str>,
}
+/// Configuration of handling bracket pairs for a given language.
+///
+/// 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)]
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]`
pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
}
@@ -523,11 +588,18 @@ 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 inbetween `start` and `end` characters.
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct BracketPair {
+ /// Starting substring for a bracket.
pub start: String,
+ /// Ending substring for a bracket.
pub end: String,
+ /// True if `end` should be automatically inserted right after `start` characters.
pub close: bool,
+ /// True if an extra newline should be inserted while the cursor is in the middle
+ /// of that bracket pair.
pub newline: bool,
}
@@ -780,7 +852,7 @@ impl LanguageRegistry {
let mut state = self.state.write();
state.theme = Some(theme.clone());
for language in &state.languages {
- language.set_theme(&theme.syntax());
+ language.set_theme(theme.syntax());
}
}
@@ -1094,7 +1166,7 @@ impl LanguageRegistryState {
fn add(&mut self, language: Arc<Language>) {
if let Some(theme) = self.theme.as_ref() {
- language.set_theme(&theme.syntax());
+ language.set_theme(theme.syntax());
}
self.languages.push(language);
self.version += 1;
@@ -1641,6 +1713,8 @@ impl LanguageScope {
self.language.config.collapsed_placeholder.as_ref()
}
+ /// Returns line prefix that is inserted in e.g. line continuations or
+ /// in `toggle comments` action.
pub fn line_comment_prefix(&self) -> Option<&Arc<str>> {
Override::as_option(
self.config_override().map(|o| &o.line_comment),
@@ -1656,6 +1730,11 @@ impl LanguageScope {
.map(|e| (&e.0, &e.1))
}
+ /// Returns a list of language-specific word characters.
+ ///
+ /// By default, Zed treats alphanumeric characters (and '_') as word characters for
+ /// the purpose of actions like 'move to next word end` or whole-word search.
+ /// It additionally accounts for language's additional word characters.
pub fn word_characters(&self) -> Option<&HashSet<char>> {
Override::as_option(
self.config_override().map(|o| &o.word_characters),
@@ -1663,6 +1742,8 @@ impl LanguageScope {
)
}
+ /// Returns a list of bracket pairs for a given language with an additional
+ /// piece of information about whether the particular bracket pair is currently active for a given language.
pub fn brackets(&self) -> impl Iterator<Item = (&BracketPair, bool)> {
let mut disabled_ids = self
.config_override()
@@ -1,3 +1,5 @@
+//! Provides `language`-related settings.
+
use crate::{File, Language};
use anyhow::Result;
use collections::{HashMap, HashSet};
@@ -11,10 +13,12 @@ use serde::{Deserialize, Serialize};
use settings::Settings;
use std::{num::NonZeroU32, path::Path, sync::Arc};
+/// Initializes the language settings.
pub fn init(cx: &mut AppContext) {
AllLanguageSettings::register(cx);
}
+/// Returns the settings for the specified language from the provided file.
pub fn language_settings<'a>(
language: Option<&Arc<Language>>,
file: Option<&Arc<dyn File>>,
@@ -24,6 +28,7 @@ pub fn language_settings<'a>(
all_language_settings(file, cx).language(language_name.as_deref())
}
+/// Returns the settings for all languages from the provided file.
pub fn all_language_settings<'a>(
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
@@ -32,51 +37,91 @@ pub fn all_language_settings<'a>(
AllLanguageSettings::get(location, cx)
}
+/// The settings for all languages.
#[derive(Debug, Clone)]
pub struct AllLanguageSettings {
+ /// The settings for GitHub Copilot.
pub copilot: CopilotSettings,
defaults: LanguageSettings,
languages: HashMap<Arc<str>, LanguageSettings>,
}
+/// The settings for a particular language.
#[derive(Debug, Clone, Deserialize)]
pub struct LanguageSettings {
+ /// How many columns a tab should occupy.
pub tab_size: NonZeroU32,
+ /// Whether to indent lines using tab characters, as opposed to multiple
+ /// spaces.
pub hard_tabs: bool,
+ /// How to soft-wrap long lines of text.
pub soft_wrap: SoftWrap,
+ /// The column at which to soft-wrap lines, for buffers where soft-wrap
+ /// is enabled.
pub preferred_line_length: u32,
+ /// Whether to show wrap guides in the editor. Setting this to true will
+ /// show a guide at the 'preferred_line_length' value if softwrap is set to
+ /// 'preferred_line_length', and will show any additional guides as specified
+ /// by the 'wrap_guides' setting.
pub show_wrap_guides: bool,
+ /// Character counts at which to show wrap guides in the editor.
pub wrap_guides: Vec<usize>,
+ /// Whether or not to perform a buffer format before saving.
pub format_on_save: FormatOnSave,
+ /// Whether or not to remove any trailing whitespace from lines of a buffer
+ /// before saving it.
pub remove_trailing_whitespace_on_save: bool,
+ /// Whether or not to ensure there's a single newline at the end of a buffer
+ /// when saving it.
pub ensure_final_newline_on_save: bool,
+ /// How to perform a buffer format.
pub formatter: Formatter,
+ /// Zed's Prettier integration settings.
+ /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
+ /// the project has no other Prettier installed.
pub prettier: HashMap<String, serde_json::Value>,
+ /// Whether to use language servers to provide code intelligence.
pub enable_language_server: bool,
+ /// Controls whether Copilot provides suggestion immediately (true)
+ /// or waits for a `copilot::Toggle` (false).
pub show_copilot_suggestions: bool,
+ /// Whether to show tabs and spaces in the editor.
pub show_whitespaces: ShowWhitespaceSetting,
+ /// Whether to start a new line with a comment when a previous line is a comment as well.
pub extend_comment_on_newline: bool,
+ /// Inlay hint related settings.
pub inlay_hints: InlayHintSettings,
+ /// Whether to automatically close brackets.
+ pub use_autoclose: bool,
}
+/// The settings for [GitHub Copilot](https://github.com/features/copilot).
#[derive(Clone, Debug, Default)]
pub struct CopilotSettings {
+ /// Whether Copilot is enabled.
pub feature_enabled: bool,
+ /// A list of globs representing files that Copilot should be disabled for.
pub disabled_globs: Vec<GlobMatcher>,
}
+/// The settings for all languages.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
+ /// The settings for enabling/disabling features.
#[serde(default)]
pub features: Option<FeaturesContent>,
+ /// The settings for GitHub Copilot.
#[serde(default)]
pub copilot: Option<CopilotSettingsContent>,
+ /// The default language settings.
#[serde(flatten)]
pub defaults: LanguageSettingsContent,
+ /// The settings for individual languages.
#[serde(default, alias = "language_overrides")]
pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
}
+/// The settings for a particular language.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct LanguageSettingsContent {
/// How many columns a tab should occupy.
@@ -138,7 +183,7 @@ pub struct LanguageSettingsContent {
pub formatter: Option<Formatter>,
/// Zed's Prettier integration settings.
/// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
- /// project has no other Prettier installed.
+ /// the project has no other Prettier installed.
///
/// Default: {}
#[serde(default)]
@@ -148,7 +193,7 @@ pub struct LanguageSettingsContent {
/// Default: true
#[serde(default)]
pub enable_language_server: Option<bool>,
- /// Controls whether copilot provides suggestion immediately (true)
+ /// Controls whether Copilot provides suggestion immediately (true)
/// or waits for a `copilot::Toggle` (false).
///
/// Default: true
@@ -165,20 +210,30 @@ pub struct LanguageSettingsContent {
/// Inlay hint related settings.
#[serde(default)]
pub inlay_hints: Option<InlayHintSettings>,
+ /// Whether to automatically type closing characters for you. For example,
+ /// when you type (, Zed will automatically add a closing ) at the correct position.
+ ///
+ /// Default: true
+ pub use_autoclose: Option<bool>,
}
+/// The contents of the GitHub Copilot settings.
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct CopilotSettingsContent {
+ /// A list of globs representing files that Copilot should be disabled for.
#[serde(default)]
pub disabled_globs: Option<Vec<String>>,
}
+/// The settings for enabling/disabling features.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FeaturesContent {
+ /// Whether the GitHub Copilot feature is enabled.
pub copilot: Option<bool>,
}
+/// Controls the soft-wrapping behavior in the editor.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SoftWrap {
@@ -190,29 +245,38 @@ pub enum SoftWrap {
PreferredLineLength,
}
+/// Controls the behavior of formatting files when they are saved.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum FormatOnSave {
+ /// Files should be formatted on save.
On,
+ /// Files should not be formatted on save.
Off,
+ /// Files should be formatted using the current language server.
LanguageServer,
+ /// The external program to use to format the files on save.
External {
+ /// The external program to run.
command: Arc<str>,
+ /// The arguments to pass to the program.
arguments: Arc<[String]>,
},
}
+/// Controls how whitespace should be displayedin the editor.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ShowWhitespaceSetting {
- /// Draw tabs and spaces only for the selected text.
+ /// Draw whitespace only for the selected text.
Selection,
- /// Do not draw any tabs or spaces
+ /// Do not draw any tabs or spaces.
None,
- /// Draw all invisible symbols
+ /// Draw all invisible symbols.
All,
}
+/// Controls which formatter should be used when formatting code.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Formatter {
@@ -226,11 +290,14 @@ pub enum Formatter {
Prettier,
/// Format code using an external command.
External {
+ /// The external program to run.
command: Arc<str>,
+ /// The arguments to pass to the program.
arguments: Arc<[String]>,
},
}
+/// The settings for inlay hints.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct InlayHintSettings {
/// Global switch to toggle hints on and off.
@@ -238,10 +305,19 @@ pub struct InlayHintSettings {
/// Default: false
#[serde(default)]
pub enabled: bool,
+ /// Whether type hints should be shown.
+ ///
+ /// Default: true
#[serde(default = "default_true")]
pub show_type_hints: bool,
+ /// Whether parameter hints should be shown.
+ ///
+ /// Default: true
#[serde(default = "default_true")]
pub show_parameter_hints: bool,
+ /// Whether other hints should be shown.
+ ///
+ /// Default: true
#[serde(default = "default_true")]
pub show_other_hints: bool,
}
@@ -251,6 +327,7 @@ fn default_true() -> bool {
}
impl InlayHintSettings {
+ /// Returns the kinds of inlay hints that are enabled based on the settings.
pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
let mut kinds = HashSet::default();
if self.show_type_hints {
@@ -267,6 +344,7 @@ impl InlayHintSettings {
}
impl AllLanguageSettings {
+ /// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
@@ -276,6 +354,7 @@ impl AllLanguageSettings {
&self.defaults
}
+ /// Returns whether GitHub Copilot is enabled for the given path.
pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
!self
.copilot
@@ -284,6 +363,7 @@ impl AllLanguageSettings {
.any(|glob| glob.is_match(path))
}
+ /// Returns whether GitHub Copilot is enabled for the given language and path.
pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
if !self.copilot.feature_enabled {
return false;
@@ -300,13 +380,20 @@ impl AllLanguageSettings {
}
}
+/// The kind of an inlay hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlayHintKind {
+ /// An inlay hint for a type.
Type,
+ /// An inlay hint for a parameter.
Parameter,
}
impl InlayHintKind {
+ /// Returns the [`InlayHintKind`] from the given name.
+ ///
+ /// Returns `None` if `name` does not match any of the expected
+ /// string representations.
pub fn from_name(name: &str) -> Option<Self> {
match name {
"type" => Some(InlayHintKind::Type),
@@ -315,6 +402,7 @@ impl InlayHintKind {
}
}
+ /// Returns the name of this [`InlayHintKind`].
pub fn name(&self) -> &'static str {
match self {
InlayHintKind::Type => "type",
@@ -340,7 +428,7 @@ impl settings::Settings for AllLanguageSettings {
let mut languages = HashMap::default();
for (language_name, settings) in &default_value.languages {
let mut language_settings = defaults.clone();
- merge_settings(&mut language_settings, &settings);
+ merge_settings(&mut language_settings, settings);
languages.insert(language_name.clone(), language_settings);
}
@@ -380,7 +468,7 @@ impl settings::Settings for AllLanguageSettings {
languages
.entry(language_name.clone())
.or_insert_with(|| defaults.clone()),
- &user_language_settings,
+ user_language_settings,
);
}
}
@@ -459,6 +547,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
merge(&mut settings.tab_size, src.tab_size);
merge(&mut settings.hard_tabs, src.hard_tabs);
merge(&mut settings.soft_wrap, src.soft_wrap);
+ merge(&mut settings.use_autoclose, src.use_autoclose);
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
@@ -1,3 +1,5 @@
+//! Provides Markdown-related constructs.
+
use std::sync::Arc;
use std::{ops::Range, path::PathBuf};
@@ -5,21 +7,30 @@ use crate::{HighlightId, Language, LanguageRegistry};
use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
+/// Parsed Markdown content.
#[derive(Debug, Clone)]
pub struct ParsedMarkdown {
+ /// The Markdown text.
pub text: String,
+ /// The list of highlights contained in the Markdown document.
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
+ /// The regions of the various ranges in the Markdown document.
pub region_ranges: Vec<Range<usize>>,
+ /// The regions of the Markdown document.
pub regions: Vec<ParsedRegion>,
}
+/// A run of highlighted Markdown text.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MarkdownHighlight {
+ /// A styled Markdown highlight.
Style(MarkdownHighlightStyle),
+ /// A highlighted code block.
Code(HighlightId),
}
impl MarkdownHighlight {
+ /// Converts this [`MarkdownHighlight`] to a [`HighlightStyle`].
pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
match self {
MarkdownHighlight::Style(style) => {
@@ -48,23 +59,39 @@ impl MarkdownHighlight {
}
}
+/// The style for a Markdown highlight.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MarkdownHighlightStyle {
+ /// Whether the text should be italicized.
pub italic: bool,
+ /// Whether the text should be underlined.
pub underline: bool,
+ /// The weight of the text.
pub weight: FontWeight,
}
+/// A parsed region in a Markdown document.
#[derive(Debug, Clone)]
pub struct ParsedRegion {
+ /// Whether the region is a code block.
pub code: bool,
+ /// The link contained in this region, if it has one.
pub link: Option<Link>,
}
+/// A Markdown link.
#[derive(Debug, Clone)]
pub enum Link {
- Web { url: String },
- Path { path: PathBuf },
+ /// A link to a webpage.
+ Web {
+ /// The URL of the webpage.
+ url: String,
+ },
+ /// A link to a path on the filesystem.
+ Path {
+ /// The path to the item.
+ path: PathBuf,
+ },
}
impl Link {
@@ -82,6 +109,7 @@ impl Link {
}
}
+/// Parses a string of Markdown.
pub async fn parse_markdown(
markdown: &str,
language_registry: &Arc<LanguageRegistry>,
@@ -111,6 +139,7 @@ pub async fn parse_markdown(
}
}
+/// Parses a Markdown block.
pub async fn parse_markdown_block(
markdown: &str,
language_registry: &Arc<LanguageRegistry>,
@@ -126,7 +155,7 @@ pub async fn parse_markdown_block(
let mut current_language = None;
let mut list_stack = Vec::new();
- for event in Parser::new_ext(&markdown, Options::all()) {
+ for event in Parser::new_ext(markdown, Options::all()) {
let prev_len = text.len();
match event {
Event::Text(t) => {
@@ -261,6 +290,7 @@ pub async fn parse_markdown_block(
}
}
+/// Appends a highlighted run of text to the provided `text` buffer.
pub fn highlight_code(
text: &mut String,
highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
@@ -275,6 +305,7 @@ pub fn highlight_code(
}
}
+/// Appends a new paragraph to the provided `text` buffer.
pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)>) {
let mut is_subsequent_paragraph_of_list = false;
if let Some((_, has_content)) = list_stack.last_mut() {
@@ -2,6 +2,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{BackgroundExecutor, HighlightStyle};
use std::ops::Range;
+/// An outline of all the symbols contained in a buffer.
#[derive(Debug)]
pub struct Outline<T> {
pub items: Vec<OutlineItem<T>>,
@@ -1,3 +1,5 @@
+//! Handles conversions of `language` items to and from the [`rpc`] protocol.
+
use crate::{
diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
Language,
@@ -11,15 +13,18 @@ use text::*;
pub use proto::{BufferState, Operation};
+/// Serializes a [`RopeFingerprint`] to be sent over RPC.
pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String {
fingerprint.to_hex()
}
+/// Deserializes a [`RopeFingerprint`] from the RPC representation.
pub fn deserialize_fingerprint(fingerprint: &str) -> Result<RopeFingerprint> {
RopeFingerprint::from_hex(fingerprint)
.map_err(|error| anyhow!("invalid fingerprint: {}", error))
}
+/// Deserializes a `[text::LineEnding]` from the RPC representation.
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
match message {
proto::LineEnding::Unix => text::LineEnding::Unix,
@@ -27,6 +32,7 @@ pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
}
}
+/// Serializes a [`text::LineEnding`] to be sent over RPC.
pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
match message {
text::LineEnding::Unix => proto::LineEnding::Unix,
@@ -34,6 +40,7 @@ pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
}
}
+/// Serializes a [`crate::Operation`] to be sent over RPC.
pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
proto::Operation {
variant: Some(match operation {
@@ -96,6 +103,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
}
}
+/// Serializes an [`operation::EditOperation`] to be sent over RPC.
pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
proto::operation::Edit {
replica_id: operation.timestamp.replica_id as u32,
@@ -110,6 +118,7 @@ pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::
}
}
+/// Serializes an entry in the undo map to be sent over RPC.
pub fn serialize_undo_map_entry(
(edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]),
) -> proto::UndoMapEntry {
@@ -127,6 +136,7 @@ pub fn serialize_undo_map_entry(
}
}
+/// Splits the given list of operations into chunks.
pub fn split_operations(
mut operations: Vec<proto::Operation>,
) -> impl Iterator<Item = Vec<proto::Operation>> {
@@ -152,10 +162,12 @@ pub fn split_operations(
})
}
+/// Serializes selections to be sent over RPC.
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
selections.iter().map(serialize_selection).collect()
}
+/// Serializes a [`Selection`] to be sent over RPC.
pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
proto::Selection {
id: selection.id as u64,
@@ -171,6 +183,7 @@ pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
}
}
+/// Serializes a [`CursorShape`] to be sent over RPC.
pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape {
match cursor_shape {
CursorShape::Bar => proto::CursorShape::CursorBar,
@@ -180,6 +193,7 @@ pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape
}
}
+/// Deserializes a [`CursorShape`] from the RPC representation.
pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape {
match cursor_shape {
proto::CursorShape::CursorBar => CursorShape::Bar,
@@ -189,6 +203,7 @@ pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape
}
}
+/// Serializes a list of diagnostics to be sent over RPC.
pub fn serialize_diagnostics<'a>(
diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>,
) -> Vec<proto::Diagnostic> {
@@ -208,7 +223,7 @@ pub fn serialize_diagnostics<'a>(
} as i32,
group_id: entry.diagnostic.group_id as u64,
is_primary: entry.diagnostic.is_primary,
- is_valid: entry.diagnostic.is_valid,
+ is_valid: true,
code: entry.diagnostic.code.clone(),
is_disk_based: entry.diagnostic.is_disk_based,
is_unnecessary: entry.diagnostic.is_unnecessary,
@@ -216,6 +231,7 @@ pub fn serialize_diagnostics<'a>(
.collect()
}
+/// Serializes an [`Anchor`] to be sent over RPC.
pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
proto::Anchor {
replica_id: anchor.timestamp.replica_id as u32,
@@ -230,6 +246,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
}
// This behavior is currently copied in the collab database, for snapshotting channel notes
+/// Deserializes an [`crate::Operation`] from the RPC representation.
pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {
Ok(
match message
@@ -312,6 +329,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
)
}
+/// Deserializes an [`EditOperation`] from the RPC representation.
pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
EditOperation {
timestamp: clock::Lamport {
@@ -324,6 +342,7 @@ pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation
}
}
+/// Deserializes an entry in the undo map from the RPC representation.
pub fn deserialize_undo_map_entry(
entry: proto::UndoMapEntry,
) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) {
@@ -348,6 +367,7 @@ pub fn deserialize_undo_map_entry(
)
}
+/// Deserializes selections from the RPC representation.
pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> {
Arc::from(
selections
@@ -357,6 +377,7 @@ pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selecti
)
}
+/// Deserializes a [`Selection`] from the RPC representation.
pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
Some(Selection {
id: selection.id as usize,
@@ -367,6 +388,7 @@ pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<An
})
}
+/// Deserializes a list of diagnostics from the RPC representation.
pub fn deserialize_diagnostics(
diagnostics: Vec<proto::Diagnostic>,
) -> Arc<[DiagnosticEntry<Anchor>]> {
@@ -387,7 +409,6 @@ pub fn deserialize_diagnostics(
message: diagnostic.message,
group_id: diagnostic.group_id as usize,
code: diagnostic.code,
- is_valid: diagnostic.is_valid,
is_primary: diagnostic.is_primary,
is_disk_based: diagnostic.is_disk_based,
is_unnecessary: diagnostic.is_unnecessary,
@@ -397,6 +418,7 @@ pub fn deserialize_diagnostics(
.collect()
}
+/// Deserializes an [`Anchor`] from the RPC representation.
pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
Some(Anchor {
timestamp: clock::Lamport {
@@ -412,6 +434,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
})
}
+/// Returns a `[clock::Lamport`] timestamp for the given [`proto::Operation`].
pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<clock::Lamport> {
let replica_id;
let value;
@@ -444,6 +467,7 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<c
})
}
+/// Serializes a [`Completion`] to be sent over RPC.
pub fn serialize_completion(completion: &Completion) -> proto::Completion {
proto::Completion {
old_start: Some(serialize_anchor(&completion.old_range.start)),
@@ -454,6 +478,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completion {
}
}
+/// Deserializes a [`Completion`] from the RPC representation.
pub async fn deserialize_completion(
completion: proto::Completion,
language: Option<Arc<Language>>,
@@ -488,6 +513,7 @@ pub async fn deserialize_completion(
})
}
+/// Serializes a [`CodeAction`] to be sent over RPC.
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
proto::CodeAction {
server_id: action.server_id.0 as u64,
@@ -497,6 +523,7 @@ pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
}
}
+/// Deserializes a [`CodeAction`] from the RPC representation.
pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
let start = action
.start
@@ -514,6 +541,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction>
})
}
+/// Serializes a [`Transaction`] to be sent over RPC.
pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
proto::Transaction {
id: Some(serialize_timestamp(transaction.id)),
@@ -527,6 +555,7 @@ pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
}
}
+/// Deserializes a [`Transaction`] from the RPC representation.
pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
Ok(Transaction {
id: deserialize_timestamp(
@@ -543,6 +572,7 @@ pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transa
})
}
+/// Serializes a [`clock::Lamport`] timestamp to be sent over RPC.
pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp {
proto::LamportTimestamp {
replica_id: timestamp.replica_id as u32,
@@ -550,6 +580,7 @@ pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp
}
}
+/// Deserializes a [`clock::Lamport`] timestamp from the RPC representation.
pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport {
clock::Lamport {
replica_id: timestamp.replica_id as ReplicaId,
@@ -557,6 +588,7 @@ pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lampo
}
}
+/// Serializes a range of [`FullOffset`]s to be sent over RPC.
pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
proto::Range {
start: range.start.0 as u64,
@@ -564,10 +596,12 @@ pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
}
}
+/// Deserializes a range of [`FullOffset`]s from the RPC representation.
pub fn deserialize_range(range: proto::Range) -> Range<FullOffset> {
FullOffset(range.start as usize)..FullOffset(range.end as usize)
}
+/// Deserializes a clock version from the RPC representation.
pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global {
let mut version = clock::Global::new();
for entry in message {
@@ -579,6 +613,7 @@ pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global
version
}
+/// Serializes a clock version to be sent over RPC.
pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
version
.iter()
@@ -29,7 +29,7 @@ pub struct SyntaxMap {
#[derive(Clone, Default)]
pub struct SyntaxSnapshot {
- layers: SumTree<SyntaxLayer>,
+ layers: SumTree<SyntaxLayerEntry>,
parsed_version: clock::Global,
interpolated_version: clock::Global,
language_registry_version: usize,
@@ -84,7 +84,7 @@ struct SyntaxMapMatchesLayer<'a> {
}
#[derive(Clone)]
-struct SyntaxLayer {
+struct SyntaxLayerEntry {
depth: usize,
range: Range<Anchor>,
content: SyntaxLayerContent,
@@ -117,17 +117,22 @@ impl SyntaxLayerContent {
}
}
+/// A layer of syntax highlighting, corresponding to a single syntax
+/// tree in a particular language.
#[derive(Debug)]
-pub struct SyntaxLayerInfo<'a> {
- pub depth: usize,
+pub struct SyntaxLayer<'a> {
+ /// The language for this layer.
pub language: &'a Arc<Language>,
+ depth: usize,
tree: &'a Tree,
offset: (usize, tree_sitter::Point),
}
+/// A layer of syntax highlighting. Like [SyntaxLayer], but holding
+/// owned data instead of references.
#[derive(Clone)]
-pub struct OwnedSyntaxLayerInfo {
- pub depth: usize,
+pub struct OwnedSyntaxLayer {
+ /// The language for this layer.
pub language: Arc<Language>,
tree: tree_sitter::Tree,
offset: (usize, tree_sitter::Point),
@@ -278,7 +283,7 @@ impl SyntaxSnapshot {
depth,
position: edit_range.start,
};
- if target.cmp(&cursor.start(), text).is_gt() {
+ if target.cmp(cursor.start(), text).is_gt() {
let slice = cursor.slice(&target, Bias::Left, text);
layers.append(slice, text);
}
@@ -363,7 +368,7 @@ impl SyntaxSnapshot {
cursor.next(text);
}
- layers.append(cursor.suffix(&text), &text);
+ layers.append(cursor.suffix(text), text);
drop(cursor);
self.layers = layers;
}
@@ -428,7 +433,7 @@ impl SyntaxSnapshot {
let max_depth = self.layers.summary().max_depth;
let mut cursor = self.layers.cursor::<SyntaxLayerSummary>();
- cursor.next(&text);
+ cursor.next(text);
let mut layers = SumTree::new();
let mut changed_regions = ChangeRegionSet::default();
@@ -466,17 +471,17 @@ impl SyntaxSnapshot {
};
let mut done = cursor.item().is_none();
- while !done && position.cmp(&cursor.end(text), &text).is_gt() {
+ while !done && position.cmp(&cursor.end(text), text).is_gt() {
done = true;
let bounded_position = SyntaxLayerPositionBeforeChange {
position: position.clone(),
change: changed_regions.start_position(),
};
- if bounded_position.cmp(&cursor.start(), &text).is_gt() {
+ if bounded_position.cmp(cursor.start(), text).is_gt() {
let slice = cursor.slice(&bounded_position, Bias::Left, text);
if !slice.is_empty() {
- layers.append(slice, &text);
+ layers.append(slice, text);
if changed_regions.prune(cursor.end(text), text) {
done = false;
}
@@ -486,7 +491,7 @@ impl SyntaxSnapshot {
while position.cmp(&cursor.end(text), text).is_gt() {
let Some(layer) = cursor.item() else { break };
- if changed_regions.intersects(&layer, text) {
+ if changed_regions.intersects(layer, text) {
if let SyntaxLayerContent::Parsed { language, .. } = &layer.content {
log::trace!(
"discard layer. language:{}, range:{:?}. changed_regions:{:?}",
@@ -524,7 +529,7 @@ impl SyntaxSnapshot {
if layer.range.to_offset(text) == (step_start_byte..step_end_byte)
&& layer.content.language_id() == step.language.id()
{
- cursor.next(&text);
+ cursor.next(text);
} else {
old_layer = None;
}
@@ -556,7 +561,7 @@ impl SyntaxSnapshot {
log::trace!(
"existing layer. language:{}, start:{:?}, ranges:{:?}",
language.name(),
- LogPoint(layer_start.to_point(&text)),
+ LogPoint(layer_start.to_point(text)),
LogIncludedRanges(&old_tree.included_ranges())
);
@@ -579,7 +584,7 @@ impl SyntaxSnapshot {
insert_newlines_between_ranges(
changed_indices,
&mut included_ranges,
- &text,
+ text,
step_start_byte,
step_start_point,
);
@@ -691,12 +696,12 @@ impl SyntaxSnapshot {
};
layers.push(
- SyntaxLayer {
+ SyntaxLayerEntry {
depth: step.depth,
range: step.range,
content,
},
- &text,
+ text,
);
}
@@ -741,7 +746,7 @@ impl SyntaxSnapshot {
SyntaxMapCaptures::new(
range.clone(),
text,
- [SyntaxLayerInfo {
+ [SyntaxLayer {
language,
tree,
depth: 0,
@@ -781,7 +786,7 @@ impl SyntaxSnapshot {
}
#[cfg(test)]
- pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayerInfo> {
+ pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer> {
self.layers_for_range(0..buffer.len(), buffer).collect()
}
@@ -789,7 +794,7 @@ impl SyntaxSnapshot {
&'a self,
range: Range<T>,
buffer: &'a BufferSnapshot,
- ) -> impl 'a + Iterator<Item = SyntaxLayerInfo> {
+ ) -> impl 'a + Iterator<Item = SyntaxLayer> {
let start_offset = range.start.to_offset(buffer);
let end_offset = range.end.to_offset(buffer);
let start = buffer.anchor_before(start_offset);
@@ -813,7 +818,7 @@ impl SyntaxSnapshot {
let layer_start_offset = layer.range.start.to_offset(buffer);
let layer_start_point = layer.range.start.to_point(buffer).to_ts_point();
- info = Some(SyntaxLayerInfo {
+ info = Some(SyntaxLayer {
tree,
language,
depth: layer.depth,
@@ -842,7 +847,7 @@ impl<'a> SyntaxMapCaptures<'a> {
fn new(
range: Range<usize>,
text: &'a Rope,
- layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
+ layers: impl Iterator<Item = SyntaxLayer<'a>>,
query: fn(&Grammar) -> Option<&Query>,
) -> Self {
let mut result = Self {
@@ -855,7 +860,7 @@ impl<'a> SyntaxMapCaptures<'a> {
Some(grammar) => grammar,
None => continue,
};
- let query = match query(&grammar) {
+ let query = match query(grammar) {
Some(query) => query,
None => continue,
};
@@ -964,7 +969,7 @@ impl<'a> SyntaxMapMatches<'a> {
fn new(
range: Range<usize>,
text: &'a Rope,
- layers: impl Iterator<Item = SyntaxLayerInfo<'a>>,
+ layers: impl Iterator<Item = SyntaxLayer<'a>>,
query: fn(&Grammar) -> Option<&Query>,
) -> Self {
let mut result = Self::default();
@@ -973,7 +978,7 @@ impl<'a> SyntaxMapMatches<'a> {
Some(grammar) => grammar,
None => continue,
};
- let query = match query(&grammar) {
+ let query = match query(grammar) {
Some(query) => query,
None => continue,
};
@@ -1082,7 +1087,7 @@ impl<'a> SyntaxMapMatchesLayer<'a> {
fn advance(&mut self) {
if let Some(mat) = self.matches.next() {
self.next_captures.clear();
- self.next_captures.extend_from_slice(&mat.captures);
+ self.next_captures.extend_from_slice(mat.captures);
self.next_pattern_index = mat.pattern_index;
self.has_next = true;
} else {
@@ -1296,7 +1301,7 @@ fn get_injections(
}
}
-/// Update the given list of included `ranges`, removing any ranges that intersect
+/// Updates the given list of included `ranges`, removing any ranges that intersect
/// `removed_ranges`, and inserting the given `new_ranges`.
///
/// Returns a new vector of ranges, and the range of the vector that was changed,
@@ -1436,23 +1441,25 @@ fn insert_newlines_between_ranges(
}
}
-impl OwnedSyntaxLayerInfo {
+impl OwnedSyntaxLayer {
+ /// Returns the root syntax node for this layer.
pub fn node(&self) -> Node {
self.tree
.root_node_with_offset(self.offset.0, self.offset.1)
}
}
-impl<'a> SyntaxLayerInfo<'a> {
- pub fn to_owned(&self) -> OwnedSyntaxLayerInfo {
- OwnedSyntaxLayerInfo {
+impl<'a> SyntaxLayer<'a> {
+ /// Returns an owned version of this layer.
+ pub fn to_owned(&self) -> OwnedSyntaxLayer {
+ OwnedSyntaxLayer {
tree: self.tree.clone(),
offset: self.offset,
- depth: self.depth,
language: self.language.clone(),
}
}
+ /// Returns the root node for this layer.
pub fn node(&self) -> Node<'a> {
self.tree
.root_node_with_offset(self.offset.0, self.offset.1)
@@ -1510,7 +1517,7 @@ impl Eq for ParseStep {}
impl PartialOrd for ParseStep {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(&other))
+ Some(self.cmp(other))
}
}
@@ -1564,7 +1571,7 @@ impl ChangeRegionSet {
)
}
- fn intersects(&self, layer: &SyntaxLayer, text: &BufferSnapshot) -> bool {
+ fn intersects(&self, layer: &SyntaxLayerEntry, text: &BufferSnapshot) -> bool {
for region in &self.0 {
if region.depth < layer.depth {
continue;
@@ -1675,7 +1682,7 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary>
}
}
-impl sum_tree::Item for SyntaxLayer {
+impl sum_tree::Item for SyntaxLayerEntry {
type Summary = SyntaxLayerSummary;
fn summary(&self) -> Self::Summary {
@@ -1690,7 +1697,7 @@ impl sum_tree::Item for SyntaxLayer {
}
}
-impl std::fmt::Debug for SyntaxLayer {
+impl std::fmt::Debug for SyntaxLayerEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SyntaxLayer")
.field("depth", &self.depth)
@@ -1,7 +1,7 @@
use editor::Editor;
use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext, WeakView};
use std::sync::Arc;
-use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip};
+use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, Workspace};
use crate::LanguageSelector;
@@ -45,7 +45,7 @@ struct LanguageServerRpcState {
pub struct LspLogView {
pub(crate) editor: View<Editor>,
- editor_subscription: Subscription,
+ editor_subscriptions: Vec<Subscription>,
log_store: Model<LogStore>,
current_server_id: Option<LanguageServerId>,
is_showing_rpc_trace: bool,
@@ -419,7 +419,7 @@ impl LspLogView {
}
}
});
- let (editor, editor_subscription) = Self::editor_for_logs(String::new(), cx);
+ let (editor, editor_subscriptions) = Self::editor_for_logs(String::new(), cx);
let focus_handle = cx.focus_handle();
let focus_subscription = cx.on_focus(&focus_handle, |log_view, cx| {
@@ -429,7 +429,7 @@ impl LspLogView {
let mut this = Self {
focus_handle,
editor,
- editor_subscription,
+ editor_subscriptions,
project,
log_store,
current_server_id: None,
@@ -449,7 +449,7 @@ impl LspLogView {
fn editor_for_logs(
log_contents: String,
cx: &mut ViewContext<Self>,
- ) -> (View<Editor>, Subscription) {
+ ) -> (View<Editor>, Vec<Subscription>) {
let editor = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.set_text(log_contents, cx);
@@ -464,7 +464,13 @@ impl LspLogView {
cx.emit(event.clone())
},
);
- (editor, editor_subscription)
+ let search_subscription = cx.subscribe(
+ &editor,
+ |_, _, event: &SearchEvent, cx: &mut ViewContext<'_, LspLogView>| {
+ cx.emit(event.clone())
+ },
+ );
+ (editor, vec![editor_subscription, search_subscription])
}
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
@@ -521,9 +527,9 @@ impl LspLogView {
if let Some(log_contents) = log_contents {
self.current_server_id = Some(server_id);
self.is_showing_rpc_trace = false;
- let (editor, editor_subscription) = Self::editor_for_logs(log_contents, cx);
+ let (editor, editor_subscriptions) = Self::editor_for_logs(log_contents, cx);
self.editor = editor;
- self.editor_subscription = editor_subscription;
+ self.editor_subscriptions = editor_subscriptions;
cx.notify();
}
cx.focus(&self.focus_handle);
@@ -542,7 +548,7 @@ impl LspLogView {
if let Some(rpc_log) = rpc_log {
self.current_server_id = Some(server_id);
self.is_showing_rpc_trace = true;
- let (editor, editor_subscription) = Self::editor_for_logs(rpc_log, cx);
+ let (editor, editor_subscriptions) = Self::editor_for_logs(rpc_log, cx);
let language = self.project.read(cx).languages().language_for_name("JSON");
editor
.read(cx)
@@ -564,7 +570,7 @@ impl LspLogView {
});
self.editor = editor;
- self.editor_subscription = editor_subscription;
+ self.editor_subscriptions = editor_subscriptions;
cx.notify();
}
@@ -5,7 +5,7 @@ use gpui::{
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext,
};
-use language::{Buffer, OwnedSyntaxLayerInfo};
+use language::{Buffer, OwnedSyntaxLayer};
use std::{mem, ops::Range};
use theme::ActiveTheme;
use tree_sitter::{Node, TreeCursor};
@@ -54,7 +54,7 @@ struct EditorState {
struct BufferState {
buffer: Model<Buffer>,
excerpt_id: ExcerptId,
- active_layer: Option<OwnedSyntaxLayerInfo>,
+ active_layer: Option<OwnedSyntaxLayer>,
}
impl SyntaxTreeView {
@@ -477,7 +477,7 @@ impl SyntaxTreeToolbarItemView {
})
}
- fn render_header(active_layer: &OwnedSyntaxLayerInfo) -> ButtonLike {
+ fn render_header(active_layer: &OwnedSyntaxLayer) -> ButtonLike {
ButtonLike::new("syntax tree header")
.child(Label::new(active_layer.language.name()))
.child(Label::new(format_node_range(active_layer.node())))
@@ -345,7 +345,7 @@ impl LanguageServer {
if let Some(handler) = notification_handlers.lock().get_mut(msg.method) {
handler(
msg.id,
- &msg.params.map(|params| params.get()).unwrap_or("null"),
+ msg.params.map(|params| params.get()).unwrap_or("null"),
cx.clone(),
);
} else {
@@ -863,17 +863,31 @@ impl LanguageServer {
.try_send(message)
.context("failed to write to language server's stdin");
+ let outbound_tx = outbound_tx.downgrade();
let mut timeout = executor.timer(LSP_REQUEST_TIMEOUT).fuse();
let started = Instant::now();
async move {
handle_response?;
send?;
+ let cancel_on_drop = util::defer(move || {
+ if let Some(outbound_tx) = outbound_tx.upgrade() {
+ Self::notify_internal::<notification::Cancel>(
+ &outbound_tx,
+ CancelParams {
+ id: NumberOrString::Number(id as i32),
+ },
+ )
+ .log_err();
+ }
+ });
+
let method = T::METHOD;
futures::select! {
response = rx.fuse() => {
let elapsed = started.elapsed();
log::trace!("Took {elapsed:?} to receive response to {method:?} id {id}");
+ cancel_on_drop.abort();
response?
}
@@ -2316,7 +2316,7 @@ impl MultiBufferSnapshot {
&self,
point: T,
) -> Option<(&BufferSnapshot, usize)> {
- let offset = point.to_offset(&self);
+ let offset = point.to_offset(self);
let mut cursor = self.excerpts.cursor::<usize>();
cursor.seek(&offset, Bias::Right, &());
if cursor.item().is_none() {
@@ -3028,7 +3028,7 @@ impl MultiBufferSnapshot {
pub fn has_git_diffs(&self) -> bool {
for excerpt in self.excerpts.iter() {
- if !excerpt.buffer.git_diff.is_empty() {
+ if excerpt.buffer.has_git_diff() {
return true;
}
}
@@ -3694,7 +3694,7 @@ impl ExcerptId {
pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering {
let a = snapshot.excerpt_locator_for_id(*self);
let b = snapshot.excerpt_locator_for_id(*other);
- a.cmp(&b).then_with(|| self.0.cmp(&other.0))
+ a.cmp(b).then_with(|| self.0.cmp(&other.0))
}
}
@@ -69,7 +69,7 @@ impl Default for Metering {
}
/// This struct is used to build a new [`Plugin`], using the builder pattern.
-/// Create a new default plugin with `PluginBuilder::new_with_default_ctx`,
+/// Creates a new default plugin with `PluginBuilder::new_with_default_ctx`,
/// and add host-side exported functions using `host_function` and `host_function_async`.
/// Finalize the plugin by calling [`init`].
pub struct PluginBuilder {
@@ -90,7 +90,7 @@ fn create_default_engine() -> Result<Engine, Error> {
}
impl PluginBuilder {
- /// Create a new [`PluginBuilder`] with the given WASI context.
+ /// Creates a new [`PluginBuilder`] with the given WASI context.
/// Using the default context is a safe bet, see [`new_with_default_context`].
/// This plugin will yield after a configurable amount of fuel is consumed.
pub fn new(wasi_ctx: WasiCtx, metering: Metering) -> Result<Self, Error> {
@@ -105,7 +105,7 @@ impl PluginBuilder {
})
}
- /// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]).
+ /// Creates a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]).
/// This plugin will yield after a configurable amount of fuel is consumed.
pub fn new_default() -> Result<Self, Error> {
let default_ctx = WasiCtxBuilder::new()
@@ -1532,7 +1532,7 @@ impl LspCommand for GetCompletions {
.iter()
.map(language::proto::serialize_completion)
.collect(),
- version: serialize_version(&buffer_version),
+ version: serialize_version(buffer_version),
}
}
@@ -1672,7 +1672,7 @@ impl LspCommand for GetCodeActions {
.iter()
.map(language::proto::serialize_code_action)
.collect(),
- version: serialize_version(&buffer_version),
+ version: serialize_version(buffer_version),
}
}
@@ -34,16 +34,16 @@ use gpui::{
use itertools::Itertools;
use language::{
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
- point_to_lsp,
+ markdown, point_to_lsp,
proto::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
- Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
- LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
- TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
+ Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
+ LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
+ PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use log::error;
use lsp::{
@@ -52,7 +52,7 @@ use lsp::{
};
use lsp_command::*;
use node_runtime::NodeRuntime;
-use parking_lot::Mutex;
+use parking_lot::{Mutex, RwLock};
use postage::watch;
use prettier_support::{DefaultPrettier, PrettierInstance};
use project_settings::{LspSettings, ProjectSettings};
@@ -2947,7 +2947,7 @@ impl Project {
};
task.await;
- this.update(&mut cx, |this, mut cx| {
+ this.update(&mut cx, |this, cx| {
let worktrees = this.worktrees.clone();
for worktree in worktrees {
let worktree = match worktree.upgrade() {
@@ -2962,7 +2962,7 @@ impl Project {
root_path,
adapter.clone(),
language.clone(),
- &mut cx,
+ cx,
);
}
})
@@ -3544,8 +3544,8 @@ impl Project {
if errored {
log::warn!("test binary check failed");
let task = this
- .update(&mut cx, move |this, mut cx| {
- this.reinstall_language_server(language, adapter, server_id, &mut cx)
+ .update(&mut cx, move |this, cx| {
+ this.reinstall_language_server(language, adapter, server_id, cx)
})
.ok()
.flatten();
@@ -3768,8 +3768,7 @@ impl Project {
}
};
if let Some(relative_glob_pattern) = relative_glob_pattern {
- let literal_prefix =
- glob_literal_prefix(&relative_glob_pattern);
+ let literal_prefix = glob_literal_prefix(relative_glob_pattern);
tree.as_local_mut()
.unwrap()
.add_path_prefix_to_scan(Path::new(literal_prefix).into());
@@ -3910,7 +3909,6 @@ impl Project {
message: diagnostic.message.clone(),
group_id,
is_primary: true,
- is_valid: true,
is_disk_based,
is_unnecessary,
},
@@ -3928,7 +3926,6 @@ impl Project {
message: info.message.clone(),
group_id,
is_primary: false,
- is_valid: true,
is_disk_based,
is_unnecessary: false,
},
@@ -4233,9 +4230,9 @@ impl Project {
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
&project,
- &buffer,
+ buffer,
buffer_abs_path,
- &language_server,
+ language_server,
tab_size,
&mut cx,
)
@@ -4254,8 +4251,8 @@ impl Project {
format_operation = Self::format_via_external_command(
buffer,
buffer_abs_path,
- &command,
- &arguments,
+ command,
+ arguments,
&mut cx,
)
.await
@@ -4278,9 +4275,9 @@ impl Project {
format_operation = Some(FormatOperation::Lsp(
Self::format_via_lsp(
&project,
- &buffer,
+ buffer,
buffer_abs_path,
- &language_server,
+ language_server,
tab_size,
&mut cx,
)
@@ -4830,6 +4827,170 @@ impl Project {
}
}
+ pub fn resolve_completions(
+ &self,
+ completion_indices: Vec<usize>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<bool>> {
+ let client = self.client();
+ let language_registry = self.languages().clone();
+
+ let is_remote = self.is_remote();
+ let project_id = self.remote_id();
+
+ cx.spawn(move |this, mut cx| async move {
+ let mut did_resolve = false;
+ if is_remote {
+ let project_id =
+ project_id.ok_or_else(|| anyhow!("Remote project without remote_id"))?;
+
+ for completion_index in completion_indices {
+ let completions_guard = completions.read();
+ let completion = &completions_guard[completion_index];
+ if completion.documentation.is_some() {
+ continue;
+ }
+
+ did_resolve = true;
+ let server_id = completion.server_id;
+ let completion = completion.lsp_completion.clone();
+ drop(completions_guard);
+
+ Self::resolve_completion_documentation_remote(
+ project_id,
+ server_id,
+ completions.clone(),
+ completion_index,
+ completion,
+ client.clone(),
+ language_registry.clone(),
+ )
+ .await;
+ }
+ } else {
+ for completion_index in completion_indices {
+ let completions_guard = completions.read();
+ let completion = &completions_guard[completion_index];
+ if completion.documentation.is_some() {
+ continue;
+ }
+
+ let server_id = completion.server_id;
+ let completion = completion.lsp_completion.clone();
+ drop(completions_guard);
+
+ let server = this
+ .read_with(&mut cx, |project, _| {
+ project.language_server_for_id(server_id)
+ })
+ .ok()
+ .flatten();
+ let Some(server) = server else {
+ continue;
+ };
+
+ did_resolve = true;
+ Self::resolve_completion_documentation_local(
+ server,
+ completions.clone(),
+ completion_index,
+ completion,
+ language_registry.clone(),
+ )
+ .await;
+ }
+ }
+
+ Ok(did_resolve)
+ })
+ }
+
+ async fn resolve_completion_documentation_local(
+ server: Arc<lsp::LanguageServer>,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ completion_index: usize,
+ completion: lsp::CompletionItem,
+ language_registry: Arc<LanguageRegistry>,
+ ) {
+ let can_resolve = server
+ .capabilities()
+ .completion_provider
+ .as_ref()
+ .and_then(|options| options.resolve_provider)
+ .unwrap_or(false);
+ if !can_resolve {
+ return;
+ }
+
+ let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
+ let Some(completion_item) = request.await.log_err() else {
+ return;
+ };
+
+ if let Some(lsp_documentation) = completion_item.documentation {
+ let documentation = language::prepare_completion_documentation(
+ &lsp_documentation,
+ &language_registry,
+ None, // TODO: Try to reasonably work out which language the completion is for
+ )
+ .await;
+
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(documentation);
+ } else {
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(Documentation::Undocumented);
+ }
+ }
+
+ async fn resolve_completion_documentation_remote(
+ project_id: u64,
+ server_id: LanguageServerId,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ completion_index: usize,
+ completion: lsp::CompletionItem,
+ client: Arc<Client>,
+ language_registry: Arc<LanguageRegistry>,
+ ) {
+ let request = proto::ResolveCompletionDocumentation {
+ project_id,
+ language_server_id: server_id.0 as u64,
+ lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
+ };
+
+ let Some(response) = client
+ .request(request)
+ .await
+ .context("completion documentation resolve proto request")
+ .log_err()
+ else {
+ return;
+ };
+
+ if response.text.is_empty() {
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(Documentation::Undocumented);
+ }
+
+ let documentation = if response.is_markdown {
+ Documentation::MultiLineMarkdown(
+ markdown::parse_markdown(&response.text, &language_registry, None).await,
+ )
+ } else if response.text.lines().count() <= 1 {
+ Documentation::SingleLine(response.text)
+ } else {
+ Documentation::MultiLinePlainText(response.text)
+ };
+
+ let mut completions = completions.write();
+ let completion = &mut completions[completion_index];
+ completion.documentation = Some(documentation);
+ }
+
pub fn apply_additional_edits_for_completion(
&self,
buffer_handle: Model<Buffer>,
@@ -5685,7 +5846,7 @@ impl Project {
snapshot.file().map(|file| file.path().as_ref()),
) {
query
- .search(&snapshot, None)
+ .search(snapshot, None)
.await
.iter()
.map(|range| {
@@ -6528,7 +6689,7 @@ impl Project {
snapshot.repository_and_work_directory_for_path(&path)?;
let repo = snapshot.get_local_repo(&repo)?;
let relative_path = path.strip_prefix(&work_directory).ok()?;
- let base_text = repo.repo_ptr.lock().load_index_text(&relative_path);
+ let base_text = repo.repo_ptr.lock().load_index_text(relative_path);
Some((buffer, base_text))
})
.collect::<Vec<_>>()
@@ -6661,7 +6822,7 @@ impl Project {
for (_, _, path_summary) in
self.diagnostic_summaries(include_ignored, cx)
.filter(|(path, _, _)| {
- let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ let worktree = self.entry_for_path(path, cx).map(|entry| entry.is_ignored);
include_ignored || worktree == Some(false)
})
{
@@ -6687,7 +6848,7 @@ impl Project {
})
})
.filter(move |(path, _, _)| {
- let worktree = self.entry_for_path(&path, cx).map(|entry| entry.is_ignored);
+ let worktree = self.entry_for_path(path, cx).map(|entry| entry.is_ignored);
include_ignored || worktree == Some(false)
})
}
@@ -61,7 +61,7 @@ impl Project {
if let Some(python_settings) = &python_settings.as_option() {
let activate_script_path =
- self.find_activate_script_path(&python_settings, working_directory);
+ self.find_activate_script_path(python_settings, working_directory);
self.activate_python_virtual_environment(
activate_script_path,
&terminal_handle,
@@ -1437,7 +1437,7 @@ impl LocalWorktree {
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
project_id,
worktree_id: cx.entity_id().as_u64(),
- summary: Some(summary.to_proto(server_id, &path)),
+ summary: Some(summary.to_proto(server_id, path)),
}) {
return Task::ready(Err(e));
}
@@ -1866,7 +1866,7 @@ impl Snapshot {
})
}
- /// Update the `git_status` of the given entries such that files'
+ /// Updates the `git_status` of the given entries such that files'
/// statuses bubble up to their ancestor directories.
pub fn propagate_git_statuses(&self, result: &mut [Entry]) {
let mut cursor = self
@@ -2309,7 +2309,7 @@ impl LocalSnapshot {
impl BackgroundScannerState {
fn should_scan_directory(&self, entry: &Entry) -> bool {
(!entry.is_external && !entry.is_ignored)
- || entry.path.file_name() == Some(&*DOT_GIT)
+ || entry.path.file_name() == Some(*DOT_GIT)
|| self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning
|| self
.paths_to_scan
@@ -3374,7 +3374,7 @@ impl BackgroundScanner {
let mut is_git_related = false;
if let Some(dot_git_dir) = abs_path
.ancestors()
- .find(|ancestor| ancestor.file_name() == Some(&*DOT_GIT))
+ .find(|ancestor| ancestor.file_name() == Some(*DOT_GIT))
{
let dot_git_path = dot_git_dir
.strip_prefix(&root_canonical_path)
@@ -3772,7 +3772,7 @@ impl BackgroundScanner {
for entry in &mut new_entries {
state.reuse_entry_id(entry);
if entry.is_dir() {
- if state.should_scan_directory(&entry) {
+ if state.should_scan_directory(entry) {
job_ix += 1;
} else {
log::debug!("defer scanning directory {:?}", entry.path);
@@ -3814,9 +3814,9 @@ impl BackgroundScanner {
abs_paths
.iter()
.map(|abs_path| async move {
- let metadata = self.fs.metadata(&abs_path).await?;
+ let metadata = self.fs.metadata(abs_path).await?;
if let Some(metadata) = metadata {
- let canonical_path = self.fs.canonicalize(&abs_path).await?;
+ let canonical_path = self.fs.canonicalize(abs_path).await?;
anyhow::Ok(Some((metadata, canonical_path)))
} else {
Ok(None)
@@ -3864,7 +3864,7 @@ impl BackgroundScanner {
fs_entry.is_external = !canonical_path.starts_with(&root_canonical_path);
if !is_dir && !fs_entry.is_ignored {
- if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(&path) {
+ if let Some((work_dir, repo)) = state.snapshot.local_repo_for_path(path) {
if let Ok(repo_path) = path.strip_prefix(work_dir.0) {
let repo_path = RepoPath(repo_path.into());
let repo = repo.repo_ptr.lock();
@@ -3884,7 +3884,7 @@ impl BackgroundScanner {
state.insert_entry(fs_entry, self.fs.as_ref());
}
Ok(None) => {
- self.remove_repo_path(&path, &mut state.snapshot);
+ self.remove_repo_path(path, &mut state.snapshot);
}
Err(err) => {
// TODO - create a special 'error' entry in the entries tree to mark this
@@ -381,67 +381,57 @@ impl ProjectPanel {
let is_local = project.is_local();
let is_read_only = project.is_read_only();
- let context_menu = ContextMenu::build(cx, |mut menu, cx| {
- if is_read_only {
- menu = menu.action("Copy Relative Path", Box::new(CopyRelativePath));
- if is_dir {
- menu = menu.action("Search Inside", Box::new(NewSearchInDirectory))
- }
-
- return menu;
- }
-
- if is_local {
- menu = menu.action(
- "Add Folder to Project",
- Box::new(workspace::AddFolderToProject),
- );
- if is_root {
- menu = menu.entry(
- "Remove from Project",
- None,
- cx.handler_for(&this, move |this, cx| {
- this.project.update(cx, |project, cx| {
- project.remove_worktree(worktree_id, cx)
- });
- }),
- );
- }
- }
-
- menu = menu
- .action("New File", Box::new(NewFile))
- .action("New Folder", Box::new(NewDirectory))
- .separator()
- .action("Cut", Box::new(Cut))
- .action("Copy", Box::new(Copy));
-
- if let Some(clipboard_entry) = self.clipboard_entry {
- if clipboard_entry.worktree_id() == worktree_id {
- menu = menu.action("Paste", Box::new(Paste));
- }
- }
-
- menu = menu
- .separator()
- .action("Copy Path", Box::new(CopyPath))
- .action("Copy Relative Path", Box::new(CopyRelativePath))
- .separator()
- .action("Reveal in Finder", Box::new(RevealInFinder));
-
- if is_dir {
- menu = menu
- .action("Open in Terminal", Box::new(OpenInTerminal))
- .action("Search Inside", Box::new(NewSearchInDirectory))
- }
-
- menu = menu.separator().action("Rename", Box::new(Rename));
-
- if !is_root {
- menu = menu.action("Delete", Box::new(Delete));
- }
-
- menu
+ let context_menu = ContextMenu::build(cx, |menu, cx| {
+ menu.context(self.focus_handle.clone()).when_else(
+ is_read_only,
+ |menu| {
+ menu.action("Copy Relative Path", Box::new(CopyRelativePath))
+ .when(is_dir, |menu| {
+ menu.action("Search Inside", Box::new(NewSearchInDirectory))
+ })
+ },
+ |menu| {
+ menu.when(is_local, |menu| {
+ menu.action(
+ "Add Folder to Project",
+ Box::new(workspace::AddFolderToProject),
+ )
+ .when(is_root, |menu| {
+ menu.entry(
+ "Remove from Project",
+ None,
+ cx.handler_for(&this, move |this, cx| {
+ this.project.update(cx, |project, cx| {
+ project.remove_worktree(worktree_id, cx)
+ });
+ }),
+ )
+ })
+ })
+ .action("New File", Box::new(NewFile))
+ .action("New Folder", Box::new(NewDirectory))
+ .separator()
+ .action("Cut", Box::new(Cut))
+ .action("Copy", Box::new(Copy))
+ .when_some(self.clipboard_entry, |menu, entry| {
+ menu.when(entry.worktree_id() == worktree_id, |menu| {
+ menu.action("Paste", Box::new(Paste))
+ })
+ })
+ .separator()
+ .action("Copy Path", Box::new(CopyPath))
+ .action("Copy Relative Path", Box::new(CopyRelativePath))
+ .separator()
+ .action("Reveal in Finder", Box::new(RevealInFinder))
+ .when(is_dir, |menu| {
+ menu.action("Open in Terminal", Box::new(OpenInTerminal))
+ .action("Search Inside", Box::new(NewSearchInDirectory))
+ })
+ .separator()
+ .action("Rename", Box::new(Rename))
+ .when(!is_root, |menu| menu.action("Delete", Box::new(Delete)))
+ },
+ )
});
cx.focus_view(&context_menu);
@@ -21,6 +21,7 @@ sum_tree = { path = "../sum_tree" }
theme = { path = "../theme" }
language = { path = "../language" }
util = { path = "../util" }
+ui = { path = "../ui" }
anyhow.workspace = true
futures.workspace = true
lazy_static.workspace = true
@@ -6,6 +6,7 @@ use gpui::{
use language::{HighlightId, Language, LanguageRegistry};
use std::{ops::Range, sync::Arc};
use theme::ActiveTheme;
+use ui::LinkPreview;
use util::RangeExt;
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -64,7 +65,7 @@ impl RichText {
},
Highlight::Id(id) => HighlightStyle {
background_color: Some(code_background),
- ..id.style(&theme.syntax()).unwrap_or_default()
+ ..id.style(theme.syntax()).unwrap_or_default()
},
Highlight::Highlight(highlight) => *highlight,
Highlight::Mention => HighlightStyle {
@@ -84,6 +85,18 @@ impl RichText {
let link_urls = self.link_urls.clone();
move |ix, cx| cx.open_url(&link_urls[ix])
})
+ .tooltip({
+ let link_ranges = self.link_ranges.clone();
+ let link_urls = self.link_urls.clone();
+ move |idx, cx| {
+ for (ix, range) in link_ranges.iter().enumerate() {
+ if range.contains(&idx) {
+ return Some(LinkPreview::new(&link_urls[ix], cx));
+ }
+ }
+ None
+ }
+ })
.into_any_element()
}
}
@@ -107,7 +120,7 @@ pub fn render_markdown_mut(
let mut list_stack = Vec::new();
let options = Options::all();
- for (event, source_range) in Parser::new_ext(&block, options).into_offset_iter() {
+ for (event, source_range) in Parser::new_ext(block, options).into_offset_iter() {
let prev_len = text.len();
match event {
Event::Text(t) => {
@@ -237,7 +250,7 @@ pub fn render_markdown_mut(
_ => {}
},
Event::HardBreak => text.push('\n'),
- Event::SoftBreak => text.push(' '),
+ Event::SoftBreak => text.push('\n'),
_ => {}
}
}
@@ -1472,7 +1472,10 @@ message Diagnostic {
optional string code = 6;
uint64 group_id = 7;
bool is_primary = 8;
+
+ // TODO: remove this field
bool is_valid = 9;
+
bool is_disk_based = 10;
bool is_unnecessary = 11;
@@ -246,7 +246,7 @@ impl SettingsStore {
this
}
- /// Update the value of a setting in the user's global configuration.
+ /// Updates the value of a setting in the user's global configuration.
///
/// This is only for tests. Normally, settings are only loaded from
/// JSON files.
@@ -261,7 +261,7 @@ impl SettingsStore {
self.set_user_settings(&new_text, cx).unwrap();
}
- /// Update the value of a setting in a JSON file, returning the new text
+ /// Updates the value of a setting in a JSON file, returning the new text
/// for that JSON file.
pub fn new_text_for_update<T: Settings>(
&self,
@@ -276,7 +276,7 @@ impl SettingsStore {
new_text
}
- /// Update the value of a setting in a JSON file, returning a list
+ /// Updates the value of a setting in a JSON file, returning a list
/// of edits to apply to the JSON file.
pub fn edits_for_update<T: Settings>(
&self,
@@ -344,7 +344,7 @@ impl SettingsStore {
DEFAULT_JSON_TAB_SIZE
}
- /// Set the default settings via a JSON string.
+ /// Sets the default settings via a JSON string.
///
/// The string should contain a JSON object with a default value for every setting.
pub fn set_default_settings(
@@ -362,7 +362,7 @@ impl SettingsStore {
}
}
- /// Set the user settings via a JSON string.
+ /// Sets the user settings via a JSON string.
pub fn set_user_settings(
&mut self,
user_settings_content: &str,
@@ -533,7 +533,7 @@ impl SettingsStore {
}
if let Some(local_settings) =
- setting_value.deserialize_setting(&local_settings).log_err()
+ setting_value.deserialize_setting(local_settings).log_err()
{
paths_stack.push(Some((*root_id, path.as_ref())));
user_settings_stack.push(local_settings);
@@ -697,8 +697,7 @@ fn update_value_in_json_text<'a>(
if let Some(new_object) = new_value.as_object_mut() {
new_object.retain(|_, v| !v.is_null());
}
- let (range, replacement) =
- replace_value_in_json_text(text, &key_path, tab_size, &new_value);
+ let (range, replacement) = replace_value_in_json_text(text, key_path, tab_size, &new_value);
text.replace_range(range.clone(), &replacement);
edits.push((range, replacement));
}
@@ -67,8 +67,8 @@ impl StoryContainer {
}
impl ParentElement for StoryContainer {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -104,7 +104,7 @@ impl RenderOnce for StoryContainer {
.h_px()
.flex_1()
.id("story_body")
- .overflow_hidden_x()
+ .overflow_x_hidden()
.overflow_y_scroll()
.flex()
.flex_col()
@@ -372,7 +372,7 @@ impl RenderOnce for StorySection {
}
impl ParentElement for StorySection {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -110,66 +110,3 @@ impl Render for TextStory {
).into_element()
}
}
-
-// TODO: Check all were updated to new style and remove
-
-// impl Render for TextStory {
-// type Element = Div;
-
-// fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
-// v_flex()
-// .bg(blue())
-// .child(
-// div()
-// .flex()
-// .child(div().max_w_96().bg(white()).child(concat!(
-// "max-width: 96. The quick brown fox jumps over the lazy dog. ",
-// "Meanwhile, the lazy dog decided it was time for a change. ",
-// "He started daily workout routines, ate healthier and became the fastest dog in town.",
-// ))),
-// )
-// .child(div().h_5())
-// .child(div().flex().flex_col().w_96().bg(white()).child(concat!(
-// "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ",
-// "Meanwhile, the lazy dog decided it was time for a change. ",
-// "He started daily workout routines, ate healthier and became the fastest dog in town.",
-// )))
-// .child(div().h_5())
-// .child(
-// div()
-// .flex()
-// .child(div().min_w_96().bg(white()).child(concat!(
-// "min-width: 96. The quick brown fox jumps over the lazy dog. ",
-// "Meanwhile, the lazy dog decided it was time for a change. ",
-// "He started daily workout routines, ate healthier and became the fastest dog in town.",
-// ))))
-// .child(div().h_5())
-// .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!(
-// "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
-// "Meanwhile, the lazy dog decided it was time for a change. ",
-// "He started daily workout routines, ate healthier and became the fastest dog in town.",
-// ))))
-// // NOTE: When rendering text in a horizontal flex container,
-// // Taffy will not pass width constraints down from the parent.
-// // To fix this, render text in a parent with overflow: hidden
-// .child(div().h_5())
-// .child(div().flex().w_96().bg(red()).child(concat!(
-// "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
-// "Meanwhile, the lazy dog decided it was time for a change. ",
-// "He started daily workout routines, ate healthier and became the fastest dog in town.",
-// ))).child(
-// InteractiveText::new(
-// "interactive",
-// StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
-// (6..11, HighlightStyle {
-// background_color: Some(green()),
-// ..Default::default()
-// }),
-// ]),
-// )
-// .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
-// println!("Clicked range {range_ix}");
-// })
-// )
-// }
-// }
@@ -76,7 +76,7 @@ impl Styles for Div {}
#[derive(IntoElement)]
struct ZIndexExample {
- z_index: u8,
+ z_index: u16,
}
impl RenderOnce for ZIndexExample {
@@ -166,7 +166,7 @@ impl RenderOnce for ZIndexExample {
}
impl ZIndexExample {
- pub fn new(z_index: u8) -> Self {
+ pub fn new(z_index: u16) -> Self {
Self { z_index }
}
}
@@ -52,9 +52,9 @@ use std::{
use thiserror::Error;
use gpui::{
- actions, black, px, red, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter,
- Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent,
- MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
+ actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla,
+ Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+ Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
};
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
@@ -599,7 +599,11 @@ impl Terminal {
}
}
- /// Update the cached process info, returns whether the Zed-relevant info has changed
+ pub fn selection_started(&self) -> bool {
+ self.selection_phase == SelectionPhase::Selecting
+ }
+
+ /// Updates the cached process info, returns whether the Zed-relevant info has changed
fn update_process_info(&mut self) -> bool {
let mut pid = unsafe { libc::tcgetpgrp(self.shell_fd as i32) };
if pid < 0 {
@@ -1206,7 +1210,7 @@ impl Terminal {
pub fn scroll_wheel(&mut self, e: &ScrollWheelEvent, origin: Point<Pixels>) {
let mouse_mode = self.mouse_mode(e.shift);
- if let Some(scroll_lines) = self.determine_scroll_lines(&e, mouse_mode) {
+ if let Some(scroll_lines) = self.determine_scroll_lines(e, mouse_mode) {
if mouse_mode {
let point = grid_point(
e.position - origin,
@@ -1215,7 +1219,7 @@ impl Terminal {
);
if let Some(scrolls) =
- scroll_report(point, scroll_lines as i32, &e, self.last_content.mode)
+ scroll_report(point, scroll_lines as i32, e, self.last_content.mode)
{
for scroll in scrolls {
self.pty_tx.notify(scroll);
@@ -1295,7 +1299,7 @@ impl Terminal {
"{}{}",
fpi.name,
if fpi.argv.len() >= 1 {
- format!(" {}", (&fpi.argv[1..]).join(" "))
+ format!(" {}", (fpi.argv[1..]).join(" "))
} else {
"".to_string()
}
@@ -1380,7 +1384,7 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla {
let colors = theme.colors();
match index {
- //0-15 are the same as the named colors above
+ // 0-15 are the same as the named colors above
0 => colors.terminal_ansi_black,
1 => colors.terminal_ansi_red,
2 => colors.terminal_ansi_green,
@@ -1397,34 +1401,32 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla {
13 => colors.terminal_ansi_bright_magenta,
14 => colors.terminal_ansi_bright_cyan,
15 => colors.terminal_ansi_bright_white,
- //16-231 are mapped to their RGB colors on a 0-5 range per channel
+ // 16-231 are mapped to their RGB colors on a 0-5 range per channel
16..=231 => {
- let (r, g, b) = rgb_for_index(&(index as u8)); //Split the index into it's ANSI-RGB components
- let step = (u8::MAX as f32 / 5.).floor() as u8; //Split the RGB range into 5 chunks, with floor so no overflow
- rgba_color(r * step, g * step, b * step) //Map the ANSI-RGB components to an RGB color
+ let (r, g, b) = rgb_for_index(&(index as u8)); // Split the index into it's ANSI-RGB components
+ let step = (u8::MAX as f32 / 5.).floor() as u8; // Split the RGB range into 5 chunks, with floor so no overflow
+ rgba_color(r * step, g * step, b * step) // Map the ANSI-RGB components to an RGB color
}
- //232-255 are a 24 step grayscale from black to white
+ // 232-255 are a 24 step grayscale from black to white
232..=255 => {
- let i = index as u8 - 232; //Align index to 0..24
- let step = (u8::MAX as f32 / 24.).floor() as u8; //Split the RGB grayscale values into 24 chunks
- rgba_color(i * step, i * step, i * step) //Map the ANSI-grayscale components to the RGB-grayscale
+ let i = index as u8 - 232; // Align index to 0..24
+ let step = (u8::MAX as f32 / 24.).floor() as u8; // Split the RGB grayscale values into 24 chunks
+ rgba_color(i * step, i * step, i * step) // Map the ANSI-grayscale components to the RGB-grayscale
}
- //For compatibility with the alacritty::Colors interface
+ // For compatibility with the alacritty::Colors interface
256 => colors.text,
257 => colors.background,
258 => theme.players().local().cursor,
-
- // todo!(more colors)
- 259 => red(), //style.dim_black,
- 260 => red(), //style.dim_red,
- 261 => red(), //style.dim_green,
- 262 => red(), //style.dim_yellow,
- 263 => red(), //style.dim_blue,
- 264 => red(), //style.dim_magenta,
- 265 => red(), //style.dim_cyan,
- 266 => red(), //style.dim_white,
- 267 => red(), //style.bright_foreground,
- 268 => colors.terminal_ansi_black, //'Dim Background', non-standard color
+ 259 => colors.terminal_ansi_dim_black,
+ 260 => colors.terminal_ansi_dim_red,
+ 261 => colors.terminal_ansi_dim_green,
+ 262 => colors.terminal_ansi_dim_yellow,
+ 263 => colors.terminal_ansi_dim_blue,
+ 264 => colors.terminal_ansi_dim_magenta,
+ 265 => colors.terminal_ansi_dim_cyan,
+ 266 => colors.terminal_ansi_dim_white,
+ 267 => colors.terminal_bright_foreground,
+ 268 => colors.terminal_ansi_black, // 'Dim Background', non-standard color
_ => black(),
}
@@ -90,17 +90,17 @@ pub struct TerminalSettingsContent {
///
/// Default: current_project_directory
pub working_directory: Option<WorkingDirectory>,
- /// Set the terminal's font size.
+ /// Sets the terminal's font size.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font size.
pub font_size: Option<f32>,
- /// Set the terminal's font family.
+ /// Sets the terminal's font family.
///
/// If this option is not included,
/// the terminal will default to matching the buffer's font family.
pub font_family: Option<String>,
- /// Set the terminal's line height.
+ /// Sets the terminal's line height.
///
/// Default: comfortable
pub line_height: Option<TerminalLineHeight>,
@@ -110,18 +110,18 @@ pub struct TerminalSettingsContent {
///
/// Default: {}
pub env: Option<HashMap<String, String>>,
- /// Set the cursor blinking behavior in the terminal.
+ /// Sets the cursor blinking behavior in the terminal.
///
/// Default: terminal_controlled
pub blinking: Option<TerminalBlink>,
- /// Set whether Alternate Scroll mode (code: ?1007) is active by default.
+ /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
/// Alternate Scroll mode converts mouse scroll events into up / down key
/// presses when in the alternate screen (e.g. when running applications
/// like vim or less). The terminal can still set and unset this mode.
///
/// Default: off
pub alternate_scroll: Option<AlternateScroll>,
- /// Set whether the option key behaves as the meta key.
+ /// Sets whether the option key behaves as the meta key.
///
/// Default: false
pub option_as_meta: Option<bool>,
@@ -139,7 +139,7 @@ pub struct TerminalSettingsContent {
///
/// Default: 320
pub default_height: Option<f32>,
- /// Activate the python virtual environment, if one is found, in the
+ /// Activates the python virtual environment, if one is found, in the
/// terminal's working directory (as resolved by the working_directory
/// setting). Set this to "off" to disable this behavior.
///
@@ -1,12 +1,11 @@
use editor::{Cursor, HighlightedRange, HighlightedRangeLine};
use gpui::{
- div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace,
- BorrowWindow, Bounds, DispatchPhase, Element, ElementId, FocusHandle, Font, FontStyle,
- FontWeight, HighlightStyle, Hsla, InteractiveBounds, InteractiveElement,
- InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext,
- ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point,
- ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle,
- WeakView, WhiteSpace, WindowContext,
+ div, fill, point, px, relative, AnyElement, AvailableSpace, Bounds, DispatchPhase, Element,
+ ElementContext, ElementId, FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hsla,
+ InputHandler, InteractiveBounds, InteractiveElement, InteractiveElementState, Interactivity,
+ IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, MouseMoveEvent,
+ Pixels, Point, ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem,
+ UnderlineStyle, WeakView, WhiteSpace, WindowContext,
};
use itertools::Itertools;
use language::CursorShape;
@@ -29,7 +28,7 @@ use workspace::Workspace;
use std::mem;
use std::{fmt::Debug, ops::RangeInclusive};
-///The information generated during layout that is necessary for painting
+/// The information generated during layout that is necessary for painting.
pub struct LayoutState {
cells: Vec<LayoutCell>,
rects: Vec<LayoutRect>,
@@ -43,7 +42,7 @@ pub struct LayoutState {
gutter: Pixels,
}
-///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
+/// Helper struct for converting data between Alacritty's cursor points, and displayed cursor points.
struct DisplayCursor {
line: i32,
col: usize,
@@ -82,7 +81,7 @@ impl LayoutCell {
origin: Point<Pixels>,
layout: &LayoutState,
_visible_bounds: Bounds<Pixels>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let pos = {
let point = self.point;
@@ -121,7 +120,7 @@ impl LayoutRect {
}
}
- fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
+ fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
let position = {
let alac_point = self.point;
point(
@@ -139,8 +138,8 @@ impl LayoutRect {
}
}
-///The GPUI element that paints the terminal.
-///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
+/// The GPUI element that paints the terminal.
+/// We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
pub struct TerminalElement {
terminal: Model<Terminal>,
workspace: WeakView<Workspace>,
@@ -278,8 +277,8 @@ impl TerminalElement {
(cells, rects)
}
- // Compute the cursor position and expected block width, may return a zero width if x_for_index returns
- // the same position for sequential indexes. Use em_width instead
+ /// Computes the cursor position and expected block width, may return a zero width if x_for_index returns
+ /// the same position for sequential indexes. Use em_width instead
fn shape_cursor(
cursor_point: DisplayCursor,
size: TerminalSize,
@@ -306,7 +305,7 @@ impl TerminalElement {
}
}
- /// Convert the Alacritty cell styles to GPUI text styles and background color
+ /// Converts the Alacritty cell styles to GPUI text styles and background color.
fn cell_style(
indexed: &IndexedCell,
fg: terminal::alacritty_terminal::ansi::Color,
@@ -366,7 +365,7 @@ impl TerminalElement {
result
}
- fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut WindowContext) -> LayoutState {
+ fn compute_layout(&self, bounds: Bounds<gpui::Pixels>, cx: &mut ElementContext) -> LayoutState {
let settings = ThemeSettings::get_global(cx).clone();
let buffer_font_size = settings.buffer_font_size(cx);
@@ -506,8 +505,8 @@ impl TerminalElement {
cx,
);
- //Layout cursor. Rectangle is used for IME, so we should lay it out even
- //if we don't end up showing it.
+ // Layout cursor. Rectangle is used for IME, so we should lay it out even
+ // if we don't end up showing it.
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
None
} else {
@@ -550,12 +549,12 @@ impl TerminalElement {
theme.players().local().cursor,
shape,
text,
+ None,
)
},
)
};
- //Done!
LayoutState {
cells,
cursor,
@@ -591,7 +590,7 @@ impl TerminalElement {
origin: Point<Pixels>,
mode: TermMode,
bounds: Bounds<Pixels>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let focus = self.focus.clone();
let terminal = self.terminal.clone();
@@ -622,9 +621,17 @@ impl TerminalElement {
}
if e.pressed_button.is_some() && !cx.has_active_drag() {
+ let visibly_contains = interactive_bounds.visibly_contains(&e.position, cx);
terminal.update(cx, |terminal, cx| {
- terminal.mouse_drag(e, origin, bounds);
- cx.notify();
+ if !terminal.selection_started() {
+ if visibly_contains {
+ terminal.mouse_drag(e, origin, bounds);
+ cx.notify();
+ }
+ } else {
+ terminal.mouse_drag(e, origin, bounds);
+ cx.notify();
+ }
})
}
@@ -715,7 +722,7 @@ impl Element for TerminalElement {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext<'_>,
+ cx: &mut ElementContext<'_>,
) -> (LayoutId, Self::State) {
let (layout_id, interactive_state) =
self.interactivity
@@ -734,7 +741,7 @@ impl Element for TerminalElement {
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::State,
- cx: &mut WindowContext<'_>,
+ cx: &mut ElementContext<'_>,
) {
let mut layout = self.compute_layout(bounds, cx);
@@ -742,7 +749,6 @@ impl Element for TerminalElement {
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
let terminal_input_handler = TerminalInputHandler {
- cx: cx.to_async(),
terminal: self.terminal.clone(),
cursor_bounds: layout
.cursor
@@ -831,37 +837,35 @@ impl IntoElement for TerminalElement {
}
struct TerminalInputHandler {
- cx: AsyncWindowContext,
terminal: Model<Terminal>,
workspace: WeakView<Workspace>,
cursor_bounds: Option<Bounds<Pixels>>,
}
-impl PlatformInputHandler for TerminalInputHandler {
- fn selected_text_range(&mut self) -> Option<std::ops::Range<usize>> {
- self.cx
- .update(|_, cx| {
- if self
- .terminal
- .read(cx)
- .last_content
- .mode
- .contains(TermMode::ALT_SCREEN)
- {
- None
- } else {
- Some(0..0)
- }
- })
- .ok()
- .flatten()
+impl InputHandler for TerminalInputHandler {
+ fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<std::ops::Range<usize>> {
+ if self
+ .terminal
+ .read(cx)
+ .last_content
+ .mode
+ .contains(TermMode::ALT_SCREEN)
+ {
+ None
+ } else {
+ Some(0..0)
+ }
}
- fn marked_text_range(&mut self) -> Option<std::ops::Range<usize>> {
+ fn marked_text_range(&mut self, _: &mut WindowContext) -> Option<std::ops::Range<usize>> {
None
}
- fn text_for_range(&mut self, _: std::ops::Range<usize>) -> Option<String> {
+ fn text_for_range(
+ &mut self,
+ _: std::ops::Range<usize>,
+ _: &mut WindowContext,
+ ) -> Option<String> {
None
}
@@ -869,19 +873,16 @@ impl PlatformInputHandler for TerminalInputHandler {
&mut self,
_replacement_range: Option<std::ops::Range<usize>>,
text: &str,
+ cx: &mut WindowContext,
) {
- self.cx
- .update(|_, cx| {
- self.terminal.update(cx, |terminal, _| {
- terminal.input(text.into());
- });
+ self.terminal.update(cx, |terminal, _| {
+ terminal.input(text.into());
+ });
- self.workspace
- .update(cx, |this, cx| {
- let telemetry = this.project().read(cx).client().telemetry().clone();
- telemetry.log_edit_event("terminal");
- })
- .ok();
+ self.workspace
+ .update(cx, |this, cx| {
+ let telemetry = this.project().read(cx).client().telemetry().clone();
+ telemetry.log_edit_event("terminal");
})
.ok();
}
@@ -891,12 +892,17 @@ impl PlatformInputHandler for TerminalInputHandler {
_range_utf16: Option<std::ops::Range<usize>>,
_new_text: &str,
_new_selected_range: Option<std::ops::Range<usize>>,
+ _: &mut WindowContext,
) {
}
- fn unmark_text(&mut self) {}
+ fn unmark_text(&mut self, _: &mut WindowContext) {}
- fn bounds_for_range(&mut self, _range_utf16: std::ops::Range<usize>) -> Option<Bounds<Pixels>> {
+ fn bounds_for_range(
+ &mut self,
+ _range_utf16: std::ops::Range<usize>,
+ _: &mut WindowContext,
+ ) -> Option<Bounds<Pixels>> {
self.cursor_bounds
}
}
@@ -990,11 +996,11 @@ fn to_highlighted_range_lines(
Some((start_y, highlighted_range_lines))
}
-///Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent
+/// Converts a 2, 8, or 24 bit color ANSI color to the GPUI equivalent.
fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme) -> Hsla {
let colors = theme.colors();
match fg {
- //Named and theme defined colors
+ // Named and theme defined colors
terminal::alacritty_terminal::ansi::Color::Named(n) => match n {
NamedColor::Black => colors.terminal_ansi_black,
NamedColor::Red => colors.terminal_ansi_red,
@@ -1015,24 +1021,22 @@ fn convert_color(fg: &terminal::alacritty_terminal::ansi::Color, theme: &Theme)
NamedColor::Foreground => colors.text,
NamedColor::Background => colors.background,
NamedColor::Cursor => theme.players().local().cursor,
-
- // todo!(more colors)
- NamedColor::DimBlack => red(),
- NamedColor::DimRed => red(),
- NamedColor::DimGreen => red(),
- NamedColor::DimYellow => red(),
- NamedColor::DimBlue => red(),
- NamedColor::DimMagenta => red(),
- NamedColor::DimCyan => red(),
- NamedColor::DimWhite => red(),
- NamedColor::BrightForeground => red(),
- NamedColor::DimForeground => red(),
+ NamedColor::DimBlack => colors.terminal_ansi_dim_black,
+ NamedColor::DimRed => colors.terminal_ansi_dim_red,
+ NamedColor::DimGreen => colors.terminal_ansi_dim_green,
+ NamedColor::DimYellow => colors.terminal_ansi_dim_yellow,
+ NamedColor::DimBlue => colors.terminal_ansi_dim_blue,
+ NamedColor::DimMagenta => colors.terminal_ansi_dim_magenta,
+ NamedColor::DimCyan => colors.terminal_ansi_dim_cyan,
+ NamedColor::DimWhite => colors.terminal_ansi_dim_white,
+ NamedColor::BrightForeground => colors.terminal_bright_foreground,
+ NamedColor::DimForeground => colors.terminal_dim_foreground,
},
- //'True' colors
+ // 'True' colors
terminal::alacritty_terminal::ansi::Color::Spec(rgb) => {
terminal::rgba_color(rgb.r, rgb.g, rgb.b)
}
- //8 bit, indexed colors
+ // 8 bit, indexed colors
terminal::alacritty_terminal::ansi::Color::Indexed(i) => {
terminal::get_color_at_index(*i as usize, theme)
}
@@ -772,7 +772,7 @@ impl Item for TerminalView {
.log_err()
.flatten()
.or_else(|| {
- cx.update(|_, cx| {
+ cx.update(|cx| {
let strategy = TerminalSettings::get_global(cx).working_directory.clone();
workspace
.upgrade()
@@ -832,7 +832,7 @@ impl SearchableItem for TerminalView {
self.terminal().update(cx, |term, _| term.matches = matches)
}
- /// Return the selection content to pre-load into this search
+ /// Returns the selection content to pre-load into this search
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
self.terminal()
.read(cx)
@@ -77,6 +77,9 @@ impl ThemeColors {
editor_document_highlight_read_background: neutral().light_alpha().step_3(),
editor_document_highlight_write_background: neutral().light_alpha().step_4(),
terminal_background: neutral().light().step_1(),
+ terminal_foreground: black().light().step_12(),
+ terminal_bright_foreground: black().light().step_11(),
+ terminal_dim_foreground: black().light().step_10(),
terminal_ansi_bright_black: black().light().step_11(),
terminal_ansi_bright_red: red().light().step_10(),
terminal_ansi_bright_green: green().light().step_10(),
@@ -93,6 +96,14 @@ impl ThemeColors {
terminal_ansi_magenta: violet().light().step_11(),
terminal_ansi_cyan: cyan().light().step_11(),
terminal_ansi_white: neutral().light().step_12(),
+ terminal_ansi_dim_black: black().light().step_11(),
+ terminal_ansi_dim_red: red().light().step_10(),
+ terminal_ansi_dim_green: green().light().step_10(),
+ terminal_ansi_dim_yellow: yellow().light().step_10(),
+ terminal_ansi_dim_blue: blue().light().step_10(),
+ terminal_ansi_dim_magenta: violet().light().step_10(),
+ terminal_ansi_dim_cyan: cyan().light().step_10(),
+ terminal_ansi_dim_white: neutral().light().step_11(),
link_text_hover: orange().light().step_10(),
}
}
@@ -160,22 +171,33 @@ impl ThemeColors {
editor_document_highlight_read_background: neutral().dark_alpha().step_4(),
editor_document_highlight_write_background: neutral().dark_alpha().step_4(),
terminal_background: neutral().dark().step_1(),
- terminal_ansi_bright_black: black().dark().step_11(),
- terminal_ansi_bright_red: red().dark().step_10(),
- terminal_ansi_bright_green: green().dark().step_10(),
- terminal_ansi_bright_yellow: yellow().dark().step_10(),
- terminal_ansi_bright_blue: blue().dark().step_10(),
- terminal_ansi_bright_magenta: violet().dark().step_10(),
- terminal_ansi_bright_cyan: cyan().dark().step_10(),
- terminal_ansi_bright_white: neutral().dark().step_11(),
+ terminal_foreground: white().dark().step_12(),
+ terminal_bright_foreground: white().dark().step_11(),
+ terminal_dim_foreground: white().dark().step_10(),
terminal_ansi_black: black().dark().step_12(),
+ terminal_ansi_bright_black: black().dark().step_11(),
+ terminal_ansi_dim_black: black().dark().step_10(),
terminal_ansi_red: red().dark().step_11(),
+ terminal_ansi_bright_red: red().dark().step_10(),
+ terminal_ansi_dim_red: red().dark().step_9(),
terminal_ansi_green: green().dark().step_11(),
+ terminal_ansi_bright_green: green().dark().step_10(),
+ terminal_ansi_dim_green: green().dark().step_9(),
terminal_ansi_yellow: yellow().dark().step_11(),
+ terminal_ansi_bright_yellow: yellow().dark().step_10(),
+ terminal_ansi_dim_yellow: yellow().dark().step_9(),
terminal_ansi_blue: blue().dark().step_11(),
+ terminal_ansi_bright_blue: blue().dark().step_10(),
+ terminal_ansi_dim_blue: blue().dark().step_9(),
terminal_ansi_magenta: violet().dark().step_11(),
+ terminal_ansi_bright_magenta: violet().dark().step_10(),
+ terminal_ansi_dim_magenta: violet().dark().step_9(),
terminal_ansi_cyan: cyan().dark().step_11(),
+ terminal_ansi_bright_cyan: cyan().dark().step_10(),
+ terminal_ansi_dim_cyan: cyan().dark().step_9(),
terminal_ansi_white: neutral().dark().step_12(),
+ terminal_ansi_bright_white: neutral().dark().step_11(),
+ terminal_ansi_dim_white: neutral().dark().step_10(),
link_text_hover: orange().dark().step_10(),
}
}
@@ -101,6 +101,9 @@ pub(crate) fn one_dark() -> Theme {
terminal_background: bg,
// todo!("Use one colors for terminal")
+ terminal_foreground: crate::white().dark().step_12(),
+ terminal_bright_foreground: crate::white().dark().step_11(),
+ terminal_dim_foreground: crate::white().dark().step_10(),
terminal_ansi_black: crate::black().dark().step_12(),
terminal_ansi_red: crate::red().dark().step_11(),
terminal_ansi_green: crate::green().dark().step_11(),
@@ -117,6 +120,14 @@ pub(crate) fn one_dark() -> Theme {
terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
terminal_ansi_bright_white: crate::neutral().dark().step_11(),
+ terminal_ansi_dim_black: crate::black().dark().step_10(),
+ terminal_ansi_dim_red: crate::red().dark().step_9(),
+ terminal_ansi_dim_green: crate::green().dark().step_9(),
+ terminal_ansi_dim_yellow: crate::yellow().dark().step_9(),
+ terminal_ansi_dim_blue: crate::blue().dark().step_9(),
+ terminal_ansi_dim_magenta: crate::violet().dark().step_9(),
+ terminal_ansi_dim_cyan: crate::cyan().dark().step_9(),
+ terminal_ansi_dim_white: crate::neutral().dark().step_10(),
panel_background: bg,
panel_focused_border: blue,
pane_focused_border: blue,
@@ -169,40 +169,63 @@ pub struct ThemeColors {
// ===
// Terminal
// ===
- /// Terminal Background Color
+ /// Terminal background color.
pub terminal_background: Hsla,
- /// Bright Black Color for ANSI Terminal
- pub terminal_ansi_bright_black: Hsla,
- /// Bright Red Color for ANSI Terminal
- pub terminal_ansi_bright_red: Hsla,
- /// Bright Green Color for ANSI Terminal
- pub terminal_ansi_bright_green: Hsla,
- /// Bright Yellow Color for ANSI Terminal
- pub terminal_ansi_bright_yellow: Hsla,
- /// Bright Blue Color for ANSI Terminal
- pub terminal_ansi_bright_blue: Hsla,
- /// Bright Magenta Color for ANSI Terminal
- pub terminal_ansi_bright_magenta: Hsla,
- /// Bright Cyan Color for ANSI Terminal
- pub terminal_ansi_bright_cyan: Hsla,
- /// Bright White Color for ANSI Terminal
- pub terminal_ansi_bright_white: Hsla,
- /// Black Color for ANSI Terminal
+ /// Terminal foreground color.
+ pub terminal_foreground: Hsla,
+ /// Bright terminal foreground color.
+ pub terminal_bright_foreground: Hsla,
+ /// Dim terminal foreground color.
+ pub terminal_dim_foreground: Hsla,
+
+ /// Black ANSI terminal color.
pub terminal_ansi_black: Hsla,
- /// Red Color for ANSI Terminal
+ /// Bright black ANSI terminal color.
+ pub terminal_ansi_bright_black: Hsla,
+ /// Dim black ANSI terminal color.
+ pub terminal_ansi_dim_black: Hsla,
+ /// Red ANSI terminal color.
pub terminal_ansi_red: Hsla,
- /// Green Color for ANSI Terminal
+ /// Bright red ANSI terminal color.
+ pub terminal_ansi_bright_red: Hsla,
+ /// Dim red ANSI terminal color.
+ pub terminal_ansi_dim_red: Hsla,
+ /// Green ANSI terminal color.
pub terminal_ansi_green: Hsla,
- /// Yellow Color for ANSI Terminal
+ /// Bright green ANSI terminal color.
+ pub terminal_ansi_bright_green: Hsla,
+ /// Dim green ANSI terminal color.
+ pub terminal_ansi_dim_green: Hsla,
+ /// Yellow ANSI terminal color.
pub terminal_ansi_yellow: Hsla,
- /// Blue Color for ANSI Terminal
+ /// Bright yellow ANSI terminal color.
+ pub terminal_ansi_bright_yellow: Hsla,
+ /// Dim yellow ANSI terminal color.
+ pub terminal_ansi_dim_yellow: Hsla,
+ /// Blue ANSI terminal color.
pub terminal_ansi_blue: Hsla,
- /// Magenta Color for ANSI Terminal
+ /// Bright blue ANSI terminal color.
+ pub terminal_ansi_bright_blue: Hsla,
+ /// Dim blue ANSI terminal color.
+ pub terminal_ansi_dim_blue: Hsla,
+ /// Magenta ANSI terminal color.
pub terminal_ansi_magenta: Hsla,
- /// Cyan Color for ANSI Terminal
+ /// Bright magenta ANSI terminal color.
+ pub terminal_ansi_bright_magenta: Hsla,
+ /// Dim magenta ANSI terminal color.
+ pub terminal_ansi_dim_magenta: Hsla,
+ /// Cyan ANSI terminal color.
pub terminal_ansi_cyan: Hsla,
- /// White Color for ANSI Terminal
+ /// Bright cyan ANSI terminal color.
+ pub terminal_ansi_bright_cyan: Hsla,
+ /// Dim cyan ANSI terminal color.
+ pub terminal_ansi_dim_cyan: Hsla,
+ /// White ANSI terminal color.
pub terminal_ansi_white: Hsla,
+ /// Bright white ANSI terminal color.
+ pub terminal_ansi_bright_white: Hsla,
+ /// Dim white ANSI terminal color.
+ pub terminal_ansi_dim_white: Hsla,
// ===
// UI/Rich Text
@@ -75,22 +75,33 @@ pub fn andromeda() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x11a7931a).into()),
editor_document_highlight_write_background: Some(rgba(0x64646d66).into()),
terminal_background: Some(rgba(0x1e2025ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x40434cff).into()),
- terminal_ansi_bright_red: Some(rgba(0x8e103aff).into()),
- terminal_ansi_bright_green: Some(rgba(0x457c38ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x958435ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x1b5148ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x682781ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x018169ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf7f7f8ff).into()),
+ terminal_foreground: Some(rgba(0xf7f7f8ff).into()),
+ terminal_bright_foreground: Some(rgba(0xf7f7f8ff).into()),
+ terminal_dim_foreground: Some(rgba(0x1e2025ff).into()),
terminal_ansi_black: Some(rgba(0x1e2025ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x40434cff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf7f7f8ff).into()),
terminal_ansi_red: Some(rgba(0xf82872ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x8e103aff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xffa3b6ff).into()),
terminal_ansi_green: Some(rgba(0x96df72ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x457c38ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xcef0b9ff).into()),
terminal_ansi_yellow: Some(rgba(0xfee56dff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x958435ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xfff2b8ff).into()),
terminal_ansi_blue: Some(rgba(0x11a793ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x1b5148ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x9cd4c8ff).into()),
terminal_ansi_magenta: Some(rgba(0xc74decff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x682781ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe8abf7ff).into()),
terminal_ansi_cyan: Some(rgba(0x09e7c6ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x018169ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xaaf5e2ff).into()),
terminal_ansi_white: Some(rgba(0xf7f7f8ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf7f7f8ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x88868dff).into()),
link_text_hover: Some(rgba(0x11a793ff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x576dda1a).into()),
editor_document_highlight_write_background: Some(rgba(0x726c7a66).into()),
terminal_background: Some(rgba(0x19171cff).into()),
- terminal_ansi_bright_black: Some(rgba(0x635d6bff).into()),
- terminal_ansi_bright_red: Some(rgba(0x5c283cff).into()),
- terminal_ansi_bright_green: Some(rgba(0x1f4747ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x4e3821ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x2d376fff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x60255bff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x26445eff).into()),
- terminal_ansi_bright_white: Some(rgba(0xefecf4ff).into()),
+ terminal_foreground: Some(rgba(0xefecf4ff).into()),
+ terminal_bright_foreground: Some(rgba(0xefecf4ff).into()),
+ terminal_dim_foreground: Some(rgba(0x19171cff).into()),
terminal_ansi_black: Some(rgba(0x19171cff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x635d6bff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xefecf4ff).into()),
terminal_ansi_red: Some(rgba(0xbe4678ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x5c283cff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xe3a4b9ff).into()),
terminal_ansi_green: Some(rgba(0x2c9292ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x1f4747ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x9dc8c8ff).into()),
terminal_ansi_yellow: Some(rgba(0xa06e3bff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x4e3821ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd4b499ff).into()),
terminal_ansi_blue: Some(rgba(0x576ddaff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x2d376fff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb3b3eeff).into()),
terminal_ansi_magenta: Some(rgba(0xbf41bfff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x60255bff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe3a4dfff).into()),
terminal_ansi_cyan: Some(rgba(0x3a8bc6ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x26445eff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xa6c4e3ff).into()),
terminal_ansi_white: Some(rgba(0xefecf4ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xefecf4ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x807b89ff).into()),
link_text_hover: Some(rgba(0x576ddaff).into()),
..Default::default()
},
@@ -541,22 +552,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x586dda1a).into()),
editor_document_highlight_write_background: Some(rgba(0x726c7a66).into()),
terminal_background: Some(rgba(0xefecf4ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x807b89ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xe3a4b9ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x9dc8c8ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xd4b499ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xb3b3eeff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xe3a4dfff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xa6c4e3ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x19171cff).into()),
+ terminal_foreground: Some(rgba(0x19171cff).into()),
+ terminal_bright_foreground: Some(rgba(0x19171cff).into()),
+ terminal_dim_foreground: Some(rgba(0xefecf4ff).into()),
terminal_ansi_black: Some(rgba(0xefecf4ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x807b89ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x19171cff).into()),
terminal_ansi_red: Some(rgba(0xbe4778ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xe3a4b9ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x5c283cff).into()),
terminal_ansi_green: Some(rgba(0x2c9292ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x9dc8c8ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x1f4747ff).into()),
terminal_ansi_yellow: Some(rgba(0xa06e3cff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xd4b499ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x4e3821ff).into()),
terminal_ansi_blue: Some(rgba(0x586ddaff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xb3b3eeff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x2d376fff).into()),
terminal_ansi_magenta: Some(rgba(0xbf41bfff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xe3a4dfff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x60255bff).into()),
terminal_ansi_cyan: Some(rgba(0x3b8bc6ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xa6c4e3ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x26445eff).into()),
terminal_ansi_white: Some(rgba(0x19171cff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x19171cff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x635d6bff).into()),
link_text_hover: Some(rgba(0x586ddaff).into()),
..Default::default()
},
@@ -1006,22 +1028,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x6684e01a).into()),
editor_document_highlight_write_background: Some(rgba(0x8b887466).into()),
terminal_background: Some(rgba(0x20201dff).into()),
- terminal_ansi_bright_black: Some(rgba(0x7a7766ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x781c1fff).into()),
- terminal_ansi_bright_green: Some(rgba(0x335322ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x574815ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x334173ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x721d2bff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x1e5341ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfefbecff).into()),
+ terminal_foreground: Some(rgba(0xfefbecff).into()),
+ terminal_bright_foreground: Some(rgba(0xfefbecff).into()),
+ terminal_dim_foreground: Some(rgba(0x20201dff).into()),
terminal_ansi_black: Some(rgba(0x20201dff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x7a7766ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfefbecff).into()),
terminal_ansi_red: Some(rgba(0xd73837ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x781c1fff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xf7a195ff).into()),
terminal_ansi_green: Some(rgba(0x60ac3aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x335322ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xb3d69cff).into()),
terminal_ansi_yellow: Some(rgba(0xae9515ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x574815ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xdcc98eff).into()),
terminal_ansi_blue: Some(rgba(0x6684e0ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x334173ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb8c0f1ff).into()),
terminal_ansi_magenta: Some(rgba(0xd43652ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x721d2bff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xf3a0a4ff).into()),
terminal_ansi_cyan: Some(rgba(0x21ad83ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x1e5341ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9ed7c0ff).into()),
terminal_ansi_white: Some(rgba(0xfefbecff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfefbecff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x9b9782ff).into()),
link_text_hover: Some(rgba(0x6684e0ff).into()),
..Default::default()
},
@@ -1471,22 +1504,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x6784e01a).into()),
editor_document_highlight_write_background: Some(rgba(0x8b887466).into()),
terminal_background: Some(rgba(0xfefbecff).into()),
- terminal_ansi_bright_black: Some(rgba(0x9b9782ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xf7a195ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xb3d69cff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xdcc98eff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xb8c0f1ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xf3a0a4ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9ed7c0ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x20201dff).into()),
+ terminal_foreground: Some(rgba(0x20201dff).into()),
+ terminal_bright_foreground: Some(rgba(0x20201dff).into()),
+ terminal_dim_foreground: Some(rgba(0xfefbecff).into()),
terminal_ansi_black: Some(rgba(0xfefbecff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x9b9782ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x20201dff).into()),
terminal_ansi_red: Some(rgba(0xd73838ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xf7a195ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x781c1fff).into()),
terminal_ansi_green: Some(rgba(0x61ac3aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xb3d69cff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x335322ff).into()),
terminal_ansi_yellow: Some(rgba(0xae9515ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xdcc98eff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x574815ff).into()),
terminal_ansi_blue: Some(rgba(0x6784e0ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xb8c0f1ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x334173ff).into()),
terminal_ansi_magenta: Some(rgba(0xd43753ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xf3a0a4ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x721d2bff).into()),
terminal_ansi_cyan: Some(rgba(0x22ad83ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9ed7c0ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1e5341ff).into()),
terminal_ansi_white: Some(rgba(0x20201dff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x20201dff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x7a7766ff).into()),
link_text_hover: Some(rgba(0x6784e0ff).into()),
..Default::default()
},
@@ -1936,22 +1980,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x37a1661a).into()),
editor_document_highlight_write_background: Some(rgba(0x7a786766).into()),
terminal_background: Some(rgba(0x22221bff).into()),
- terminal_ansi_bright_black: Some(rgba(0x6a6958ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x5c331fff).into()),
- terminal_ansi_bright_green: Some(rgba(0x3f491aff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x514a14ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x234e34ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x4c373eff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x314c27ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf4f3ecff).into()),
+ terminal_foreground: Some(rgba(0xf4f3ecff).into()),
+ terminal_bright_foreground: Some(rgba(0xf4f3ecff).into()),
+ terminal_dim_foreground: Some(rgba(0x22221bff).into()),
terminal_ansi_black: Some(rgba(0x22221bff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x6a6958ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf4f3ecff).into()),
terminal_ansi_red: Some(rgba(0xba6237ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x5c331fff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xe4af96ff).into()),
terminal_ansi_green: Some(rgba(0x7d9727ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x3f491aff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xc0ca93ff).into()),
terminal_ansi_yellow: Some(rgba(0xa59810ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x514a14ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd7ca8dff).into()),
terminal_ansi_blue: Some(rgba(0x37a166ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x234e34ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xa0d1b0ff).into()),
terminal_ansi_magenta: Some(rgba(0x9d6c7cff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x4c373eff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xcfb4bcff).into()),
terminal_ansi_cyan: Some(rgba(0x5b9d48ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x314c27ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xaecea1ff).into()),
terminal_ansi_white: Some(rgba(0xf4f3ecff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf4f3ecff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x898775ff).into()),
link_text_hover: Some(rgba(0x37a166ff).into()),
..Default::default()
},
@@ -2401,22 +2456,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x38a1661a).into()),
editor_document_highlight_write_background: Some(rgba(0x7a786766).into()),
terminal_background: Some(rgba(0xf4f3ecff).into()),
- terminal_ansi_bright_black: Some(rgba(0x898775ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xe4af96ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xc0ca93ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xd7ca8dff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xa0d1b0ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xcfb4bcff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xaecea1ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x22221bff).into()),
+ terminal_foreground: Some(rgba(0x22221bff).into()),
+ terminal_bright_foreground: Some(rgba(0x22221bff).into()),
+ terminal_dim_foreground: Some(rgba(0xf4f3ecff).into()),
terminal_ansi_black: Some(rgba(0xf4f3ecff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x898775ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x22221bff).into()),
terminal_ansi_red: Some(rgba(0xba6337ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xe4af96ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x5c331fff).into()),
terminal_ansi_green: Some(rgba(0x7d9728ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xc0ca93ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x3f491aff).into()),
terminal_ansi_yellow: Some(rgba(0xa59810ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xd7ca8dff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x514a14ff).into()),
terminal_ansi_blue: Some(rgba(0x38a166ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xa0d1b0ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x234e34ff).into()),
terminal_ansi_magenta: Some(rgba(0x9d6c7cff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xcfb4bcff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x4c373eff).into()),
terminal_ansi_cyan: Some(rgba(0x5c9d49ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xaecea1ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x314c27ff).into()),
terminal_ansi_white: Some(rgba(0x22221bff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x22221bff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x6a6958ff).into()),
link_text_hover: Some(rgba(0x38a166ff).into()),
..Default::default()
},
@@ -2866,22 +2932,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()),
editor_document_highlight_write_background: Some(rgba(0x89817e66).into()),
terminal_background: Some(rgba(0x1b1918ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x746c69ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x8c1223ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x3e491aff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x674115ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x213f78ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x662186ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x264958ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf1efeeff).into()),
+ terminal_foreground: Some(rgba(0xf1efeeff).into()),
+ terminal_bright_foreground: Some(rgba(0xf1efeeff).into()),
+ terminal_dim_foreground: Some(rgba(0x1b1918ff).into()),
terminal_ansi_black: Some(rgba(0x1b1918ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x746c69ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf1efeeff).into()),
terminal_ansi_red: Some(rgba(0xf22d40ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x8c1223ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xffa29aff).into()),
terminal_ansi_green: Some(rgba(0x7b9727ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x3e491aff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xbfca93ff).into()),
terminal_ansi_yellow: Some(rgba(0xc38419ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x674115ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xe9c08eff).into()),
terminal_ansi_blue: Some(rgba(0x417ee6ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x213f78ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xaebcf4ff).into()),
terminal_ansi_magenta: Some(rgba(0xc340f2ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x662186ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe7a6fbff).into()),
terminal_ansi_cyan: Some(rgba(0x3e97b8ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x264958ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xa6cadbff).into()),
terminal_ansi_white: Some(rgba(0xf1efeeff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf1efeeff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x9e9693ff).into()),
link_text_hover: Some(rgba(0x417ee6ff).into()),
..Default::default()
},
@@ -3331,22 +3408,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()),
editor_document_highlight_write_background: Some(rgba(0x89817e66).into()),
terminal_background: Some(rgba(0xf1efeeff).into()),
- terminal_ansi_bright_black: Some(rgba(0x9e9693ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xffa29aff).into()),
- terminal_ansi_bright_green: Some(rgba(0xbfca93ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe9c08eff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xaebcf4ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xe7a6fbff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xa6cadbff).into()),
- terminal_ansi_bright_white: Some(rgba(0x1b1918ff).into()),
+ terminal_foreground: Some(rgba(0x1b1918ff).into()),
+ terminal_bright_foreground: Some(rgba(0x1b1918ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf1efeeff).into()),
terminal_ansi_black: Some(rgba(0xf1efeeff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x9e9693ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x1b1918ff).into()),
terminal_ansi_red: Some(rgba(0xf22e41ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xffa29aff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x8c1223ff).into()),
terminal_ansi_green: Some(rgba(0x7b9728ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xbfca93ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x3e491aff).into()),
terminal_ansi_yellow: Some(rgba(0xc3841aff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe9c08eff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x674115ff).into()),
terminal_ansi_blue: Some(rgba(0x417ee6ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xaebcf4ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x213f78ff).into()),
terminal_ansi_magenta: Some(rgba(0xc340f2ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xe7a6fbff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x662186ff).into()),
terminal_ansi_cyan: Some(rgba(0x3f97b8ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xa6cadbff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x264958ff).into()),
terminal_ansi_white: Some(rgba(0x1b1918ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x1b1918ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x746c69ff).into()),
link_text_hover: Some(rgba(0x417ee6ff).into()),
..Default::default()
},
@@ -3796,22 +3884,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()),
editor_document_highlight_write_background: Some(rgba(0x8b7c8b66).into()),
terminal_background: Some(rgba(0x1b181bff).into()),
- terminal_ansi_bright_black: Some(rgba(0x756775ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x6d221aff).into()),
- terminal_ansi_bright_green: Some(rgba(0x474422ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x5e441fff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x26367eff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x6c1e67ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x1a4848ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf7f3f7ff).into()),
+ terminal_foreground: Some(rgba(0xf7f3f7ff).into()),
+ terminal_bright_foreground: Some(rgba(0xf7f3f7ff).into()),
+ terminal_dim_foreground: Some(rgba(0x1b181bff).into()),
terminal_ansi_black: Some(rgba(0x1b181bff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x756775ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf7f3f7ff).into()),
terminal_ansi_red: Some(rgba(0xca402cff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x6d221aff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xf0a28fff).into()),
terminal_ansi_green: Some(rgba(0x918b3bff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x474422ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xcac49aff).into()),
terminal_ansi_yellow: Some(rgba(0xbb8a36ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x5e441fff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xe2c398ff).into()),
terminal_ansi_blue: Some(rgba(0x526aebff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x26367eff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb4b2f7ff).into()),
terminal_ansi_magenta: Some(rgba(0xcc34ccff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x6c1e67ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xeba2e6ff).into()),
terminal_ansi_cyan: Some(rgba(0x189393ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x1a4848ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9ac9c8ff).into()),
terminal_ansi_white: Some(rgba(0xf7f3f7ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf7f3f7ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0xa091a0ff).into()),
link_text_hover: Some(rgba(0x526aebff).into()),
..Default::default()
},
@@ -4261,22 +4360,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()),
editor_document_highlight_write_background: Some(rgba(0x8b7c8b66).into()),
terminal_background: Some(rgba(0xf7f3f7ff).into()),
- terminal_ansi_bright_black: Some(rgba(0xa091a0ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xf0a28fff).into()),
- terminal_ansi_bright_green: Some(rgba(0xcac49aff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe2c398ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xb4b2f7ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xeba2e6ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9ac9c8ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x1b181bff).into()),
+ terminal_foreground: Some(rgba(0x1b181bff).into()),
+ terminal_bright_foreground: Some(rgba(0x1b181bff).into()),
+ terminal_dim_foreground: Some(rgba(0xf7f3f7ff).into()),
terminal_ansi_black: Some(rgba(0xf7f3f7ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xa091a0ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x1b181bff).into()),
terminal_ansi_red: Some(rgba(0xca412cff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xf0a28fff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x6d221aff).into()),
terminal_ansi_green: Some(rgba(0x918b3cff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xcac49aff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x474422ff).into()),
terminal_ansi_yellow: Some(rgba(0xbb8a36ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe2c398ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x5e441fff).into()),
terminal_ansi_blue: Some(rgba(0x526aebff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xb4b2f7ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x26367eff).into()),
terminal_ansi_magenta: Some(rgba(0xcc35ccff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xeba2e6ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x6c1e67ff).into()),
terminal_ansi_cyan: Some(rgba(0x199393ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9ac9c8ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1a4848ff).into()),
terminal_ansi_white: Some(rgba(0x1b181bff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x1b181bff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x756775ff).into()),
link_text_hover: Some(rgba(0x526aebff).into()),
..Default::default()
},
@@ -4726,22 +4836,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()),
editor_document_highlight_write_background: Some(rgba(0x66889a66).into()),
terminal_background: Some(rgba(0x161b1dff).into()),
- terminal_ansi_bright_black: Some(rgba(0x587989ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x6f1c3aff).into()),
- terminal_ansi_bright_green: Some(rgba(0x2e4522ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x454413ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x1e3f53ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x5c1e6bff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x1f4638ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xebf8ffff).into()),
+ terminal_foreground: Some(rgba(0xebf8ffff).into()),
+ terminal_bright_foreground: Some(rgba(0xebf8ffff).into()),
+ terminal_dim_foreground: Some(rgba(0x161b1dff).into()),
terminal_ansi_black: Some(rgba(0x161b1dff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x587989ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xebf8ffff).into()),
terminal_ansi_red: Some(rgba(0xd22e72ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x6f1c3aff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xf09fb6ff).into()),
terminal_ansi_green: Some(rgba(0x568c3bff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x2e4522ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xabc59aff).into()),
terminal_ansi_yellow: Some(rgba(0x8a8a11ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x454413ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xc8c38bff).into()),
terminal_ansi_blue: Some(rgba(0x277fadff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x1e3f53ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x9ebdd6ff).into()),
terminal_ansi_magenta: Some(rgba(0xb72ed2ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x5c1e6bff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe09fe9ff).into()),
terminal_ansi_cyan: Some(rgba(0x2e8f6fff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x1f4638ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9bc7b5ff).into()),
terminal_ansi_white: Some(rgba(0xebf8ffff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xebf8ffff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x7397aaff).into()),
link_text_hover: Some(rgba(0x277fadff).into()),
..Default::default()
},
@@ -5191,22 +5312,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()),
editor_document_highlight_write_background: Some(rgba(0x66889a66).into()),
terminal_background: Some(rgba(0xebf8ffff).into()),
- terminal_ansi_bright_black: Some(rgba(0x7397aaff).into()),
- terminal_ansi_bright_red: Some(rgba(0xf09fb6ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xabc59aff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xc8c38bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x9ebdd6ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xe09fe9ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9bc7b5ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x161b1dff).into()),
+ terminal_foreground: Some(rgba(0x161b1dff).into()),
+ terminal_bright_foreground: Some(rgba(0x161b1dff).into()),
+ terminal_dim_foreground: Some(rgba(0xebf8ffff).into()),
terminal_ansi_black: Some(rgba(0xebf8ffff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x7397aaff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x161b1dff).into()),
terminal_ansi_red: Some(rgba(0xd22f72ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xf09fb6ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x6f1c3aff).into()),
terminal_ansi_green: Some(rgba(0x578c3cff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xabc59aff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x2e4522ff).into()),
terminal_ansi_yellow: Some(rgba(0x8a8a11ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xc8c38bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x454413ff).into()),
terminal_ansi_blue: Some(rgba(0x277fadff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x9ebdd6ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x1e3f53ff).into()),
terminal_ansi_magenta: Some(rgba(0xb72fd2ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xe09fe9ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x5c1e6bff).into()),
terminal_ansi_cyan: Some(rgba(0x2f8f6fff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9bc7b5ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1f4638ff).into()),
terminal_ansi_white: Some(rgba(0x161b1dff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x161b1dff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x587989ff).into()),
link_text_hover: Some(rgba(0x277fadff).into()),
..Default::default()
},
@@ -5656,22 +5788,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x7272ca1a).into()),
editor_document_highlight_write_background: Some(rgba(0x726a6a66).into()),
terminal_background: Some(rgba(0x1b1818ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x635b5bff).into()),
- terminal_ansi_bright_red: Some(rgba(0x692727ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x2a4444ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x4e3821ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x3b3960ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x5b2c42ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x2e4257ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf4ececff).into()),
+ terminal_foreground: Some(rgba(0xf4ececff).into()),
+ terminal_bright_foreground: Some(rgba(0xf4ececff).into()),
+ terminal_dim_foreground: Some(rgba(0x1b1818ff).into()),
terminal_ansi_black: Some(rgba(0x1b1818ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x635b5bff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf4ececff).into()),
terminal_ansi_red: Some(rgba(0xca4949ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x692727ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xeda69fff).into()),
terminal_ansi_green: Some(rgba(0x4b8b8bff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x2a4444ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xa6c4c4ff).into()),
terminal_ansi_yellow: Some(rgba(0xa06e3bff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x4e3821ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd4b499ff).into()),
terminal_ansi_blue: Some(rgba(0x7272caff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x3b3960ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xbbb6e5ff).into()),
terminal_ansi_magenta: Some(rgba(0xbd5187ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x5b2c42ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe2a9c2ff).into()),
terminal_ansi_cyan: Some(rgba(0x5485b6ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x2e4257ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xacc0daff).into()),
terminal_ansi_white: Some(rgba(0xf4ececff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf4ececff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x807979ff).into()),
link_text_hover: Some(rgba(0x7272caff).into()),
..Default::default()
},
@@ -6121,22 +6264,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x7372ca1a).into()),
editor_document_highlight_write_background: Some(rgba(0x726a6a66).into()),
terminal_background: Some(rgba(0xf4ececff).into()),
- terminal_ansi_bright_black: Some(rgba(0x807979ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xeda69fff).into()),
- terminal_ansi_bright_green: Some(rgba(0xa6c4c4ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xd4b499ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xbbb6e5ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xe2a9c2ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xacc0daff).into()),
- terminal_ansi_bright_white: Some(rgba(0x1b1818ff).into()),
+ terminal_foreground: Some(rgba(0x1b1818ff).into()),
+ terminal_bright_foreground: Some(rgba(0x1b1818ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf4ececff).into()),
terminal_ansi_black: Some(rgba(0xf4ececff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x807979ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x1b1818ff).into()),
terminal_ansi_red: Some(rgba(0xca4a4aff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xeda69fff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x692727ff).into()),
terminal_ansi_green: Some(rgba(0x4c8b8bff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xa6c4c4ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x2a4444ff).into()),
terminal_ansi_yellow: Some(rgba(0xa06e3cff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xd4b499ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x4e3821ff).into()),
terminal_ansi_blue: Some(rgba(0x7372caff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xbbb6e5ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x3b3960ff).into()),
terminal_ansi_magenta: Some(rgba(0xbd5287ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xe2a9c2ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x5b2c42ff).into()),
terminal_ansi_cyan: Some(rgba(0x5585b6ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xacc0daff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x2e4257ff).into()),
terminal_ansi_white: Some(rgba(0x1b1818ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x1b1818ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x635b5bff).into()),
link_text_hover: Some(rgba(0x7372caff).into()),
..Default::default()
},
@@ -6586,22 +6740,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x478c901a).into()),
editor_document_highlight_write_background: Some(rgba(0x6c7a7166).into()),
terminal_background: Some(rgba(0x171c19ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x5d6b62ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x563220ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x294a33ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x4e3f22ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x284546ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x423a36ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x1d4b4dff).into()),
- terminal_ansi_bright_white: Some(rgba(0xecf4eeff).into()),
+ terminal_foreground: Some(rgba(0xecf4eeff).into()),
+ terminal_bright_foreground: Some(rgba(0xecf4eeff).into()),
+ terminal_dim_foreground: Some(rgba(0x171c19ff).into()),
terminal_ansi_black: Some(rgba(0x171c19ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x5d6b62ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xecf4eeff).into()),
terminal_ansi_red: Some(rgba(0xb16139ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x563220ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xdeae97ff).into()),
terminal_ansi_green: Some(rgba(0x489963ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x294a33ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xa5ccafff).into()),
terminal_ansi_yellow: Some(rgba(0xa07e3bff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x4e3f22ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd3bd9aff).into()),
terminal_ansi_blue: Some(rgba(0x478c90ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x284546ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xa5c5c6ff).into()),
terminal_ansi_magenta: Some(rgba(0x867469ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x423a36ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xc2b7b1ff).into()),
terminal_ansi_cyan: Some(rgba(0x1e9aa0ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x1d4b4dff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9dcdcfff).into()),
terminal_ansi_white: Some(rgba(0xecf4eeff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xecf4eeff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x7b897fff).into()),
link_text_hover: Some(rgba(0x478c90ff).into()),
..Default::default()
},
@@ -7051,22 +7216,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x488c901a).into()),
editor_document_highlight_write_background: Some(rgba(0x6c7a7166).into()),
terminal_background: Some(rgba(0xecf4eeff).into()),
- terminal_ansi_bright_black: Some(rgba(0x7b897fff).into()),
- terminal_ansi_bright_red: Some(rgba(0xdeae97ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xa5ccafff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xd3bd9aff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xa5c5c6ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xc2b7b1ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9dcdcfff).into()),
- terminal_ansi_bright_white: Some(rgba(0x171c19ff).into()),
+ terminal_foreground: Some(rgba(0x171c19ff).into()),
+ terminal_bright_foreground: Some(rgba(0x171c19ff).into()),
+ terminal_dim_foreground: Some(rgba(0xecf4eeff).into()),
terminal_ansi_black: Some(rgba(0xecf4eeff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x7b897fff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x171c19ff).into()),
terminal_ansi_red: Some(rgba(0xb1623aff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xdeae97ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x563220ff).into()),
terminal_ansi_green: Some(rgba(0x499963ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xa5ccafff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x294a33ff).into()),
terminal_ansi_yellow: Some(rgba(0xa07e3cff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xd3bd9aff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x4e3f22ff).into()),
terminal_ansi_blue: Some(rgba(0x488c90ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xa5c5c6ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x284546ff).into()),
terminal_ansi_magenta: Some(rgba(0x867469ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xc2b7b1ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x423a36ff).into()),
terminal_ansi_cyan: Some(rgba(0x1f9aa0ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9dcdcfff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1d4b4dff).into()),
terminal_ansi_white: Some(rgba(0x171c19ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x171c19ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x5d6b62ff).into()),
link_text_hover: Some(rgba(0x488c90ff).into()),
..Default::default()
},
@@ -7516,22 +7692,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x3e62f41a).into()),
editor_document_highlight_write_background: Some(rgba(0x748b7466).into()),
terminal_background: Some(rgba(0x131513ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x667a66ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x840b21ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x204f1bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x4b4a17ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x193385ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x810e60ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x1d4a56ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf4fbf4ff).into()),
+ terminal_foreground: Some(rgba(0xf4fbf4ff).into()),
+ terminal_bright_foreground: Some(rgba(0xf4fbf4ff).into()),
+ terminal_dim_foreground: Some(rgba(0x131513ff).into()),
terminal_ansi_black: Some(rgba(0x131513ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x667a66ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf4fbf4ff).into()),
terminal_ansi_red: Some(rgba(0xe61c3cff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x840b21ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xff9d98ff).into()),
terminal_ansi_green: Some(rgba(0x2ba32aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x204f1bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xa0d294ff).into()),
terminal_ansi_yellow: Some(rgba(0x98981cff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x4b4a17ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd0ca90ff).into()),
terminal_ansi_blue: Some(rgba(0x3e62f4ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x193385ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb1adfcff).into()),
terminal_ansi_magenta: Some(rgba(0xe61cc3ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x810e60ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xf9a1e1ff).into()),
terminal_ansi_cyan: Some(rgba(0x1c99b3ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x1d4a56ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9fccd9ff).into()),
terminal_ansi_white: Some(rgba(0xf4fbf4ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf4fbf4ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x829b82ff).into()),
link_text_hover: Some(rgba(0x3e62f4ff).into()),
..Default::default()
},
@@ -7981,22 +8168,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x3f62f41a).into()),
editor_document_highlight_write_background: Some(rgba(0x748b7466).into()),
terminal_background: Some(rgba(0xf4fbf4ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x829b82ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xff9d98ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xa0d294ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xd0ca90ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xb1adfcff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xf9a1e1ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9fccd9ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x131513ff).into()),
+ terminal_foreground: Some(rgba(0x131513ff).into()),
+ terminal_bright_foreground: Some(rgba(0x131513ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf4fbf4ff).into()),
terminal_ansi_black: Some(rgba(0xf4fbf4ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x829b82ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x131513ff).into()),
terminal_ansi_red: Some(rgba(0xe61c3dff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xff9d98ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x840b21ff).into()),
terminal_ansi_green: Some(rgba(0x2ba32bff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xa0d294ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x204f1bff).into()),
terminal_ansi_yellow: Some(rgba(0x98981dff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xd0ca90ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x4b4a17ff).into()),
terminal_ansi_blue: Some(rgba(0x3f62f4ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xb1adfcff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x193385ff).into()),
terminal_ansi_magenta: Some(rgba(0xe61dc3ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xf9a1e1ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x810e60ff).into()),
terminal_ansi_cyan: Some(rgba(0x1d99b3ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9fccd9ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1d4a56ff).into()),
terminal_ansi_white: Some(rgba(0x131513ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x131513ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x667a66ff).into()),
link_text_hover: Some(rgba(0x3f62f4ff).into()),
..Default::default()
},
@@ -8446,22 +8644,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x3e8fd01a).into()),
editor_document_highlight_write_background: Some(rgba(0x7a819c66).into()),
terminal_background: Some(rgba(0x202746ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x697192ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x6d2616ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x534921ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x63441eff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x274664ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x4c333dff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x214e5fff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf5f7ffff).into()),
+ terminal_foreground: Some(rgba(0xf5f7ffff).into()),
+ terminal_bright_foreground: Some(rgba(0xf5f7ffff).into()),
+ terminal_dim_foreground: Some(rgba(0x202746ff).into()),
terminal_ansi_black: Some(rgba(0x202746ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x697192ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf5f7ffff).into()),
terminal_ansi_red: Some(rgba(0xc94923ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x6d2616ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xefa58cff).into()),
terminal_ansi_green: Some(rgba(0xac973aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x534921ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xd9ca9bff).into()),
terminal_ansi_yellow: Some(rgba(0xc08b31ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x63441eff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xe5c497ff).into()),
terminal_ansi_blue: Some(rgba(0x3e8fd0ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x274664ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xa9c6e8ff).into()),
terminal_ansi_magenta: Some(rgba(0x9c637aff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x4c333dff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xcfafbbff).into()),
terminal_ansi_cyan: Some(rgba(0x25a2c9ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x214e5fff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xa4d0e4ff).into()),
terminal_ansi_white: Some(rgba(0xf5f7ffff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf5f7ffff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x8b91a7ff).into()),
link_text_hover: Some(rgba(0x3e8fd0ff).into()),
..Default::default()
},
@@ -8911,22 +9120,33 @@ pub fn atelier() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x3f8fd01a).into()),
editor_document_highlight_write_background: Some(rgba(0x7a819c66).into()),
terminal_background: Some(rgba(0xf5f7ffff).into()),
- terminal_ansi_bright_black: Some(rgba(0x8b91a7ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xefa58cff).into()),
- terminal_ansi_bright_green: Some(rgba(0xd9ca9bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe5c497ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xa9c6e8ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xcfafbbff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xa4d0e4ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x202746ff).into()),
+ terminal_foreground: Some(rgba(0x202746ff).into()),
+ terminal_bright_foreground: Some(rgba(0x202746ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf5f7ffff).into()),
terminal_ansi_black: Some(rgba(0xf5f7ffff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x8b91a7ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x202746ff).into()),
terminal_ansi_red: Some(rgba(0xc94a23ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xefa58cff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x6d2616ff).into()),
terminal_ansi_green: Some(rgba(0xac973aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xd9ca9bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x534921ff).into()),
terminal_ansi_yellow: Some(rgba(0xc08b31ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe5c497ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x63441eff).into()),
terminal_ansi_blue: Some(rgba(0x3f8fd0ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xa9c6e8ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x274664ff).into()),
terminal_ansi_magenta: Some(rgba(0x9c637aff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xcfafbbff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x4c333dff).into()),
terminal_ansi_cyan: Some(rgba(0x25a2c9ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xa4d0e4ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x214e5fff).into()),
terminal_ansi_white: Some(rgba(0x202746ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x202746ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x697192ff).into()),
link_text_hover: Some(rgba(0x3f8fd0ff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn ayu() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x5ac2fe1a).into()),
editor_document_highlight_write_background: Some(rgba(0x66676766).into()),
terminal_background: Some(rgba(0x0d1017ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x545557ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x83363cff).into()),
- terminal_ansi_bright_green: Some(rgba(0x567627ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x92592cff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x28628cff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x205b78ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x4c806fff).into()),
- terminal_ansi_bright_white: Some(rgba(0xbfbdb6ff).into()),
+ terminal_foreground: Some(rgba(0xbfbdb6ff).into()),
+ terminal_bright_foreground: Some(rgba(0xbfbdb6ff).into()),
+ terminal_dim_foreground: Some(rgba(0x0d1017ff).into()),
terminal_ansi_black: Some(rgba(0x0d1017ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x545557ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xbfbdb6ff).into()),
terminal_ansi_red: Some(rgba(0xef7178ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x83363cff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfebab9ff).into()),
terminal_ansi_green: Some(rgba(0xaad84cff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x567627ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xd8eca8ff).into()),
terminal_ansi_yellow: Some(rgba(0xfeb454ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x92592cff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffd9aaff).into()),
terminal_ansi_blue: Some(rgba(0x5ac2feff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x28628cff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb8e0ffff).into()),
terminal_ansi_magenta: Some(rgba(0x3abae5ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x205b78ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xaddcf3ff).into()),
terminal_ansi_cyan: Some(rgba(0x95e5cbff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x4c806fff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xccf3e5ff).into()),
terminal_ansi_white: Some(rgba(0xbfbdb6ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xbfbdb6ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x787876ff).into()),
link_text_hover: Some(rgba(0x5ac2feff).into()),
..Default::default()
},
@@ -520,22 +531,33 @@ pub fn ayu() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x3b9ee51a).into()),
editor_document_highlight_write_background: Some(rgba(0xacafb166).into()),
terminal_background: Some(rgba(0xfcfcfcff).into()),
- terminal_ansi_bright_black: Some(rgba(0xbcbec0ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xfebab6ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xc7d98fff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xffd6a4ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xaccef3ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xb2d9e9ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xace0cbff).into()),
- terminal_ansi_bright_white: Some(rgba(0x5c6166ff).into()),
+ terminal_foreground: Some(rgba(0x5c6166ff).into()),
+ terminal_bright_foreground: Some(rgba(0x5c6166ff).into()),
+ terminal_dim_foreground: Some(rgba(0xfcfcfcff).into()),
terminal_ansi_black: Some(rgba(0xfcfcfcff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xbcbec0ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x5c6166ff).into()),
terminal_ansi_red: Some(rgba(0xef7271ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xfebab6ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x833639ff).into()),
terminal_ansi_green: Some(rgba(0x86b305ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xc7d98fff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x445614ff).into()),
terminal_ansi_yellow: Some(rgba(0xf1ae4aff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xffd6a4ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x8a5328ff).into()),
terminal_ansi_blue: Some(rgba(0x3b9ee5ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xaccef3ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x214d76ff).into()),
terminal_ansi_magenta: Some(rgba(0x56b4d3ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xb2d9e9ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x2f5669ff).into()),
terminal_ansi_cyan: Some(rgba(0x4dbf99ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xace0cbff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x2a5f4aff).into()),
terminal_ansi_white: Some(rgba(0x5c6166ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x5c6166ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x9c9fa2ff).into()),
link_text_hover: Some(rgba(0x3b9ee5ff).into()),
..Default::default()
},
@@ -964,22 +986,33 @@ pub fn ayu() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x73cffe1a).into()),
editor_document_highlight_write_background: Some(rgba(0x787a7c66).into()),
terminal_background: Some(rgba(0x242936ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x67696eff).into()),
- terminal_ansi_bright_red: Some(rgba(0x83403dff).into()),
- terminal_ansi_bright_green: Some(rgba(0x76993dff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x937238ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x346e8dff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x2b6c7bff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x4c806fff).into()),
- terminal_ansi_bright_white: Some(rgba(0xcccac2ff).into()),
+ terminal_foreground: Some(rgba(0xcccac2ff).into()),
+ terminal_bright_foreground: Some(rgba(0xcccac2ff).into()),
+ terminal_dim_foreground: Some(rgba(0x242936ff).into()),
terminal_ansi_black: Some(rgba(0x242936ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x67696eff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xcccac2ff).into()),
terminal_ansi_red: Some(rgba(0xf18779ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x83403dff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfec4baff).into()),
terminal_ansi_green: Some(rgba(0xd5fe80ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x76993dff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xecffc1ff).into()),
terminal_ansi_yellow: Some(rgba(0xfed073ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x937238ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffe7b9ff).into()),
terminal_ansi_blue: Some(rgba(0x73cffeff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x346e8dff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xc1e7ffff).into()),
terminal_ansi_magenta: Some(rgba(0x5ccee5ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x2b6c7bff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xb7e7f2ff).into()),
terminal_ansi_cyan: Some(rgba(0x95e5cbff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x4c806fff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xccf3e5ff).into()),
terminal_ansi_white: Some(rgba(0xcccac2ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xcccac2ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x898a8aff).into()),
link_text_hover: Some(rgba(0x73cffeff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0x282828ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
- terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
- terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_bright_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_dim_foreground: Some(rgba(0x282828ff).into()),
terminal_ansi_black: Some(rgba(0x282828ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfbf1c7ff).into()),
terminal_ansi_red: Some(rgba(0xfb4a35ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xffaa95ff).into()),
terminal_ansi_green: Some(rgba(0xb8bb27ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xe0dc98ff).into()),
terminal_ansi_yellow: Some(rgba(0xf9bd30ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffdd9cff).into()),
terminal_ansi_blue: Some(rgba(0x83a598ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xc0d2cbff).into()),
terminal_ansi_magenta: Some(rgba(0xa89984ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xd3cbc0ff).into()),
terminal_ansi_cyan: Some(rgba(0x8ec07cff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xc7dfbdff).into()),
terminal_ansi_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0xb1a28aff).into()),
link_text_hover: Some(rgba(0x83a598ff).into()),
..Default::default()
},
@@ -527,22 +538,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0x1d2021ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
- terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
- terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_bright_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_dim_foreground: Some(rgba(0x1d2021ff).into()),
terminal_ansi_black: Some(rgba(0x1d2021ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfbf1c7ff).into()),
terminal_ansi_red: Some(rgba(0xfb4a35ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xffaa95ff).into()),
terminal_ansi_green: Some(rgba(0xb8bb27ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xe0dc98ff).into()),
terminal_ansi_yellow: Some(rgba(0xf9bd30ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffdd9cff).into()),
terminal_ansi_blue: Some(rgba(0x83a598ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xc0d2cbff).into()),
terminal_ansi_magenta: Some(rgba(0xa89984ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xd3cbc0ff).into()),
terminal_ansi_cyan: Some(rgba(0x8ec07cff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xc7dfbdff).into()),
terminal_ansi_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0xb1a28aff).into()),
link_text_hover: Some(rgba(0x83a598ff).into()),
..Default::default()
},
@@ -978,22 +1000,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0x32302fff).into()),
- terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
- terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
- terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_bright_foreground: Some(rgba(0xfbf1c7ff).into()),
+ terminal_dim_foreground: Some(rgba(0x32302fff).into()),
terminal_ansi_black: Some(rgba(0x32302fff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x73675eff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfbf1c7ff).into()),
terminal_ansi_red: Some(rgba(0xfb4a35ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x93211eff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xffaa95ff).into()),
terminal_ansi_green: Some(rgba(0xb8bb27ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x615d1bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xe0dc98ff).into()),
terminal_ansi_yellow: Some(rgba(0xf9bd30ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x91611cff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffdd9cff).into()),
terminal_ansi_blue: Some(rgba(0x83a598ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x414f4aff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xc0d2cbff).into()),
terminal_ansi_magenta: Some(rgba(0xa89984ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x514a41ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xd3cbc0ff).into()),
terminal_ansi_cyan: Some(rgba(0x8ec07cff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x45603eff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xc7dfbdff).into()),
terminal_ansi_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0xb1a28aff).into()),
link_text_hover: Some(rgba(0x83a598ff).into()),
..Default::default()
},
@@ -1429,22 +1462,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0xfbf1c7ff).into()),
- terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
- terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
- terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_foreground: Some(rgba(0x282828ff).into()),
+ terminal_bright_foreground: Some(rgba(0x282828ff).into()),
+ terminal_dim_foreground: Some(rgba(0xfbf1c7ff).into()),
terminal_ansi_black: Some(rgba(0xfbf1c7ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x282828ff).into()),
terminal_ansi_red: Some(rgba(0x9d0408ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x4f1207ff).into()),
terminal_ansi_green: Some(rgba(0x797410ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x3e3a11ff).into()),
terminal_ansi_yellow: Some(rgba(0xb57616ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x5c3b13ff).into()),
terminal_ansi_blue: Some(rgba(0x0b6678ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x14343cff).into()),
terminal_ansi_magenta: Some(rgba(0x7c6f64ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x3e3833ff).into()),
terminal_ansi_cyan: Some(rgba(0x437b59ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x253e2eff).into()),
terminal_ansi_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x73675eff).into()),
link_text_hover: Some(rgba(0x0b6678ff).into()),
..Default::default()
},
@@ -1880,22 +1924,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0xf9f5d7ff).into()),
- terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
- terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
- terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_foreground: Some(rgba(0x282828ff).into()),
+ terminal_bright_foreground: Some(rgba(0x282828ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf9f5d7ff).into()),
terminal_ansi_black: Some(rgba(0xf9f5d7ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x282828ff).into()),
terminal_ansi_red: Some(rgba(0x9d0408ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x4f1207ff).into()),
terminal_ansi_green: Some(rgba(0x797410ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x3e3a11ff).into()),
terminal_ansi_yellow: Some(rgba(0xb57616ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x5c3b13ff).into()),
terminal_ansi_blue: Some(rgba(0x0b6678ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x14343cff).into()),
terminal_ansi_magenta: Some(rgba(0x7c6f64ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x3e3833ff).into()),
terminal_ansi_cyan: Some(rgba(0x437b59ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x253e2eff).into()),
terminal_ansi_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x73675eff).into()),
link_text_hover: Some(rgba(0x0b6678ff).into()),
..Default::default()
},
@@ -2331,22 +2386,33 @@ pub fn gruvbox() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()),
editor_document_highlight_write_background: Some(rgba(0x92847466).into()),
terminal_background: Some(rgba(0xf2e5bcff).into()),
- terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
- terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
- terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_foreground: Some(rgba(0x282828ff).into()),
+ terminal_bright_foreground: Some(rgba(0x282828ff).into()),
+ terminal_dim_foreground: Some(rgba(0xf2e5bcff).into()),
terminal_ansi_black: Some(rgba(0xf2e5bcff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xb1a28aff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x282828ff).into()),
terminal_ansi_red: Some(rgba(0x9d0408ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xdc8c7bff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x4f1207ff).into()),
terminal_ansi_green: Some(rgba(0x797410ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xbfb787ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x3e3a11ff).into()),
terminal_ansi_yellow: Some(rgba(0xb57616ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe2b88bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x5c3b13ff).into()),
terminal_ansi_blue: Some(rgba(0x0b6678ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x8fb0baff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x14343cff).into()),
terminal_ansi_magenta: Some(rgba(0x7c6f64ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xbcb5afff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x3e3833ff).into()),
terminal_ansi_cyan: Some(rgba(0x437b59ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9fbca8ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x253e2eff).into()),
terminal_ansi_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x282828ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x73675eff).into()),
link_text_hover: Some(rgba(0x0b6678ff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn one() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x74ade81a).into()),
editor_document_highlight_write_background: Some(rgba(0x555a6366).into()),
terminal_background: Some(rgba(0x282c34ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x525661ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x673a3cff).into()),
- terminal_ansi_bright_green: Some(rgba(0x4d6140ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x786441ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x385378ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x5e2b26ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x3a565bff).into()),
- terminal_ansi_bright_white: Some(rgba(0xc8ccd4ff).into()),
+ terminal_foreground: Some(rgba(0xc8ccd4ff).into()),
+ terminal_bright_foreground: Some(rgba(0xc8ccd4ff).into()),
+ terminal_dim_foreground: Some(rgba(0x282c34ff).into()),
terminal_ansi_black: Some(rgba(0x282c34ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x525661ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xc8ccd4ff).into()),
terminal_ansi_red: Some(rgba(0xd07277ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x673a3cff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xebb8b9ff).into()),
terminal_ansi_green: Some(rgba(0xa1c181ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x4d6140ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xd1e0bfff).into()),
terminal_ansi_yellow: Some(rgba(0xdec184ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x786441ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xf1dfc1ff).into()),
terminal_ansi_blue: Some(rgba(0x74ade8ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x385378ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xbed5f4ff).into()),
terminal_ansi_magenta: Some(rgba(0xbe5046ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x5e2b26ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xe6a79eff).into()),
terminal_ansi_cyan: Some(rgba(0x6fb4c0ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x3a565bff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xb9d9dfff).into()),
terminal_ansi_white: Some(rgba(0xc8ccd4ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xc8ccd4ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x575d65ff).into()),
link_text_hover: Some(rgba(0x74ade8ff).into()),
..Default::default()
},
@@ -527,22 +538,33 @@ pub fn one() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()),
editor_document_highlight_write_background: Some(rgba(0xa3a3a466).into()),
terminal_background: Some(rgba(0xfafafaff).into()),
- terminal_ansi_bright_black: Some(rgba(0xaaaaaaff).into()),
- terminal_ansi_bright_red: Some(rgba(0xf0b0a4ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xb2cfa9ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xf1dfc1ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xb5baf2ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xcea6d3ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0xa4bfdbff).into()),
- terminal_ansi_bright_white: Some(rgba(0x383a41ff).into()),
+ terminal_foreground: Some(rgba(0x383a41ff).into()),
+ terminal_bright_foreground: Some(rgba(0x383a41ff).into()),
+ terminal_dim_foreground: Some(rgba(0xfafafaff).into()),
terminal_ansi_black: Some(rgba(0xfafafaff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xaaaaaaff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x383a41ff).into()),
terminal_ansi_red: Some(rgba(0xd36151ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xf0b0a4ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x6f312aff).into()),
terminal_ansi_green: Some(rgba(0x669f59ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xb2cfa9ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x354d2eff).into()),
terminal_ansi_yellow: Some(rgba(0xdec184ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xf1dfc1ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x786441ff).into()),
terminal_ansi_blue: Some(rgba(0x5c79e2ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xb5baf2ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x2d3d75ff).into()),
terminal_ansi_magenta: Some(rgba(0x994fa6ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xcea6d3ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x4b2a50ff).into()),
terminal_ansi_cyan: Some(rgba(0x3b82b7ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0xa4bfdbff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x254058ff).into()),
terminal_ansi_white: Some(rgba(0x383a41ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x383a41ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x98989bff).into()),
link_text_hover: Some(rgba(0x5c79e2ff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn rose_pine() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()),
editor_document_highlight_write_background: Some(rgba(0x28253c66).into()),
terminal_background: Some(rgba(0x191724ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x403d55ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x4c3b47ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x203a46ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
+ terminal_foreground: Some(rgba(0xe0def4ff).into()),
+ terminal_bright_foreground: Some(rgba(0xe0def4ff).into()),
+ terminal_dim_foreground: Some(rgba(0x191724ff).into()),
terminal_ansi_black: Some(rgba(0x191724ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x403d55ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xe0def4ff).into()),
terminal_ansi_red: Some(rgba(0xea6f92ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfab9c7ff).into()),
terminal_ansi_green: Some(rgba(0x5dc2a3ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xb3e1d1ff).into()),
terminal_ansi_yellow: Some(rgba(0xf5c177ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xfedfbbff).into()),
terminal_ansi_blue: Some(rgba(0x9cced7ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xcfe7ebff).into()),
terminal_ansi_magenta: Some(rgba(0x9d7691ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x4c3b47ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xceb9c7ff).into()),
terminal_ansi_cyan: Some(rgba(0x32748fff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x203a46ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9cb7c6ff).into()),
terminal_ansi_white: Some(rgba(0xe0def4ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x514e68ff).into()),
link_text_hover: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
@@ -534,22 +545,33 @@ pub fn rose_pine() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x57949f1a).into()),
editor_document_highlight_write_background: Some(rgba(0x9691a466).into()),
terminal_background: Some(rgba(0xfaf4edff).into()),
- terminal_ansi_bright_black: Some(rgba(0xb8b2baff).into()),
- terminal_ansi_bright_red: Some(rgba(0xdcb0bbff).into()),
- terminal_ansi_bright_green: Some(rgba(0xa5d5c5ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xfccd9bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xacc9ceff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xbcb1bdff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x97b1c0ff).into()),
- terminal_ansi_bright_white: Some(rgba(0x575279ff).into()),
+ terminal_foreground: Some(rgba(0x575279ff).into()),
+ terminal_bright_foreground: Some(rgba(0x575279ff).into()),
+ terminal_dim_foreground: Some(rgba(0xfaf4edff).into()),
terminal_ansi_black: Some(rgba(0xfaf4edff).into()),
+ terminal_ansi_bright_black: Some(rgba(0xb8b2baff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x575279ff).into()),
terminal_ansi_red: Some(rgba(0xb4647aff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xdcb0bbff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x57333dff).into()),
terminal_ansi_green: Some(rgba(0x3eaa8eff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xa5d5c5ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x265245ff).into()),
terminal_ansi_yellow: Some(rgba(0xe99d35ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xfccd9bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x854a1fff).into()),
terminal_ansi_blue: Some(rgba(0x57949fff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xacc9ceff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x2f484dff).into()),
terminal_ansi_magenta: Some(rgba(0x7c697fff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xbcb1bdff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x3e353fff).into()),
terminal_ansi_cyan: Some(rgba(0x2a6983ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x97b1c0ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x1c3641ff).into()),
terminal_ansi_white: Some(rgba(0x575279ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x575279ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x827e98ff).into()),
link_text_hover: Some(rgba(0x57949fff).into()),
..Default::default()
},
@@ -992,22 +1014,33 @@ pub fn rose_pine() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()),
editor_document_highlight_write_background: Some(rgba(0x59557166).into()),
terminal_background: Some(rgba(0x232136ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x3f3b58ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x51414eff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x264654ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
+ terminal_foreground: Some(rgba(0xe0def4ff).into()),
+ terminal_bright_foreground: Some(rgba(0xe0def4ff).into()),
+ terminal_dim_foreground: Some(rgba(0x232136ff).into()),
terminal_ansi_black: Some(rgba(0x232136ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x3f3b58ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xe0def4ff).into()),
terminal_ansi_red: Some(rgba(0xea6f92ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x7e3647ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfab9c7ff).into()),
terminal_ansi_green: Some(rgba(0x5dc2a3ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x31614fff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xb3e1d1ff).into()),
terminal_ansi_yellow: Some(rgba(0xf5c177ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x8a653bff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xfedfbbff).into()),
terminal_ansi_blue: Some(rgba(0x9cced7ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x566c70ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xcfe7ebff).into()),
terminal_ansi_magenta: Some(rgba(0xa784a1ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x51414eff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xd3c0cfff).into()),
terminal_ansi_cyan: Some(rgba(0x3f8fb0ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x264654ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xa5c6d7ff).into()),
terminal_ansi_white: Some(rgba(0xe0def4ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xe0def4ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x75718eff).into()),
link_text_hover: Some(rgba(0x9cced7ff).into()),
..Default::default()
},
@@ -75,22 +75,33 @@ pub fn sandcastle() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x528b8b1a).into()),
editor_document_highlight_write_background: Some(rgba(0x7c6f6466).into()),
terminal_background: Some(rgba(0x282c34ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x5e5753ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x57333dff).into()),
- terminal_ansi_bright_green: Some(rgba(0x414f4aff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x4e3f22ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x2c4444ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x523a18ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x414f4aff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfdf4c1ff).into()),
+ terminal_foreground: Some(rgba(0xfdf4c1ff).into()),
+ terminal_bright_foreground: Some(rgba(0xfdf4c1ff).into()),
+ terminal_dim_foreground: Some(rgba(0x282c34ff).into()),
terminal_ansi_black: Some(rgba(0x282c34ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x5e5753ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfdf4c1ff).into()),
terminal_ansi_red: Some(rgba(0xb4637aff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x57333dff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xdcb0bbff).into()),
terminal_ansi_green: Some(rgba(0x83a598ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x414f4aff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xc0d2cbff).into()),
terminal_ansi_yellow: Some(rgba(0xa07e3bff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x4e3f22ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xd3bd9aff).into()),
terminal_ansi_blue: Some(rgba(0x528b8bff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x2c4444ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xa8c4c4ff).into()),
terminal_ansi_magenta: Some(rgba(0xa87323ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x523a18ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xdab78eff).into()),
terminal_ansi_cyan: Some(rgba(0x83a598ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x414f4aff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xc0d2cbff).into()),
terminal_ansi_white: Some(rgba(0xfdf4c1ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfdf4c1ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x968777ff).into()),
link_text_hover: Some(rgba(0x528b8bff).into()),
..Default::default()
},
@@ -76,22 +76,33 @@ pub fn solarized() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x288bd11a).into()),
editor_document_highlight_write_background: Some(rgba(0x6d828866).into()),
terminal_background: Some(rgba(0x002b36ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x5c7279ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x7d181cff).into()),
- terminal_ansi_bright_green: Some(rgba(0x434a11ff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x5d4310ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x214465ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x6f1f40ff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x204e4aff).into()),
- terminal_ansi_bright_white: Some(rgba(0xfdf6e3ff).into()),
+ terminal_foreground: Some(rgba(0xfdf6e3ff).into()),
+ terminal_bright_foreground: Some(rgba(0xfdf6e3ff).into()),
+ terminal_dim_foreground: Some(rgba(0x002b36ff).into()),
terminal_ansi_black: Some(rgba(0x002b36ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x5c7279ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_red: Some(rgba(0xdc3330ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x7d181cff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfaa091ff).into()),
terminal_ansi_green: Some(rgba(0x859904ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x434a11ff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xc6cb8bff).into()),
terminal_ansi_yellow: Some(rgba(0xb58903ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x5d4310ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xe1c28aff).into()),
terminal_ansi_blue: Some(rgba(0x288bd1ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x214465ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xa5c3e9ff).into()),
terminal_ansi_magenta: Some(rgba(0xd33782ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x6f1f40ff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xf0a2bfff).into()),
terminal_ansi_cyan: Some(rgba(0x2ca198ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x204e4aff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x9fd0cbff).into()),
terminal_ansi_white: Some(rgba(0xfdf6e3ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xfdf6e3ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x7b8e91ff).into()),
link_text_hover: Some(rgba(0x288bd1ff).into()),
..Default::default()
},
@@ -520,22 +531,33 @@ pub fn solarized() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()),
editor_document_highlight_write_background: Some(rgba(0x6d828866).into()),
terminal_background: Some(rgba(0xfdf6e3ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x7b8e91ff).into()),
- terminal_ansi_bright_red: Some(rgba(0xfaa091ff).into()),
- terminal_ansi_bright_green: Some(rgba(0xc6cb8bff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0xe1c28aff).into()),
- terminal_ansi_bright_blue: Some(rgba(0xa5c3e9ff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0xf0a2bfff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x9fd0cbff).into()),
- terminal_ansi_bright_white: Some(rgba(0x002b36ff).into()),
+ terminal_foreground: Some(rgba(0x002b36ff).into()),
+ terminal_bright_foreground: Some(rgba(0x002b36ff).into()),
+ terminal_dim_foreground: Some(rgba(0xfdf6e3ff).into()),
terminal_ansi_black: Some(rgba(0xfdf6e3ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x7b8e91ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0x002b36ff).into()),
terminal_ansi_red: Some(rgba(0xdc3330ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0xfaa091ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0x7d181cff).into()),
terminal_ansi_green: Some(rgba(0x859904ff).into()),
+ terminal_ansi_bright_green: Some(rgba(0xc6cb8bff).into()),
+ terminal_ansi_dim_green: Some(rgba(0x434a11ff).into()),
terminal_ansi_yellow: Some(rgba(0xb58904ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0xe1c28aff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0x5d4310ff).into()),
terminal_ansi_blue: Some(rgba(0x298bd1ff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0xa5c3e9ff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0x214465ff).into()),
terminal_ansi_magenta: Some(rgba(0xd33882ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0xf0a2bfff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0x6f1f40ff).into()),
terminal_ansi_cyan: Some(rgba(0x2ca198ff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x9fd0cbff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0x204e4aff).into()),
terminal_ansi_white: Some(rgba(0x002b36ff).into()),
+ terminal_ansi_bright_white: Some(rgba(0x002b36ff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x5c7279ff).into()),
link_text_hover: Some(rgba(0x298bd1ff).into()),
..Default::default()
},
@@ -75,22 +75,33 @@ pub fn summercamp() -> UserThemeFamily {
editor_document_highlight_read_background: Some(rgba(0x499bef1a).into()),
editor_document_highlight_write_background: Some(rgba(0x49443366).into()),
terminal_background: Some(rgba(0x1c1810ff).into()),
- terminal_ansi_bright_black: Some(rgba(0x3b3627ff).into()),
- terminal_ansi_bright_red: Some(rgba(0x7f2724ff).into()),
- terminal_ansi_bright_green: Some(rgba(0x28842cff).into()),
- terminal_ansi_bright_yellow: Some(rgba(0x8c9a10ff).into()),
- terminal_ansi_bright_blue: Some(rgba(0x234b7fff).into()),
- terminal_ansi_bright_magenta: Some(rgba(0x88487eff).into()),
- terminal_ansi_bright_cyan: Some(rgba(0x298462ff).into()),
- terminal_ansi_bright_white: Some(rgba(0xf8f5deff).into()),
+ terminal_foreground: Some(rgba(0xf8f5deff).into()),
+ terminal_bright_foreground: Some(rgba(0xf8f5deff).into()),
+ terminal_dim_foreground: Some(rgba(0x1c1810ff).into()),
terminal_ansi_black: Some(rgba(0x1c1810ff).into()),
+ terminal_ansi_bright_black: Some(rgba(0x3b3627ff).into()),
+ terminal_ansi_dim_black: Some(rgba(0xf8f5deff).into()),
terminal_ansi_red: Some(rgba(0xe35142ff).into()),
+ terminal_ansi_bright_red: Some(rgba(0x7f2724ff).into()),
+ terminal_ansi_dim_red: Some(rgba(0xfbab9cff).into()),
terminal_ansi_green: Some(rgba(0x5dea5aff).into()),
+ terminal_ansi_bright_green: Some(rgba(0x28842cff).into()),
+ terminal_ansi_dim_green: Some(rgba(0xb9f7aeff).into()),
terminal_ansi_yellow: Some(rgba(0xf1fe29ff).into()),
+ terminal_ansi_bright_yellow: Some(rgba(0x8c9a10ff).into()),
+ terminal_ansi_dim_yellow: Some(rgba(0xffffa2ff).into()),
terminal_ansi_blue: Some(rgba(0x499befff).into()),
+ terminal_ansi_bright_blue: Some(rgba(0x234b7fff).into()),
+ terminal_ansi_dim_blue: Some(rgba(0xb1ccf8ff).into()),
terminal_ansi_magenta: Some(rgba(0xf59be6ff).into()),
+ terminal_ansi_bright_magenta: Some(rgba(0x88487eff).into()),
+ terminal_ansi_dim_magenta: Some(rgba(0xfccef3ff).into()),
terminal_ansi_cyan: Some(rgba(0x5beabcff).into()),
+ terminal_ansi_bright_cyan: Some(rgba(0x298462ff).into()),
+ terminal_ansi_dim_cyan: Some(rgba(0xb7f6ddff).into()),
terminal_ansi_white: Some(rgba(0xf8f5deff).into()),
+ terminal_ansi_bright_white: Some(rgba(0xf8f5deff).into()),
+ terminal_ansi_dim_white: Some(rgba(0x57533fff).into()),
link_text_hover: Some(rgba(0x499befff).into()),
..Default::default()
},
@@ -244,43 +244,60 @@ impl<'a> Debug for ThemeColorsRefinementPrinter<'a> {
self.0.editor_document_highlight_write_background,
),
("terminal_background", self.0.terminal_background),
+ ("terminal_foreground", self.0.terminal_foreground),
+ (
+ "terminal_bright_foreground",
+ self.0.terminal_bright_foreground,
+ ),
+ ("terminal_dim_foreground", self.0.terminal_dim_foreground),
+ ("terminal_ansi_black", self.0.terminal_ansi_black),
(
"terminal_ansi_bright_black",
self.0.terminal_ansi_bright_black,
),
+ ("terminal_ansi_dim_black", self.0.terminal_ansi_dim_black),
+ ("terminal_ansi_red", self.0.terminal_ansi_red),
("terminal_ansi_bright_red", self.0.terminal_ansi_bright_red),
+ ("terminal_ansi_dim_red", self.0.terminal_ansi_dim_red),
+ ("terminal_ansi_green", self.0.terminal_ansi_green),
(
"terminal_ansi_bright_green",
self.0.terminal_ansi_bright_green,
),
+ ("terminal_ansi_dim_green", self.0.terminal_ansi_dim_green),
+ ("terminal_ansi_yellow", self.0.terminal_ansi_yellow),
(
"terminal_ansi_bright_yellow",
self.0.terminal_ansi_bright_yellow,
),
+ ("terminal_ansi_dim_yellow", self.0.terminal_ansi_dim_yellow),
+ ("terminal_ansi_blue", self.0.terminal_ansi_blue),
(
"terminal_ansi_bright_blue",
self.0.terminal_ansi_bright_blue,
),
+ ("terminal_ansi_dim_blue", self.0.terminal_ansi_dim_blue),
+ ("terminal_ansi_magenta", self.0.terminal_ansi_magenta),
(
"terminal_ansi_bright_magenta",
self.0.terminal_ansi_bright_magenta,
),
+ (
+ "terminal_ansi_dim_magenta",
+ self.0.terminal_ansi_dim_magenta,
+ ),
+ ("terminal_ansi_cyan", self.0.terminal_ansi_cyan),
(
"terminal_ansi_bright_cyan",
self.0.terminal_ansi_bright_cyan,
),
+ ("terminal_ansi_dim_cyan", self.0.terminal_ansi_dim_cyan),
+ ("terminal_ansi_white", self.0.terminal_ansi_white),
(
"terminal_ansi_bright_white",
self.0.terminal_ansi_bright_white,
),
- ("terminal_ansi_black", self.0.terminal_ansi_black),
- ("terminal_ansi_red", self.0.terminal_ansi_red),
- ("terminal_ansi_green", self.0.terminal_ansi_green),
- ("terminal_ansi_yellow", self.0.terminal_ansi_yellow),
- ("terminal_ansi_blue", self.0.terminal_ansi_blue),
- ("terminal_ansi_magenta", self.0.terminal_ansi_magenta),
- ("terminal_ansi_cyan", self.0.terminal_ansi_cyan),
- ("terminal_ansi_white", self.0.terminal_ansi_white),
+ ("terminal_ansi_dim_white", self.0.terminal_ansi_dim_white),
("link_text_hover", self.0.link_text_hover),
];
@@ -250,22 +250,33 @@ impl Zed1ThemeConverter {
editor.document_highlight_write_background,
),
terminal_background: convert(terminal.background),
- terminal_ansi_bright_black: convert(terminal.bright_black),
- terminal_ansi_bright_red: convert(terminal.bright_red),
- terminal_ansi_bright_green: convert(terminal.bright_green),
- terminal_ansi_bright_yellow: convert(terminal.bright_yellow),
- terminal_ansi_bright_blue: convert(terminal.bright_blue),
- terminal_ansi_bright_magenta: convert(terminal.bright_magenta),
- terminal_ansi_bright_cyan: convert(terminal.bright_cyan),
- terminal_ansi_bright_white: convert(terminal.bright_white),
+ terminal_foreground: convert(terminal.foreground),
+ terminal_bright_foreground: convert(terminal.bright_foreground),
+ terminal_dim_foreground: convert(terminal.dim_foreground),
terminal_ansi_black: convert(terminal.black),
+ terminal_ansi_bright_black: convert(terminal.bright_black),
+ terminal_ansi_dim_black: convert(terminal.dim_black),
terminal_ansi_red: convert(terminal.red),
+ terminal_ansi_bright_red: convert(terminal.bright_red),
+ terminal_ansi_dim_red: convert(terminal.dim_red),
terminal_ansi_green: convert(terminal.green),
+ terminal_ansi_bright_green: convert(terminal.bright_green),
+ terminal_ansi_dim_green: convert(terminal.dim_green),
terminal_ansi_yellow: convert(terminal.yellow),
+ terminal_ansi_bright_yellow: convert(terminal.bright_yellow),
+ terminal_ansi_dim_yellow: convert(terminal.dim_yellow),
terminal_ansi_blue: convert(terminal.blue),
+ terminal_ansi_bright_blue: convert(terminal.bright_blue),
+ terminal_ansi_dim_blue: convert(terminal.dim_blue),
terminal_ansi_magenta: convert(terminal.magenta),
+ terminal_ansi_bright_magenta: convert(terminal.bright_magenta),
+ terminal_ansi_dim_magenta: convert(terminal.dim_magenta),
terminal_ansi_cyan: convert(terminal.cyan),
+ terminal_ansi_bright_cyan: convert(terminal.bright_cyan),
+ terminal_ansi_dim_cyan: convert(terminal.dim_cyan),
terminal_ansi_white: convert(terminal.white),
+ terminal_ansi_bright_white: convert(terminal.bright_white),
+ terminal_ansi_dim_white: convert(terminal.dim_white),
link_text_hover: convert(highest.accent.default.foreground),
})
}
@@ -44,7 +44,7 @@ impl Avatar {
/// Sets the shape of the avatar image.
///
- /// This method allows the shape of the avatar to be specified using a [`Shape`].
+ /// This method allows the shape of the avatar to be specified using an [`AvatarShape`].
/// It modifies the corner radius of the image to match the specified shape.
///
/// # Examples
@@ -20,7 +20,7 @@ impl AvatarAvailabilityIndicator {
}
}
- /// Sets the size of the [`Avatar`] this indicator appears on.
+ /// Sets the size of the [`Avatar`](crate::Avatar) this indicator appears on.
pub fn avatar_size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.avatar_size = size.into();
self
@@ -407,8 +407,8 @@ impl VisibleOnHover for ButtonLike {
}
impl ParentElement for ButtonLike {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -27,6 +27,7 @@ enum ContextMenuItem {
pub struct ContextMenu {
items: Vec<ContextMenuItem>,
focus_handle: FocusHandle,
+ action_context: Option<FocusHandle>,
selected_index: Option<usize>,
delayed: bool,
clicked: bool,
@@ -41,6 +42,8 @@ impl FocusableView for ContextMenu {
impl EventEmitter<DismissEvent> for ContextMenu {}
+impl FluentBuilder for ContextMenu {}
+
impl ContextMenu {
pub fn build(
cx: &mut WindowContext,
@@ -56,6 +59,7 @@ impl ContextMenu {
Self {
items: Default::default(),
focus_handle,
+ action_context: None,
selected_index: None,
delayed: false,
clicked: false,
@@ -66,6 +70,11 @@ impl ContextMenu {
})
}
+ pub fn context(mut self, focus: FocusHandle) -> Self {
+ self.action_context = Some(focus);
+ self
+ }
+
pub fn header(mut self, title: impl Into<SharedString>) -> Self {
self.items.push(ContextMenuItem::Header(title.into()));
self
@@ -305,7 +314,14 @@ impl Render for ContextMenu {
.child(label_element)
.debug_selector(|| format!("MENU_ITEM-{}", label))
.children(action.as_ref().and_then(|action| {
- KeyBinding::for_action(&**action, cx)
+ self.action_context
+ .as_ref()
+ .map(|focus| {
+ KeyBinding::for_action_in(&**action, focus, cx)
+ })
+ .unwrap_or_else(|| {
+ KeyBinding::for_action(&**action, cx)
+ })
.map(|binding| div().ml_1().child(binding))
})),
)
@@ -16,7 +16,7 @@ impl RenderOnce for KeyBinding {
.flex_none()
.gap_2()
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
- let key_icon = Self::icon_for_key(&keystroke);
+ let key_icon = Self::icon_for_key(keystroke);
h_flex()
.flex_none()
@@ -37,7 +37,7 @@ pub struct Label {
}
impl Label {
- /// Create a new [`Label`] with the given text.
+ /// Creates a new [`Label`] with the given text.
///
/// # Examples
///
@@ -78,8 +78,8 @@ impl LabelCommon for LabelLike {
}
impl ParentElement for LabelLike {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -40,8 +40,8 @@ impl List {
}
impl ParentElement for List {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -141,8 +141,8 @@ impl Selectable for ListItem {
}
impl ParentElement for ListItem {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -222,17 +222,19 @@ impl RenderOnce for ListItem {
}))
.child(
h_flex()
- // HACK: We need to set *any* width value here in order for this container to size correctly.
- // Without this the `h_flex` will overflow the parent `inner_list_item`.
- .w_px()
- .flex_1()
+ .flex_grow()
+ .flex_shrink_0()
+ .flex_basis(relative(0.25))
.gap_1()
+ .overflow_hidden()
.children(self.start_slot)
.children(self.children),
)
.when_some(self.end_slot, |this, end_slot| {
this.justify_between().child(
h_flex()
+ .flex_shrink()
+ .overflow_hidden()
.when(self.end_hover_slot.is_some(), |this| {
this.visible()
.group_hover("list_item", |this| this.invisible())
@@ -16,7 +16,7 @@ use smallvec::SmallVec;
///
/// Related elements:
///
-/// `ContextMenu`:
+/// [`ContextMenu`](crate::ContextMenu):
///
/// Used to display a popover menu that only contains a list of items. Context menus are always
/// launched by secondary clicking on an element. The menu is positioned relative to the user's cursor.
@@ -74,7 +74,7 @@ impl Popover {
}
impl ParentElement for Popover {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -1,9 +1,10 @@
use std::{cell::RefCell, rc::Rc};
use gpui::{
- overlay, point, px, rems, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
- Element, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseDownEvent,
- ParentElement, Pixels, Point, View, VisualContext, WindowContext,
+ overlay, point, prelude::FluentBuilder, px, rems, AnchorCorner, AnyElement, Bounds,
+ DismissEvent, DispatchPhase, Element, ElementContext, ElementId, InteractiveBounds,
+ IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
+ VisualContext, WindowContext,
};
use crate::{Clickable, Selectable};
@@ -51,7 +52,7 @@ impl<M: ManagedView> PopoverMenu<M> {
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
if modal.focus_handle(cx).contains_focused(cx) {
if previous_focus_handle.is_some() {
- cx.focus(&previous_focus_handle.as_ref().unwrap())
+ cx.focus(previous_focus_handle.as_ref().unwrap())
}
}
*menu2.borrow_mut() = None;
@@ -134,7 +135,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (gpui::LayoutId, Self::State) {
let mut menu_layout_id = None;
@@ -188,7 +189,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
&mut self,
_: Bounds<gpui::Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
if let Some(mut child) = element_state.child_element.take() {
child.paint(cx);
@@ -1,9 +1,9 @@
use std::{cell::RefCell, rc::Rc};
use gpui::{
- overlay, AnchorCorner, AnyElement, BorrowWindow, Bounds, DismissEvent, DispatchPhase, Element,
- ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton, MouseDownEvent,
- ParentElement, Pixels, Point, View, VisualContext, WindowContext,
+ overlay, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase, Element,
+ ElementContext, ElementId, InteractiveBounds, IntoElement, LayoutId, ManagedView, MouseButton,
+ MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
};
pub struct RightClickMenu<M: ManagedView> {
@@ -64,7 +64,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
fn request_layout(
&mut self,
element_state: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (gpui::LayoutId, Self::State) {
let (menu, position) = if let Some(element_state) = element_state {
(element_state.menu, element_state.position)
@@ -116,7 +116,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
&mut self,
bounds: Bounds<gpui::Pixels>,
element_state: &mut Self::State,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
if let Some(mut child) = element_state.child_element.take() {
child.paint(cx);
@@ -155,7 +155,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| {
if modal.focus_handle(cx).contains_focused(cx) {
if previous_focus_handle.is_some() {
- cx.focus(&previous_focus_handle.as_ref().unwrap())
+ cx.focus(previous_focus_handle.as_ref().unwrap())
}
}
*menu2.borrow_mut() = None;
@@ -4,6 +4,8 @@ use story::Story;
use crate::{prelude::*, Avatar};
use crate::{IconName, ListItem};
+const OVERFLOWING_TEXT: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean mauris ligula, luctus vel dignissim eu, vestibulum sed libero. Sed at convallis velit.";
+
pub struct ListItemStory;
impl Render for ListItemStory {
@@ -98,5 +100,29 @@ impl Render for ListItemStory {
println!("Right mouse down!");
}),
)
+ .child(Story::label("With overflowing content in the `end_slot`"))
+ .child(
+ ListItem::new("with_overflowing_content_in_end_slot")
+ .child("An excerpt")
+ .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+ )
+ .child(Story::label(
+ "`inset` with overflowing content in the `end_slot`",
+ ))
+ .child(
+ ListItem::new("inset_with_overflowing_content_in_end_slot")
+ .inset(true)
+ .child("An excerpt")
+ .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+ )
+ .child(Story::label(
+ "`inset` with overflowing content in `children` and `end_slot`",
+ ))
+ .child(
+ ListItem::new("inset_with_overflowing_content_in_children_and_end_slot")
+ .inset(true)
+ .child(Label::new(OVERFLOWING_TEXT))
+ .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+ )
}
}
@@ -92,8 +92,8 @@ impl Selectable for Tab {
}
impl ParentElement for Tab {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -83,8 +83,8 @@ impl TabBar {
}
impl ParentElement for TabBar {
- fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -117,7 +117,7 @@ impl RenderOnce for TabBar {
.relative()
.flex_1()
.h_full()
- .overflow_hidden_x()
+ .overflow_x_hidden()
.child(
div()
.absolute()
@@ -69,29 +69,74 @@ impl Tooltip {
impl Render for Tooltip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
- overlay().child(
- // padding to avoid mouse cursor
- div().pl_2().pt_2p5().child(
- v_flex()
- .elevation_2(cx)
- .font(ui_font)
- .text_ui()
- .text_color(cx.theme().colors().text)
- .py_1()
- .px_2()
- .child(
- h_flex()
- .gap_4()
- .child(self.title.clone())
- .when_some(self.key_binding.clone(), |this, key_binding| {
- this.justify_between().child(key_binding)
- }),
- )
- .when_some(self.meta.clone(), |this, meta| {
- this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
+ tooltip_container(cx, |el, _| {
+ el.child(
+ h_flex()
+ .gap_4()
+ .child(self.title.clone())
+ .when_some(self.key_binding.clone(), |this, key_binding| {
+ this.justify_between().child(key_binding)
}),
- ),
- )
+ )
+ .when_some(self.meta.clone(), |this, meta| {
+ this.child(Label::new(meta).size(LabelSize::Small).color(Color::Muted))
+ })
+ })
+ }
+}
+
+fn tooltip_container<V>(
+ cx: &mut ViewContext<V>,
+ f: impl FnOnce(Div, &mut ViewContext<V>) -> Div,
+) -> impl IntoElement {
+ let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
+ overlay().child(
+ // padding to avoid mouse cursor
+ div().pl_2().pt_2p5().child(
+ v_flex()
+ .elevation_2(cx)
+ .font(ui_font)
+ .text_ui()
+ .text_color(cx.theme().colors().text)
+ .py_1()
+ .px_2()
+ .map(|el| f(el, cx)),
+ ),
+ )
+}
+
+pub struct LinkPreview {
+ link: SharedString,
+}
+
+impl LinkPreview {
+ pub fn new(url: &str, cx: &mut WindowContext) -> AnyView {
+ let mut wrapped_url = String::new();
+ for (i, ch) in url.chars().enumerate() {
+ if i == 500 {
+ wrapped_url.push('โฆ');
+ break;
+ }
+ if i % 100 == 0 && i != 0 {
+ wrapped_url.push('\n');
+ }
+ wrapped_url.push(ch);
+ }
+ cx.new_view(|_cx| LinkPreview {
+ link: wrapped_url.into(),
+ })
+ .into()
+ }
+}
+
+impl Render for LinkPreview {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+ tooltip_container(cx, |el, _| {
+ el.child(
+ Label::new(self.link.clone())
+ .size(LabelSize::XSmall)
+ .color(Color::Muted),
+ )
+ })
}
}
@@ -2,9 +2,9 @@
pub use gpui::prelude::*;
pub use gpui::{
- div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
- InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
- WindowContext,
+ div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementContext,
+ ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled,
+ ViewContext, WindowContext,
};
pub use crate::clickable::*;
@@ -20,7 +20,7 @@ pub enum ElevationIndex {
}
impl ElevationIndex {
- pub fn z_index(self) -> u8 {
+ pub fn z_index(self) -> u16 {
match self {
ElevationIndex::Background => 0,
ElevationIndex::Surface => 42,
@@ -99,6 +99,15 @@ impl FakeHttpClient {
.unwrap())
})
}
+
+ pub fn with_200_response() -> Arc<dyn HttpClient> {
+ Self::create(|_| async move {
+ Ok(Response::builder()
+ .status(200)
+ .body(Default::default())
+ .unwrap())
+ })
+ }
}
#[cfg(feature = "test-support")]
@@ -7,19 +7,21 @@ pub mod paths;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
+pub use backtrace::Backtrace;
+use futures::Future;
+use lazy_static::lazy_static;
+use rand::{seq::SliceRandom, Rng};
use std::{
borrow::Cow,
cmp::{self, Ordering},
+ env,
ops::{AddAssign, Range, RangeInclusive},
panic::Location,
pin::Pin,
task::{Context, Poll},
+ time::Instant,
};
-pub use backtrace::Backtrace;
-use futures::Future;
-use rand::{seq::SliceRandom, Rng};
-
pub use take_until::*;
#[macro_export]
@@ -133,6 +135,24 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se
}
}
+pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
+ lazy_static! {
+ pub static ref ZED_MEASUREMENTS: bool = env::var("ZED_MEASUREMENTS")
+ .map(|measurements| measurements == "1" || measurements == "true")
+ .unwrap_or(false);
+ }
+
+ if *ZED_MEASUREMENTS {
+ let start = Instant::now();
+ let result = f();
+ let elapsed = start.elapsed();
+ eprintln!("{}: {:?}", label, elapsed);
+ result
+ } else {
+ f()
+ }
+}
+
pub trait ResultExt<E> {
type Ok;
@@ -299,7 +319,7 @@ pub struct Deferred<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Deferred<F> {
/// Drop without running the deferred function.
- pub fn cancel(mut self) {
+ pub fn abort(mut self) {
self.0.take();
}
}
@@ -23,6 +23,7 @@ async-trait = { workspace = true, "optional" = true }
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
tokio = { version = "1.15", "optional" = true }
serde_json.workspace = true
+regex.workspace = true
collections = { path = "../collections" }
command_palette = { path = "../command_palette" }
@@ -0,0 +1,36 @@
+This contains the code for Zed's Vim emulation mode.
+
+Vim mode in Zed is supposed to primarily "do what you expect": it mostly tries to copy vim exactly, but will use Zed-specific functionality when available to make things smoother. This means Zed will never be 100% vim compatible, but should be 100% vim familiar!
+
+The backlog is maintained in the `#vim` channel notes.
+
+## Testing against Neovim
+
+If you are making a change to make Zed's behaviour more closely match vim/nvim, you can create a test using the `NeovimBackedTestContext`.
+
+For example, the following test checks that Zed and Neovim have the same behaviour when running `*` in visual mode:
+
+```rust
+#[gpui::test]
+async fn test_visual_star_hash(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.set_shared_state("หa.c. abcd a.c. abcd").await;
+ cx.simulate_shared_keystrokes(["v", "3", "l", "*"]).await;
+ cx.assert_shared_state("a.c. abcd หa.c. abcd").await;
+}
+```
+
+To keep CI runs fast, by default the neovim tests use a cached JSON file that records what neovim did (see crates/vim/test_data),
+but while developing this test you'll need to run it with the neovim flag enabled:
+
+```
+cargo test -p vim --features neovim test_visual_star_hash
+```
+
+This will run your keystrokes against a headless neovim and cache the results in the test_data directory.
+
+
+## Testing zed-only behaviour
+
+Zed does more than vim/neovim in their default modes. The `VimTestContext` can be used instead. This lets you test integration with the language server and other parts of zed's UI that don't have a NeoVim equivalent.
@@ -11,6 +11,7 @@ use workspace::Workspace;
use crate::{
normal::normal_motion,
state::{Mode, Operator},
+ utils::coerce_punctuation,
visual::visual_motion,
Vim,
};
@@ -72,9 +73,9 @@ pub(crate) struct Up {
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
-struct Down {
+pub(crate) struct Down {
#[serde(default)]
- display_lines: bool,
+ pub(crate) display_lines: bool,
}
#[derive(Clone, Deserialize, PartialEq)]
@@ -680,8 +681,8 @@ pub(crate) fn next_word_start(
for _ in 0..times {
let mut crossed_newline = false;
point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+ 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';
let found = (left_kind != right_kind && right_kind != CharKind::Whitespace)
@@ -710,8 +711,8 @@ fn next_word_end(
*point.column_mut() = 0;
}
point = movement::find_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+ 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
});
@@ -743,8 +744,8 @@ fn previous_word_start(
// cursor because the newline is checked only once.
point =
movement::find_preceding_boundary(map, point, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+ 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'
});
@@ -1,4 +1,10 @@
-use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
+use crate::{
+ motion::Motion,
+ object::Object,
+ state::Mode,
+ utils::{coerce_punctuation, copy_selections_content},
+ Vim,
+};
use editor::{
display_map::DisplaySnapshot,
movement::{self, FindRange, TextLayoutDetails},
@@ -102,9 +108,9 @@ fn expand_changed_word_selection(
if in_word {
selection.end =
movement::find_boundary(map, selection.end, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
+ let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
let right_kind =
- char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+ coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
left_kind != right_kind && left_kind != CharKind::Whitespace
});
@@ -202,7 +202,7 @@ mod test {
use futures::StreamExt;
use indoc::indoc;
- use gpui::InputHandler;
+ use gpui::ViewInputHandler;
use crate::{
state::Mode,
@@ -3,7 +3,12 @@ use search::{buffer_search, BufferSearchBar, SearchMode, SearchOptions};
use serde_derive::Deserialize;
use workspace::{searchable::Direction, Workspace};
-use crate::{motion::Motion, normal::move_cursor, state::SearchState, Vim};
+use crate::{
+ motion::Motion,
+ normal::move_cursor,
+ state::{Mode, SearchState},
+ Vim,
+};
#[derive(Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
@@ -91,7 +96,6 @@ fn search(workspace: &mut Workspace, action: &Search, cx: &mut ViewContext<Works
if query.is_empty() {
search_bar.set_replacement(None, cx);
- search_bar.set_search_options(SearchOptions::CASE_SENSITIVE, cx);
search_bar.activate_search_mode(SearchMode::Regex, cx);
}
vim.workspace_state.search = SearchState {
@@ -146,18 +150,23 @@ pub fn move_to_internal(
Vim::update(cx, |vim, cx| {
let pane = workspace.active_pane().clone();
let count = vim.take_count(cx).unwrap_or(1);
+
pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
let search = search_bar.update(cx, |search_bar, cx| {
- let mut options = SearchOptions::CASE_SENSITIVE;
- options.set(SearchOptions::WHOLE_WORD, whole_word);
- if search_bar.show(cx) {
- search_bar
- .query_suggestion(cx)
- .map(|query| search_bar.search(&query, Some(options), cx))
- } else {
- None
+ let options = SearchOptions::CASE_SENSITIVE;
+ if !search_bar.show(cx) {
+ return None;
+ }
+ let Some(query) = search_bar.query_suggestion(cx) else {
+ return None;
+ };
+ let mut query = regex::escape(&query);
+ if whole_word {
+ query = format!(r"\b{}\b", query);
}
+ search_bar.activate_search_mode(SearchMode::Regex, cx);
+ Some(search_bar.search(&query, Some(options), cx))
});
if let Some(search) = search {
@@ -173,6 +182,11 @@ pub fn move_to_internal(
}
}
});
+
+ if vim.state().mode.is_visual() {
+ vim.switch_mode(Mode::Normal, false, cx)
+ }
+
vim.clear_operator(cx);
});
}
@@ -350,7 +364,10 @@ mod test {
use editor::DisplayPoint;
use search::BufferSearchBar;
- use crate::{state::Mode, test::VimTestContext};
+ use crate::{
+ state::Mode,
+ test::{NeovimBackedTestContext, VimTestContext},
+ };
#[gpui::test]
async fn test_move_to_next(cx: &mut gpui::TestAppContext) {
@@ -459,6 +476,13 @@ mod test {
cx.simulate_keystrokes(["/", "b"]);
cx.simulate_keystrokes(["enter"]);
cx.assert_state("aa\nหbb\ndd\ncc\nbb\n", Mode::Normal);
+
+ // check that searching switches to normal mode if in visual mode
+ cx.set_state("หone two one", Mode::Normal);
+ cx.simulate_keystrokes(["v", "l", "l"]);
+ cx.assert_editor_state("ยซoneหยป two one");
+ cx.simulate_keystrokes(["*"]);
+ cx.assert_state("one two หone", Mode::Normal);
}
#[gpui::test]
@@ -474,4 +498,14 @@ mod test {
cx.simulate_keystrokes(["shift-enter"]);
cx.assert_editor_state("ยซoneหยป one one one");
}
+
+ #[gpui::test]
+ async fn test_visual_star_hash(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.set_shared_state("หa.c. abcd a.c. abcd").await;
+ cx.simulate_shared_keystrokes(["v", "3", "l", "*"]).await;
+ cx.assert_shared_state("a.c. abcd หa.c. abcd").await;
+ cx.assert_shared_mode(Mode::Normal).await;
+ }
}
@@ -10,7 +10,10 @@ use language::{char_kind, CharKind, Selection};
use serde::Deserialize;
use workspace::Workspace;
-use crate::{motion::right, normal::normal_object, state::Mode, visual::visual_object, Vim};
+use crate::{
+ motion::right, normal::normal_object, state::Mode, utils::coerce_punctuation,
+ visual::visual_object, Vim,
+};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Object {
@@ -195,9 +198,10 @@ impl Object {
}
}
-/// Return a range that surrounds the word relative_to is in
-/// If relative_to is at the start of a word, return the word.
-/// If relative_to is between words, return the space between
+/// Returns a range that surrounds the word `relative_to` is in.
+///
+/// If `relative_to` is at the start of a word, return the word.
+/// If `relative_to` is between words, return the space between.
fn in_word(
map: &DisplaySnapshot,
relative_to: DisplayPoint,
@@ -212,24 +216,25 @@ fn in_word(
right(map, relative_to, 1),
movement::FindRange::SingleLine,
|left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+ coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
+ != coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
},
);
let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+ coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
+ != coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
});
Some(start..end)
}
-/// Return a range that surrounds the word and following whitespace
+/// Returns a range that surrounds the word and following whitespace
/// relative_to is in.
-/// If relative_to is at the start of a word, return the word and following whitespace.
-/// If relative_to is between words, return the whitespace back and the following word
-
+///
+/// If `relative_to` is at the start of a word, return the word and following whitespace.
+/// If `relative_to` is between words, return the whitespace back and the following word.
+///
/// if in word
/// delete that word
/// if there is whitespace following the word, delete that as well
@@ -281,15 +286,15 @@ fn around_next_word(
right(map, relative_to, 1),
FindRange::SingleLine,
|left, right| {
- char_kind(&scope, left).coerce_punctuation(ignore_punctuation)
- != char_kind(&scope, right).coerce_punctuation(ignore_punctuation)
+ coerce_punctuation(char_kind(&scope, left), ignore_punctuation)
+ != coerce_punctuation(char_kind(&scope, right), ignore_punctuation)
},
);
let mut word_found = false;
let end = movement::find_boundary(map, relative_to, FindRange::MultiLine, |left, right| {
- let left_kind = char_kind(&scope, left).coerce_punctuation(ignore_punctuation);
- let right_kind = char_kind(&scope, right).coerce_punctuation(ignore_punctuation);
+ let left_kind = coerce_punctuation(char_kind(&scope, left), ignore_punctuation);
+ let right_kind = coerce_punctuation(char_kind(&scope, right), ignore_punctuation);
let found = (word_found && left_kind != right_kind) || right == '\n' && left == '\n';
@@ -3,8 +3,11 @@ mod neovim_backed_test_context;
mod neovim_connection;
mod vim_test_context;
+use std::time::Duration;
+
use command_palette::CommandPalette;
use editor::DisplayPoint;
+use gpui::KeyBinding;
pub use neovim_backed_binding_test_context::*;
pub use neovim_backed_test_context::*;
pub use vim_test_context::*;
@@ -12,7 +15,7 @@ pub use vim_test_context::*;
use indoc::indoc;
use search::BufferSearchBar;
-use crate::{state::Mode, ModeIndicator};
+use crate::{insert::NormalBefore, motion, state::Mode, ModeIndicator};
#[gpui::test]
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
@@ -774,3 +777,73 @@ async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
Mode::Visual,
);
}
+
+#[gpui::test]
+async fn test_jk(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.update(|cx| {
+ cx.bind_keys([KeyBinding::new(
+ "j k",
+ NormalBefore,
+ Some("vim_mode == insert"),
+ )])
+ });
+ cx.neovim.exec("imap jk <esc>").await;
+
+ cx.set_shared_state("หhello").await;
+ cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
+ .await;
+ cx.assert_shared_state("jหohello").await;
+}
+
+#[gpui::test]
+async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
+ let mut cx = VimTestContext::new(cx, true).await;
+
+ cx.update(|cx| {
+ cx.bind_keys([KeyBinding::new(
+ "j k",
+ NormalBefore,
+ Some("vim_mode == insert"),
+ )])
+ });
+
+ cx.set_state("หhello", Mode::Normal);
+ cx.simulate_keystrokes(["i", "j"]);
+ cx.executor().advance_clock(Duration::from_millis(500));
+ cx.run_until_parked();
+ cx.assert_state("หhello", Mode::Insert);
+ cx.executor().advance_clock(Duration::from_millis(500));
+ cx.run_until_parked();
+ cx.assert_state("jหhello", Mode::Insert);
+ cx.simulate_keystrokes(["k", "j", "k"]);
+ cx.assert_state("jหkhello", Mode::Normal);
+}
+
+#[gpui::test]
+async fn test_comma_w(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.update(|cx| {
+ cx.bind_keys([KeyBinding::new(
+ ", w",
+ motion::Down {
+ display_lines: false,
+ },
+ Some("vim_mode == normal"),
+ )])
+ });
+ cx.neovim.exec("map ,w j").await;
+
+ cx.set_shared_state("หhello hello\nhello hello").await;
+ cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
+ .await;
+ cx.assert_shared_state("hello hello\nhello hellหo").await;
+
+ cx.set_shared_state("หhello hello\nhello hello").await;
+ cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
+ .await;
+ cx.assert_shared_state("hellหo hello\nhello hello").await;
+ cx.assert_shared_mode(Mode::Insert).await;
+}
@@ -52,7 +52,7 @@ pub struct NeovimBackedTestContext {
// Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
// bindings are exempted. If None, all bindings are ignored for that insertion text.
exemptions: HashMap<String, Option<HashSet<String>>>,
- neovim: NeovimConnection,
+ pub(crate) neovim: NeovimConnection,
last_set_state: Option<String>,
recent_keystrokes: Vec<String>,
@@ -277,6 +277,24 @@ impl NeovimBackedTestContext {
self.neovim.mode().await.unwrap()
}
+ pub async fn assert_shared_mode(&mut self, mode: Mode) {
+ let neovim = self.neovim_mode().await;
+ let editor = self.cx.mode();
+
+ if neovim != mode || editor != mode {
+ panic!(
+ indoc! {"Test failed (zed does not match nvim behaviour)
+ # desired mode:
+ {:?}
+ # neovim mode:
+ {:?}
+ # zed mode:
+ {:?}"},
+ mode, neovim, editor,
+ )
+ }
+ }
+
pub async fn assert_state_matches(&mut self) {
self.is_dirty = false;
let neovim = self.neovim_state().await;
@@ -42,6 +42,7 @@ pub enum NeovimData {
Key(String),
Get { state: String, mode: Option<Mode> },
ReadRegister { name: char, value: String },
+ Exec { command: String },
SetOption { value: String },
}
@@ -269,6 +270,32 @@ impl NeovimConnection {
);
}
+ #[cfg(feature = "neovim")]
+ pub async fn exec(&mut self, value: &str) {
+ self.nvim
+ .command_output(format!("{}", value).as_str())
+ .await
+ .unwrap();
+
+ self.data.push_back(NeovimData::Exec {
+ command: value.to_string(),
+ })
+ }
+
+ #[cfg(not(feature = "neovim"))]
+ pub async fn exec(&mut self, value: &str) {
+ if let Some(NeovimData::Get { .. }) = self.data.front() {
+ self.data.pop_front();
+ };
+ assert_eq!(
+ self.data.pop_front(),
+ Some(NeovimData::Exec {
+ command: value.to_string(),
+ }),
+ "operation does not match recorded script. re-record with --features=neovim"
+ );
+ }
+
#[cfg(not(feature = "neovim"))]
pub async fn read_register(&mut self, register: char) -> String {
if let Some(NeovimData::Get { .. }) = self.data.front() {
@@ -1,6 +1,6 @@
use editor::{ClipboardSelection, Editor};
use gpui::{AppContext, ClipboardItem};
-use language::Point;
+use language::{CharKind, Point};
pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut AppContext) {
let selections = editor.selections.all_adjusted(cx);
@@ -48,3 +48,11 @@ pub fn copy_selections_content(editor: &mut Editor, linewise: bool, cx: &mut App
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
}
+
+pub fn coerce_punctuation(kind: CharKind, treat_punctuation_as_word: bool) -> CharKind {
+ if treat_punctuation_as_word && kind == CharKind::Punctuation {
+ CharKind::Word
+ } else {
+ kind
+ }
+}
@@ -0,0 +1,15 @@
+{"Exec":{"command":"map ,w j"}}
+{"Put":{"state":"หhello hello\nhello hello"}}
+{"Key":"f"}
+{"Key":"o"}
+{"Key":";"}
+{"Key":","}
+{"Key":"w"}
+{"Get":{"state":"hello hello\nhello hellหo","mode":"Normal"}}
+{"Put":{"state":"หhello hello\nhello hello"}}
+{"Key":"f"}
+{"Key":"o"}
+{"Key":";"}
+{"Key":","}
+{"Key":"i"}
+{"Get":{"state":"hellหo hello\nhello hello","mode":"Insert"}}
@@ -0,0 +1,8 @@
+{"Exec":{"command":"imap jk <esc>"}}
+{"Put":{"state":"หhello"}}
+{"Key":"i"}
+{"Key":"j"}
+{"Key":"o"}
+{"Key":"j"}
+{"Key":"k"}
+{"Get":{"state":"jหohello","mode":"Normal"}}
@@ -0,0 +1,6 @@
+{"Put":{"state":"หa.c. abcd a.c. abcd"}}
+{"Key":"v"}
+{"Key":"3"}
+{"Key":"l"}
+{"Key":"*"}
+{"Get":{"state":"a.c. abcd หa.c. abcd","mode":"Normal"}}
@@ -1,3 +1,4 @@
+use crate::persistence::model::DockData;
use crate::DraggedDock;
use crate::{status_bar::StatusItemView, Workspace};
use gpui::{
@@ -141,6 +142,7 @@ pub struct Dock {
is_open: bool,
active_panel_index: usize,
focus_handle: FocusHandle,
+ pub(crate) serialized_dock: Option<DockData>,
_focus_subscription: Subscription,
}
@@ -201,6 +203,7 @@ impl Dock {
is_open: false,
focus_handle: focus_handle.clone(),
_focus_subscription: focus_subscription,
+ serialized_dock: None,
}
});
@@ -408,10 +411,26 @@ impl Dock {
}),
];
+ let name = panel.persistent_name().to_string();
+
self.panel_entries.push(PanelEntry {
panel: Arc::new(panel),
_subscriptions: subscriptions,
});
+ if let Some(serialized) = self.serialized_dock.clone() {
+ if serialized.active_panel == Some(name) {
+ self.activate_panel(self.panel_entries.len() - 1, cx);
+ if serialized.visible {
+ self.set_open(true, cx);
+ }
+ if serialized.zoom {
+ if let Some(panel) = self.active_panel() {
+ panel.set_zoomed(true, cx)
+ };
+ }
+ }
+ }
+
cx.notify()
}
@@ -281,7 +281,7 @@ where
Ok(value) => Some(value),
Err(err) => {
log::error!("TODO {err:?}");
- cx.update(|view, cx| {
+ cx.update_root(|view, cx| {
if let Ok(workspace) = view.downcast::<Workspace>() {
workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
}
@@ -1092,7 +1092,7 @@ impl Pane {
return Ok(true);
}
- let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
+ let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|cx| {
(
item.has_conflict(cx),
item.is_dirty(cx),
@@ -1132,7 +1132,7 @@ impl Pane {
}
} else if is_dirty && (can_save || can_save_as) {
if save_intent == SaveIntent::Close {
- let will_autosave = cx.update(|_, cx| {
+ let will_autosave = cx.update(|cx| {
matches!(
WorkspaceSettings::get_global(cx).autosave,
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
@@ -1166,7 +1166,7 @@ impl Pane {
})?
.unwrap_or_else(|| Path::new("").into());
- let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
+ let abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path))?;
if let Some(abs_path) = abs_path.await.ok().flatten() {
pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
.await?;
@@ -2054,7 +2054,7 @@ fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
let path = buffer_path
.as_ref()
.and_then(|p| p.path.to_str())
- .unwrap_or(&"This buffer");
+ .unwrap_or("This buffer");
let path = truncate_and_remove_front(path, 80);
format!("{path} contains unsaved edits. Do you want to save it?")
}
@@ -710,7 +710,7 @@ mod element {
pane_bounds: Bounds<Pixels>,
axis_bounds: Bounds<Pixels>,
workspace: WeakView<Workspace>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) {
let handle_bounds = Bounds {
origin: pane_bounds.origin.apply_along(axis, |origin| {
@@ -803,7 +803,7 @@ mod element {
fn request_layout(
&mut self,
state: Option<Self::State>,
- cx: &mut ui::prelude::WindowContext,
+ cx: &mut ui::prelude::ElementContext,
) -> (gpui::LayoutId, Self::State) {
let mut style = Style::default();
style.flex_grow = 1.;
@@ -820,7 +820,7 @@ mod element {
&mut self,
bounds: gpui::Bounds<ui::prelude::Pixels>,
state: &mut Self::State,
- cx: &mut ui::prelude::WindowContext,
+ cx: &mut ui::prelude::ElementContext,
) {
let flexes = self.flexes.lock().clone();
let len = self.children.len();
@@ -904,8 +904,8 @@ mod element {
}
impl ParentElement for PaneAxisElement {
- fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
- &mut self.children
+ fn extend(&mut self, elements: impl Iterator<Item = AnyElement>) {
+ self.children.extend(elements)
}
}
@@ -591,7 +591,7 @@ impl WorkspaceDb {
Ok(())
}
SerializedPaneGroup::Pane(pane) => {
- Self::save_pane(conn, workspace_id, &pane, parent)?;
+ Self::save_pane(conn, workspace_id, pane, parent)?;
Ok(())
}
}
@@ -11,7 +11,7 @@ use crate::{
ItemHandle,
};
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum SearchEvent {
MatchesInvalidated,
ActiveMatchChanged,
@@ -1,6 +1,6 @@
use crate::{ItemHandle, Pane};
use gpui::{
- div, AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
+ AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
WindowContext,
};
use std::any::TypeId;
@@ -34,13 +34,12 @@ pub struct StatusBar {
impl Render for StatusBar {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
- div()
+ h_flex()
+ .w_full()
+ .justify_between()
+ .gap_2()
.py_0p5()
.px_1()
- .flex()
- .items_center()
- .justify_between()
- .w_full()
.h_8()
.bg(cx.theme().colors().status_bar_background)
.child(self.render_left_tools(cx))
@@ -51,14 +50,13 @@ impl Render for StatusBar {
impl StatusBar {
fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
h_flex()
- .items_center()
.gap_2()
+ .overflow_x_hidden()
.children(self.left_items.iter().map(|item| item.to_any()))
}
fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
h_flex()
- .items_center()
.gap_2()
.children(self.right_items.iter().rev().map(|item| item.to_any()))
}
@@ -112,18 +112,22 @@ impl Render for Toolbar {
.child(
h_flex()
.justify_between()
+ .gap_2()
.when(has_left_items, |this| {
this.child(
h_flex()
- .flex_1()
+ .flex_auto()
.justify_start()
+ .overflow_x_hidden()
.children(self.left_items().map(|item| item.to_any())),
)
})
.when(has_right_items, |this| {
this.child(
h_flex()
- .flex_1()
+ // We're using `flex_none` here to prevent some flickering that can occur when the
+ // size of the left items container changes.
+ .flex_none()
.justify_end()
.children(self.right_items().map(|item| item.to_any())),
)
@@ -26,12 +26,12 @@ use futures::{
};
use gpui::{
actions, canvas, div, impl_actions, point, px, size, Action, AnyElement, AnyModel, AnyView,
- AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow, Bounds, Context,
- Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
- GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId, ManagedView, Model,
- ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel, Render, Size,
- Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds,
- WindowContext, WindowHandle, WindowOptions,
+ AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div,
+ DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
+ FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
+ ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
+ Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+ WindowBounds, WindowContext, WindowHandle, WindowOptions,
};
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
use itertools::Itertools;
@@ -669,18 +669,6 @@ impl Workspace {
cx.defer(|this, cx| {
this.update_window_title(cx);
- // todo! @nate - these are useful for testing notifications
- // this.show_error(
- // &anyhow::anyhow!("what happens if this message is very very very very very long"),
- // cx,
- // );
-
- // this.show_notification(1, cx, |cx| {
- // cx.new_view(|_cx| {
- // simple_message_notification::MessageNotification::new(format!("Error:"))
- // .with_click_message("click here because!")
- // })
- // });
});
Workspace {
weak_self: weak_handle.clone(),
@@ -740,7 +728,7 @@ impl Workspace {
cx.spawn(|mut cx| async move {
let serialized_workspace: Option<SerializedWorkspace> =
- persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+ persistence::DB.workspace_for_roots(abs_paths.as_slice());
let paths_to_open = Arc::new(abs_paths);
@@ -860,7 +848,7 @@ impl Workspace {
self.window_edited
}
- pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
+ pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut WindowContext) {
let dock = match panel.position(cx) {
DockPosition::Left => &self.left_dock,
DockPosition::Bottom => &self.bottom_dock,
@@ -1245,7 +1233,7 @@ impl Workspace {
}
for (pane, item) in dirty_items {
let (singleton, project_entry_ids) =
- cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
+ cx.update(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
if singleton || !project_entry_ids.is_empty() {
if let Some(ix) =
pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
@@ -1319,7 +1307,7 @@ impl Workspace {
} else {
None
};
- cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))?
+ cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))?
.await?;
Ok(())
})
@@ -1500,14 +1488,14 @@ impl Workspace {
item.to_any().downcast::<I>().ok()
}
- fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
+ fn active_project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
self.active_item(cx).and_then(|item| item.project_path(cx))
}
pub fn save_active_item(
&mut self,
save_intent: SaveIntent,
- cx: &mut ViewContext<Self>,
+ cx: &mut WindowContext,
) -> Task<Result<()>> {
let project = self.project.clone();
let pane = self.active_pane();
@@ -1515,7 +1503,7 @@ impl Workspace {
let item = pane.read(cx).active_item();
let pane = pane.downgrade();
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|mut cx| async move {
if let Some(item) = item {
Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
.await
@@ -1693,6 +1681,18 @@ impl Workspace {
None
}
+ /// Open the panel of the given type
+ pub fn open_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
+ for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
+ if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
+ dock.update(cx, |dock, cx| {
+ dock.activate_panel(panel_index, cx);
+ dock.set_open(true, cx);
+ });
+ }
+ }
+ }
+
pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
let dock = dock.read(cx);
@@ -1776,7 +1776,7 @@ impl Workspace {
}
}
- pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+ pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut WindowContext) {
if let Some(text) = item.telemetry_event_text(cx) {
self.client()
.telemetry()
@@ -1858,7 +1858,7 @@ impl Workspace {
path: impl Into<ProjectPath>,
pane: Option<WeakView<Pane>>,
focus_item: bool,
- cx: &mut ViewContext<Self>,
+ cx: &mut WindowContext,
) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
let pane = pane.unwrap_or_else(|| {
self.last_active_center_pane.clone().unwrap_or_else(|| {
@@ -1870,7 +1870,7 @@ impl Workspace {
});
let task = self.load_path(path.into(), cx);
- cx.spawn(move |_, mut cx| async move {
+ cx.spawn(move |mut cx| async move {
let (project_entry_id, build_item) = task.await?;
pane.update(&mut cx, |pane, cx| {
pane.open_item(project_entry_id, focus_item, cx, build_item)
@@ -1913,7 +1913,7 @@ impl Workspace {
fn load_path(
&mut self,
path: ProjectPath,
- cx: &mut ViewContext<Self>,
+ cx: &mut WindowContext,
) -> Task<
Result<(
Option<ProjectEntryId>,
@@ -1922,9 +1922,9 @@ impl Workspace {
> {
let project = self.project().clone();
let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|mut cx| async move {
let (project_entry_id, project_item) = project_item.await?;
- let build_item = cx.update(|_, cx| {
+ let build_item = cx.update(|cx| {
cx.default_global::<ProjectItemBuilders>()
.get(&project_item.entity_type())
.ok_or_else(|| anyhow!("no item builder for project item"))
@@ -1992,7 +1992,7 @@ impl Workspace {
}
}
- pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
+ pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut WindowContext) -> bool {
let result = self.panes.iter().find_map(|pane| {
pane.read(cx)
.index_for_item(item)
@@ -2015,7 +2015,7 @@ impl Workspace {
}
}
- pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
+ pub fn activate_next_pane(&mut self, cx: &mut WindowContext) {
let panes = self.center.panes();
if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
let next_ix = (ix + 1) % panes.len();
@@ -2024,7 +2024,7 @@ impl Workspace {
}
}
- pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
+ pub fn activate_previous_pane(&mut self, cx: &mut WindowContext) {
let panes = self.center.panes();
if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
@@ -2036,7 +2036,7 @@ impl Workspace {
pub fn activate_pane_in_direction(
&mut self,
direction: SplitDirection,
- cx: &mut ViewContext<Self>,
+ cx: &mut WindowContext,
) {
if let Some(pane) = self.find_pane_in_direction(direction, cx) {
cx.focus_view(pane);
@@ -2060,7 +2060,7 @@ impl Workspace {
fn find_pane_in_direction(
&mut self,
direction: SplitDirection,
- cx: &mut ViewContext<Self>,
+ cx: &AppContext,
) -> Option<&View<Pane>> {
let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
return None;
@@ -2495,14 +2495,14 @@ impl Workspace {
.any(|state| state.leader_id == peer_id)
}
- fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
+ fn active_item_path_changed(&mut self, cx: &mut WindowContext) {
let active_entry = self.active_project_path(cx);
self.project
.update(cx, |project, cx| project.set_active_path(active_entry, cx));
self.update_window_title(cx);
}
- fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
+ fn update_window_title(&mut self, cx: &mut WindowContext) {
let project = self.project().read(cx);
let mut title = String::new();
@@ -2546,7 +2546,7 @@ impl Workspace {
cx.set_window_title(&title);
}
- fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
+ fn update_window_edited(&mut self, cx: &mut WindowContext) {
let is_edited = !self.project.read(cx).is_disconnected()
&& self
.items(cx)
@@ -2721,7 +2721,7 @@ impl Workspace {
) -> Result<()> {
let this = this.upgrade().context("workspace dropped")?;
- let item_builders = cx.update(|_, cx| {
+ let item_builders = cx.update(|cx| {
cx.default_global::<FollowableItemBuilders>()
.values()
.map(|b| b.0)
@@ -2740,7 +2740,7 @@ impl Workspace {
Err(anyhow!("missing view variant"))?;
}
for build_item in &item_builders {
- let task = cx.update(|_, cx| {
+ let task = cx.update(|cx| {
build_item(pane.clone(), this.clone(), id, &mut variant, cx)
})?;
if let Some(task) = task {
@@ -2771,7 +2771,7 @@ impl Workspace {
Ok(())
}
- fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
+ fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
let mut is_project_item = true;
let mut update = proto::UpdateActiveView::default();
@@ -2888,7 +2888,7 @@ impl Workspace {
&self,
peer_id: PeerId,
pane: &View<Pane>,
- cx: &mut ViewContext<Self>,
+ cx: &mut WindowContext,
) -> Option<View<SharedScreen>> {
let call = self.active_call()?;
let room = call.read(cx).room()?.read(cx);
@@ -3005,7 +3005,7 @@ impl Workspace {
}));
}
- fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
+ fn serialize_workspace(&self, cx: &mut WindowContext) {
fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
let (items, active) = {
let pane = pane_handle.read(cx);
@@ -3046,15 +3046,12 @@ impl Workspace {
flexes: Some(flexes.lock().clone()),
},
Member::Pane(pane_handle) => {
- SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
+ SerializedPaneGroup::Pane(serialize_pane_handle(pane_handle, cx))
}
}
}
- fn build_serialized_docks(
- this: &Workspace,
- cx: &mut ViewContext<Workspace>,
- ) -> DockStructure {
+ fn build_serialized_docks(this: &Workspace, cx: &mut WindowContext) -> DockStructure {
let left_dock = this.left_dock.read(cx);
let left_visible = left_dock.is_open();
let left_active_panel = left_dock
@@ -3121,7 +3118,7 @@ impl Workspace {
docks,
};
- cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
+ cx.spawn(|_| persistence::DB.save_workspace(serialized_workspace))
.detach();
}
}
@@ -3153,7 +3150,7 @@ impl Workspace {
center_group = Some((group, active_pane))
}
- let mut items_by_project_path = cx.update(|_, cx| {
+ let mut items_by_project_path = cx.update(|cx| {
center_items
.unwrap_or_default()
.into_iter()
@@ -3190,49 +3187,19 @@ impl Workspace {
}
let docks = serialized_workspace.docks;
- workspace.left_dock.update(cx, |dock, cx| {
- dock.set_open(docks.left.visible, cx);
- if let Some(active_panel) = docks.left.active_panel {
- if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
- dock.activate_panel(ix, cx);
- }
- }
- dock.active_panel()
- .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
- if docks.left.visible && docks.left.zoom {
- cx.focus_self()
- }
- });
- // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
- workspace.right_dock.update(cx, |dock, cx| {
- dock.set_open(docks.right.visible, cx);
- if let Some(active_panel) = docks.right.active_panel {
- if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
- dock.activate_panel(ix, cx);
- }
- }
- dock.active_panel()
- .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
-
- if docks.right.visible && docks.right.zoom {
- cx.focus_self()
- }
- });
- workspace.bottom_dock.update(cx, |dock, cx| {
- dock.set_open(docks.bottom.visible, cx);
- if let Some(active_panel) = docks.bottom.active_panel {
- if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
- dock.activate_panel(ix, cx);
- }
- }
-
- dock.active_panel()
- .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
- if docks.bottom.visible && docks.bottom.zoom {
- cx.focus_self()
- }
- });
+ let right = docks.right.clone();
+ workspace
+ .right_dock
+ .update(cx, |dock, _| dock.serialized_dock = Some(right));
+ let left = docks.left.clone();
+ workspace
+ .left_dock
+ .update(cx, |dock, _| dock.serialized_dock = Some(left));
+ let bottom = docks.bottom.clone();
+ workspace
+ .bottom_dock
+ .update(cx, |dock, _| dock.serialized_dock = Some(bottom));
cx.notify();
})?;
@@ -3362,14 +3329,11 @@ impl Workspace {
self.modal_layer.read(cx).has_active_modal()
}
- pub fn active_modal<V: ManagedView + 'static>(
- &mut self,
- cx: &ViewContext<Self>,
- ) -> Option<View<V>> {
+ pub fn active_modal<V: ManagedView + 'static>(&mut self, cx: &AppContext) -> Option<View<V>> {
self.modal_layer.read(cx).active_modal()
}
- pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
+ pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut WindowContext, build: B)
where
B: FnOnce(&mut ViewContext<V>) -> V,
{
@@ -3419,7 +3383,7 @@ fn open_items(
let restored_project_paths = restored_items
.iter()
.filter_map(|item| {
- cx.update(|_, cx| item.as_ref()?.project_path(cx))
+ cx.update(|cx| item.as_ref()?.project_path(cx))
.ok()
.flatten()
})
@@ -3557,9 +3521,14 @@ impl Render for Workspace {
.border_b()
.border_color(colors.border)
.child(
- canvas(cx.listener(|workspace, bounds, _| {
- workspace.bounds = *bounds;
- }))
+ canvas({
+ let this = cx.view().clone();
+ move |bounds, cx| {
+ this.update(cx, |this, _cx| {
+ this.bounds = *bounds;
+ })
+ }
+ })
.absolute()
.size_full(),
)
@@ -4246,7 +4215,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
// If multiple windows have unsaved changes, and need a save prompt,
// prompt in the active window before switching to a different window.
- workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
+ workspace_windows.sort_by_key(|window| window.is_active(cx) == Some(false));
let mut prompt = None;
if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
@@ -4311,7 +4280,7 @@ impl Element for DisconnectedOverlay {
fn request_layout(
&mut self,
_: Option<Self::State>,
- cx: &mut WindowContext,
+ cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let mut background = cx.theme().colors().elevated_surface_background;
background.fade_out(0.2);
@@ -4333,8 +4302,13 @@ impl Element for DisconnectedOverlay {
(overlay.request_layout(cx), overlay)
}
- fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
- cx.with_z_index(u8::MAX, |cx| {
+ fn paint(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ overlay: &mut Self::State,
+ cx: &mut ElementContext,
+ ) {
+ cx.with_z_index(u16::MAX, |cx| {
cx.add_opaque_layer(bounds);
overlay.paint(cx);
})
@@ -544,22 +544,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
let mut backtrace = backtrace
.frames()
.iter()
- .flat_map(|frame| {
- frame.symbols().iter().filter_map(|symbol| {
- let name = symbol.name()?;
- let addr = symbol.addr()? as usize;
- let position = if let (Some(path), Some(lineno)) = (
- symbol.filename().and_then(|path| path.file_name()),
- symbol.lineno(),
- ) {
- format!("{}:{}", path.to_string_lossy(), lineno)
- } else {
- "?".to_string()
- };
-
- Some(format!("{:} ({:#x}) at {}", name, addr, position))
- })
- })
+ .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
.collect::<Vec<_>>();
// Strip out leading stack frames for rust panic-handling.
@@ -32,11 +32,11 @@ use util::{
};
use uuid::Uuid;
use welcome::BaseKeymap;
+use workspace::Pane;
use workspace::{
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings,
};
-use workspace::{dock::Panel, Pane};
use zed_actions::{OpenBrowser, OpenSettings, OpenZedURL, Quit};
actions!(
@@ -178,10 +178,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
)?;
workspace_handle.update(&mut cx, |workspace, cx| {
- let (position, was_deserialized) = {
- let project_panel = project_panel.read(cx);
- (project_panel.position(cx), project_panel.was_deserialized())
- };
+ let was_deserialized = project_panel.read(cx).was_deserialized();
workspace.add_panel(project_panel, cx);
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(assistant_panel, cx);
@@ -200,7 +197,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
.map_or(false, |entry| entry.is_dir())
})
{
- workspace.toggle_dock(position, cx);
+ workspace.open_panel::<ProjectPanel>(cx);
}
cx.focus_self();
})
@@ -875,7 +872,7 @@ mod tests {
let window = cx.update(|cx| cx.windows()[0].downcast::<Workspace>().unwrap());
let window_is_edited = |window: WindowHandle<Workspace>, cx: &mut TestAppContext| {
- cx.test_window(window.into()).edited()
+ cx.update(|cx| window.read(cx).unwrap().is_edited())
};
let pane = window
.read_with(cx, |workspace, _| workspace.active_pane().clone())
@@ -19,5 +19,6 @@ export ZED_IMAGE_ID=${image_id}
target_zed_kube_cluster
envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
+kubectl -n "$environment" rollout status deployment/collab --watch
echo "deployed collab v${version} to ${environment}"
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+
+# Required dependencies for this script:
+#
+# pandas: For data manipulation and analysis.
+# matplotlib: For creating static, interactive, and animated visualizations in Python.
+# seaborn: For making statistical graphics in Python, based on matplotlib.
+
+# To install these dependencies, use the following pip command:
+# pip install pandas matplotlib seaborn
+
+# This script is designed to parse log files for performance measurements and create histograms of these measurements.
+# It expects log files to contain lines with measurements in the format "measurement: timeunit" where timeunit can be in milliseconds (ms) or microseconds (ยตs).
+# Lines that do not contain a colon ':' are skipped.
+# The script takes one or more file paths as command-line arguments, parses each log file, and then combines the data into a single DataFrame.
+# It then converts all time measurements into milliseconds, discards the original time and unit columns, and creates histograms for each unique measurement type.
+# The histograms display the distribution of times for each measurement, separated by log file, and normalized to show density rather than count.
+# To use this script, run it from the command line with the log file paths as arguments, like so:
+# python this_script.py log1.txt log2.txt ...
+# The script will then parse the provided log files and display the histograms for each type of measurement found.
+
+import pandas as pd
+import matplotlib.pyplot as plt
+import seaborn as sns
+import sys
+
+def parse_log_file(file_path):
+ data = {'measurement': [], 'time': [], 'unit': [], 'log_file': []}
+ with open(file_path, 'r') as file:
+ for line in file:
+ if ':' not in line:
+ continue
+
+ parts = line.strip().split(': ')
+ if len(parts) != 2:
+ continue
+
+ measurement, time_with_unit = parts[0], parts[1]
+ if 'ms' in time_with_unit:
+ time, unit = time_with_unit[:-2], 'ms'
+ elif 'ยตs' in time_with_unit:
+ time, unit = time_with_unit[:-2], 'ยตs'
+ else:
+ raise ValueError(f"Invalid time unit in line: {line.strip()}")
+ continue
+
+ data['measurement'].append(measurement)
+ data['time'].append(float(time))
+ data['unit'].append(unit)
+ data['log_file'].append(file_path.split('/')[-1])
+ return pd.DataFrame(data)
+
+def create_histograms(df, measurement):
+ filtered_df = df[df['measurement'] == measurement]
+ plt.figure(figsize=(12, 6))
+ sns.histplot(data=filtered_df, x='time_ms', hue='log_file', element='step', stat='density', common_norm=False, palette='bright')
+ plt.title(f'Histogram of {measurement}')
+ plt.xlabel('Time (ms)')
+ plt.ylabel('Density')
+ plt.grid(True)
+ plt.xlim(filtered_df['time_ms'].quantile(0.01), filtered_df['time_ms'].quantile(0.99))
+ plt.show()
+
+
+file_paths = sys.argv[1:]
+dfs = [parse_log_file(path) for path in file_paths]
+combined_df = pd.concat(dfs, ignore_index=True)
+combined_df['time_ms'] = combined_df.apply(lambda row: row['time'] if row['unit'] == 'ms' else row['time'] / 1000, axis=1)
+combined_df.drop(['time', 'unit'], axis=1, inplace=True)
+
+measurement_types = combined_df['measurement'].unique()
+for measurement in measurement_types:
+ create_histograms(combined_df, measurement)