Detailed changes
@@ -23,7 +23,7 @@ env:
jobs:
style:
- name: Check formatting and Clippy lints
+ name: Check formatting, Clippy lints, and spelling
runs-on:
- self-hosted
- test
@@ -38,6 +38,13 @@ jobs:
- 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
@@ -9,6 +9,7 @@
/styles/src/types/zed.ts
/crates/theme/schemas/theme.json
/crates/collab/static/styles.css
+/crates/collab/.admins.json
/vendor/bin
/assets/themes/*.json
/assets/*licenses.md
@@ -54,4 +54,4 @@ We're happy to pair with you to help you learn the codebase and get your contrib
Reviewing code in a pull request, after the fact, is hard and tedious - the team generally likes to build trust and review code through pair programming.
We'd prefer have conversations about the code, through Zed, while it is being written, so decisions can be made in real-time and less time is spent on fixing things after the fact. Ideally, GitHub is only used to merge code that has already been discussed and reviewed in Zed.
-Remeber that smaller, incremental PRs are easier to review and merge than large PRs.
+Remember that smaller, incremental PRs are easier to review and merge than large PRs.
@@ -1,4 +1,2 @@
-web: cd ../zed.dev && PORT=3000 npm run dev
collab: cd crates/collab && RUST_LOG=${RUST_LOG:-warn,collab=info} cargo run serve
livekit: livekit-server --dev
-postgrest: postgrest crates/collab/admin_api.conf
@@ -81,8 +81,8 @@ impl PromptChain {
pub fn generate(&self, truncate: bool) -> anyhow::Result<(String, usize)> {
// Argsort based on Prompt Priority
- let seperator = "\n";
- let seperator_tokens = self.args.model.count_tokens(seperator)?;
+ let separator = "\n";
+ let separator_tokens = self.args.model.count_tokens(separator)?;
let mut sorted_indices = (0..self.templates.len()).collect::<Vec<_>>();
sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0));
@@ -104,7 +104,7 @@ impl PromptChain {
prompts[idx] = template_prompt;
if let Some(remaining_tokens) = tokens_outstanding {
- let new_tokens = prompt_token_count + seperator_tokens;
+ let new_tokens = prompt_token_count + separator_tokens;
tokens_outstanding = if remaining_tokens > new_tokens {
Some(remaining_tokens - new_tokens)
} else {
@@ -117,9 +117,9 @@ impl PromptChain {
prompts.retain(|x| x != "");
- let full_prompt = prompts.join(seperator);
+ let full_prompt = prompts.join(separator);
let total_token_count = self.args.model.count_tokens(&full_prompt)?;
- anyhow::Ok((prompts.join(seperator), total_token_count))
+ anyhow::Ok((prompts.join(separator), total_token_count))
}
}
@@ -68,7 +68,7 @@ impl PromptTemplate for RepositoryContext {
let mut prompt = String::new();
let mut remaining_tokens = max_token_length.clone();
- let seperator_token_length = args.model.count_tokens("\n")?;
+ let separator_token_length = args.model.count_tokens("\n")?;
for snippet in &args.snippets {
let mut snippet_prompt = template.to_string();
let content = snippet.to_string();
@@ -79,9 +79,9 @@ impl PromptTemplate for RepositoryContext {
if let Some(tokens_left) = remaining_tokens {
if tokens_left >= token_count {
writeln!(prompt, "{snippet_prompt}").unwrap();
- remaining_tokens = if tokens_left >= (token_count + seperator_token_length)
+ remaining_tokens = if tokens_left >= (token_count + separator_token_length)
{
- Some(tokens_left - token_count - seperator_token_length)
+ Some(tokens_left - token_count - separator_token_length)
} else {
Some(0)
};
@@ -273,7 +273,7 @@ impl CompletionProvider for OpenAICompletionProvider {
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
// Currently the CompletionRequest for OpenAI, includes a 'model' parameter
// This means that the model is determined by the CompletionRequest and not the CompletionProvider,
- // which is currently model based, due to the langauge model.
+ // which is currently model based, due to the language model.
// At some point in the future we should rectify this.
let credential = self.credential.read().clone();
let request = stream_completion(credential, self.executor.clone(), prompt);
@@ -19,12 +19,13 @@ use chrono::{DateTime, Local};
use client::telemetry::AssistantKind;
use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{
+ actions::{MoveDown, MoveUp},
display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, ToDisplayPoint,
},
- scroll::autoscroll::{Autoscroll, AutoscrollStrategy},
- Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MoveDown, MoveUp, MultiBufferSnapshot,
- ToOffset, ToPoint,
+ scroll::{Autoscroll, AutoscrollStrategy},
+ Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBufferSnapshot, ToOffset,
+ ToPoint,
};
use fs::Fs;
use futures::StreamExt;
@@ -479,7 +480,7 @@ impl AssistantPanel {
fn cancel_last_inline_assist(
workspace: &mut Workspace,
- _: &editor::Cancel,
+ _: &editor::actions::Cancel,
cx: &mut ViewContext<Workspace>,
) {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
@@ -891,7 +892,7 @@ impl AssistantPanel {
}
}
- fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
+ fn handle_editor_cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
if !search_bar.read(cx).is_dismissed() {
search_bar.update(cx, |search_bar, cx| {
@@ -1148,7 +1149,7 @@ impl Render for AssistantPanel {
|panel, cx| panel.toolbar.read(cx).item_of_type::<BufferSearchBar>(),
cx,
);
- BufferSearchBar::register_inner(&mut registrar);
+ BufferSearchBar::register(&mut registrar);
registrar.into_div()
} else {
div()
@@ -2158,7 +2159,7 @@ impl ConversationEditor {
}
}
- fn cancel_last_assist(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
+ fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
if !self
.conversation
.update(cx, |conversation, _| conversation.cancel_last_assist())
@@ -2311,8 +2312,7 @@ impl ConversationEditor {
}
});
- div()
- .h_flex()
+ h_flex()
.id(("message_header", message_id.0))
.h_11()
.relative()
@@ -2328,6 +2328,7 @@ impl ConversationEditor {
.add_suffix(true)
.to_string(),
)
+ .size(LabelSize::XSmall)
.color(Color::Muted),
)
.children(
@@ -2417,7 +2418,7 @@ impl ConversationEditor {
}
}
- fn copy(&mut self, _: &editor::Copy, cx: &mut ViewContext<Self>) {
+ fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
let editor = self.editor.read(cx);
let conversation = self.conversation.read(cx);
if editor.selections.count() == 1 {
@@ -2828,7 +2829,7 @@ impl InlineAssistant {
cx.notify();
}
- fn cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
+ fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(InlineAssistantEvent::Canceled);
}
@@ -2917,7 +2918,7 @@ impl InlineAssistant {
let semantic_permissioned = self.semantic_permissioned(cx);
if let Some(semantic_index) = SemanticIndex::global(cx) {
cx.spawn(|_, mut cx| async move {
- // This has to be updated to accomodate for semantic_permissions
+ // This has to be updated to accommodate for semantic_permissions
if semantic_permissioned.await.unwrap_or(false) {
semantic_index
.update(&mut cx, |index, cx| index.index_project(project, cx))?
@@ -93,7 +93,9 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
workspace.register_action(|_, action: &Check, cx| check(action, cx));
- workspace.register_action(|_, action, cx| view_release_notes(action, cx));
+ 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")
@@ -140,24 +142,23 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
}
}
-pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
- if let Some(auto_updater) = AutoUpdater::get(cx) {
+pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<()> {
+ let auto_updater = AutoUpdater::get(cx)?;
+ let release_channel = cx.try_global::<ReleaseChannel>()?;
+
+ if matches!(
+ release_channel,
+ ReleaseChannel::Stable | ReleaseChannel::Preview
+ ) {
let auto_updater = auto_updater.read(cx);
let server_url = &auto_updater.server_url;
+ let release_channel = release_channel.dev_name();
let current_version = auto_updater.current_version;
- if cx.has_global::<ReleaseChannel>() {
- match cx.global::<ReleaseChannel>() {
- ReleaseChannel::Dev => {}
- ReleaseChannel::Nightly => {}
- ReleaseChannel::Preview => {
- cx.open_url(&format!("{server_url}/releases/preview/{current_version}"))
- }
- ReleaseChannel::Stable => {
- cx.open_url(&format!("{server_url}/releases/stable/{current_version}"))
- }
- }
- }
+ let url = format!("{server_url}/releases/{release_channel}/{current_version}");
+ cx.open_url(&url);
}
+
+ None
}
pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
@@ -257,11 +258,13 @@ impl AutoUpdater {
"{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
);
cx.update(|cx| {
- if cx.has_global::<ReleaseChannel>() {
- if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
- url_string += "&";
- url_string += param;
- }
+ if let Some(param) = cx
+ .try_global::<ReleaseChannel>()
+ .map(|release_channel| release_channel.release_query_param())
+ .flatten()
+ {
+ url_string += "&";
+ url_string += param;
}
})?;
@@ -313,8 +316,8 @@ impl AutoUpdater {
let (installation_id, release_channel, telemetry) = cx.update(|cx| {
let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
let release_channel = cx
- .has_global::<ReleaseChannel>()
- .then(|| cx.global::<ReleaseChannel>().display_name());
+ .try_global::<ReleaseChannel>()
+ .map(|release_channel| release_channel.display_name());
let telemetry = TelemetrySettings::get_global(cx).metrics;
(installation_id, release_channel, telemetry)
@@ -40,7 +40,9 @@ impl Render for UpdateNotification {
.id("notes")
.child(Label::new("View the release notes"))
.cursor_pointer()
- .on_click(|_, cx| crate::view_release_notes(&Default::default(), cx)),
+ .on_click(|_, cx| {
+ crate::view_release_notes(&Default::default(), cx);
+ }),
)
}
}
@@ -150,11 +150,9 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
impl Telemetry {
pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> {
- let release_channel = if cx.has_global::<ReleaseChannel>() {
- Some(cx.global::<ReleaseChannel>().display_name())
- } else {
- None
- };
+ let release_channel = cx
+ .try_global::<ReleaseChannel>()
+ .map(|release_channel| release_channel.display_name());
TelemetrySettings::register(cx);
@@ -0,0 +1 @@
+["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"]
@@ -1,7 +1,11 @@
-use collab::{db, executor::Executor};
+use collab::{
+ db::{self, NewUserParams},
+ env::load_dotenv,
+ executor::Executor,
+};
use db::{ConnectOptions, Database};
use serde::{de::DeserializeOwned, Deserialize};
-use std::fmt::Write;
+use std::{fmt::Write, fs};
#[derive(Debug, Deserialize)]
struct GitHubUser {
@@ -12,90 +16,75 @@ struct GitHubUser {
#[tokio::main]
async fn main() {
+ load_dotenv().expect("failed to load .env.toml file");
+
+ let mut admin_logins =
+ load_admins("./.admins.default.json").expect("failed to load default admins file");
+ if let Ok(other_admins) = load_admins("./.admins.json") {
+ admin_logins.extend(other_admins);
+ }
+
let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var");
let db = Database::new(ConnectOptions::new(database_url), Executor::Production)
.await
.expect("failed to connect to postgres database");
- let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var");
let client = reqwest::Client::new();
- let mut current_user =
- fetch_github::<GitHubUser>(&client, &github_token, "https://api.github.com/user").await;
- current_user
- .email
- .get_or_insert_with(|| "placeholder@example.com".to_string());
- let staff_users = fetch_github::<Vec<GitHubUser>>(
- &client,
- &github_token,
- "https://api.github.com/orgs/zed-industries/teams/staff/members",
- )
- .await;
-
- let mut zed_users = Vec::new();
- zed_users.push((current_user, true));
- zed_users.extend(staff_users.into_iter().map(|user| (user, true)));
+ // Create admin users for all of the users in `.admins.toml` or `.admins.default.toml`.
+ for admin_login in admin_logins {
+ let user = fetch_github::<GitHubUser>(
+ &client,
+ &format!("https://api.github.com/users/{admin_login}"),
+ )
+ .await;
+ db.create_user(
+ &user.email.unwrap_or(format!("{admin_login}@example.com")),
+ true,
+ NewUserParams {
+ github_login: user.login,
+ github_user_id: user.id,
+ },
+ )
+ .await
+ .expect("failed to create admin user");
+ }
- let user_count = db
+ // Fetch 100 other random users from GitHub and insert them into the database.
+ let mut user_count = db
.get_all_users(0, 200)
.await
.expect("failed to load users from db")
.len();
- if user_count < 100 {
- let mut last_user_id = None;
- for _ in 0..10 {
- let mut uri = "https://api.github.com/users?per_page=100".to_string();
- if let Some(last_user_id) = last_user_id {
- write!(&mut uri, "&since={}", last_user_id).unwrap();
- }
- let users = fetch_github::<Vec<GitHubUser>>(&client, &github_token, &uri).await;
- if let Some(last_user) = users.last() {
- last_user_id = Some(last_user.id);
- zed_users.extend(users.into_iter().map(|user| (user, false)));
- } else {
- break;
- }
+ let mut last_user_id = None;
+ while user_count < 100 {
+ let mut uri = "https://api.github.com/users?per_page=100".to_string();
+ if let Some(last_user_id) = last_user_id {
+ write!(&mut uri, "&since={}", last_user_id).unwrap();
}
- }
+ let users = fetch_github::<Vec<GitHubUser>>(&client, &uri).await;
- for (github_user, admin) in zed_users {
- if db
- .get_user_by_github_login(&github_user.login)
+ for github_user in users {
+ last_user_id = Some(github_user.id);
+ user_count += 1;
+ db.get_or_create_user_by_github_account(
+ &github_user.login,
+ Some(github_user.id),
+ github_user.email.as_deref(),
+ )
.await
- .expect("failed to fetch user")
- .is_none()
- {
- if admin {
- db.create_user(
- &format!("{}@zed.dev", github_user.login),
- admin,
- db::NewUserParams {
- github_login: github_user.login,
- github_user_id: github_user.id,
- },
- )
- .await
- .expect("failed to insert user");
- } else {
- db.get_or_create_user_by_github_account(
- &github_user.login,
- Some(github_user.id),
- github_user.email.as_deref(),
- )
- .await
- .expect("failed to insert user");
- }
+ .expect("failed to insert user");
}
}
}
-async fn fetch_github<T: DeserializeOwned>(
- client: &reqwest::Client,
- access_token: &str,
- url: &str,
-) -> T {
+fn load_admins(path: &str) -> anyhow::Result<Vec<String>> {
+ let file_content = fs::read_to_string(path)?;
+ Ok(serde_json::from_str(&file_content)?)
+}
+
+async fn fetch_github<T: DeserializeOwned>(client: &reqwest::Client, url: &str) -> T {
let response = client
.get(url)
- .bearer_auth(&access_token)
.header("user-agent", "zed")
.send()
.await
@@ -152,7 +152,7 @@ impl Database {
.await?;
// If the buffer epoch hasn't changed since the client lost
- // connection, then the client's buffer can be syncronized with
+ // connection, then the client's buffer can be synchronized with
// the server's buffer.
if buffer.epoch as u64 != client_buffer.epoch {
log::info!("can't rejoin buffer, epoch has changed");
@@ -970,7 +970,7 @@ fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::
.collect()
}
-// This is currently a manual copy of the deserialization code in the client's langauge crate
+// This is currently a manual copy of the deserialization code in the client's language crate
pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operation> {
match operation.variant? {
proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation {
@@ -20,7 +20,11 @@ impl Database {
})
.on_conflict(
OnConflict::column(user::Column::GithubLogin)
- .update_column(user::Column::GithubLogin)
+ .update_columns([
+ user::Column::Admin,
+ user::Column::EmailAddress,
+ user::Column::GithubUserId,
+ ])
.to_owned(),
)
.exec_with_returning(&*tx)
@@ -3116,7 +3116,7 @@ async fn leave_channel_chat(request: proto::LeaveChannelChat, session: Session)
Ok(())
}
-/// Retrive the chat history for a channel
+/// Retrieve the chat history for a channel
async fn get_channel_messages(
request: proto::GetChannelMessages,
response: Response<proto::GetChannelMessages>,
@@ -203,7 +203,7 @@ async fn test_core_channels(
executor.run_until_parked();
// Observe that client B is now an admin of channel A, and that
- // their admin priveleges extend to subchannels of channel A.
+ // their admin privileges extend to subchannels of channel A.
assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
assert_channels(
client_b.channel_store(),
@@ -8,9 +8,11 @@ use std::{
use call::ActiveCall;
use editor::{
+ actions::{
+ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Redo, Rename, ToggleCodeActions, Undo,
+ },
test::editor_test_context::{AssertionContextManager, EditorTestContext},
- ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToggleCodeActions,
- Undo,
+ Editor,
};
use futures::StreamExt;
use gpui::{TestAppContext, VisualContext, VisualTestContext};
@@ -185,31 +187,27 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
- let window_a = cx_a.add_empty_window();
- let editor_a =
- window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
+ let cx_a = cx_a.add_empty_window();
+ let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let mut editor_cx_a = EditorTestContext {
- cx: VisualTestContext::from_window(window_a, cx_a),
- window: window_a.into(),
+ cx: cx_a.clone(),
+ window: cx_a.handle(),
editor: editor_a,
assertion_cx: AssertionContextManager::new(),
};
- let window_b = cx_b.add_empty_window();
- let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
-
+ let cx_b = cx_b.add_empty_window();
// Open a buffer as client B
let buffer_b = project_b
- .update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
+ .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
- let editor_b = window_b.build_view(&mut cx_b, |cx| {
- Editor::for_buffer(buffer_b, Some(project_b), cx)
- });
+ let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
+
let mut editor_cx_b = EditorTestContext {
- cx: cx_b,
- window: window_b.into(),
+ cx: cx_b.clone(),
+ window: cx_b.handle(),
editor: editor_b,
assertion_cx: AssertionContextManager::new(),
};
@@ -221,7 +219,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
editor_cx_b.set_selections_state(indoc! {"
Some textΛ
"});
- editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
+ editor_cx_a
+ .update_editor(|editor, cx| editor.newline_above(&editor::actions::NewlineAbove, cx));
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
Λ
@@ -241,7 +240,8 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
Some textΛ
"});
- editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
+ editor_cx_a
+ .update_editor(|editor, cx| editor.newline_below(&editor::actions::NewlineBelow, cx));
executor.run_until_parked();
editor_cx_a.assert_editor_state(indoc! {"
@@ -311,10 +311,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let window_b = cx_b.add_empty_window();
- let editor_b = window_b.build_view(cx_b, |cx| {
- Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
- });
+ let cx_b = cx_b.add_empty_window();
+ let editor_b =
+ cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
cx_a.background_executor.run_until_parked();
@@ -323,10 +322,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
assert!(!buffer.completion_triggers().is_empty())
});
- let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
-
// Type a completion trigger character as the guest.
- editor_b.update(&mut cx_b, |editor, cx| {
+ editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(".", cx);
});
@@ -392,8 +389,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
});
// Confirm a completion on the guest.
-
- editor_b.update(&mut cx_b, |editor, cx| {
+ editor_b.update(cx_b, |editor, cx| {
assert!(editor.context_menu_visible());
editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
@@ -431,7 +427,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
);
});
- buffer_b.read_with(&mut cx_b, |buffer, _| {
+ buffer_b.read_with(cx_b, |buffer, _| {
assert_eq!(
buffer.text(),
"use d::SomeTrait;\nfn main() { a.first_method() }"
@@ -960,7 +956,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext,
) {
let executor = cx_a.executor();
- let window_b = cx_b.add_empty_window();
+ let cx_b = cx_b.add_empty_window();
let mut server = TestServer::start(executor.clone()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -1075,7 +1071,7 @@ async fn test_share_project(
.await
.unwrap();
- let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+ let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
executor.run_until_parked();
@@ -1089,8 +1085,7 @@ async fn test_share_project(
});
// Edit the buffer as client B and see that edit as client A.
- let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
- editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx));
+ editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
executor.run_until_parked();
buffer_a.read_with(cx_a, |buffer, _| {
@@ -1099,7 +1094,7 @@ async fn test_share_project(
// Client B can invite client C on a project shared by client A.
active_call_b
- .update(&mut cx_b, |call, cx| {
+ .update(cx_b, |call, cx| {
call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
})
.await
@@ -1190,18 +1185,14 @@ async fn test_on_input_format_from_host_to_guest(
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let window_a = cx_a.add_empty_window();
- let editor_a = window_a
- .update(cx_a, |_, cx| {
- cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
- })
- .unwrap();
+ let cx_a = cx_a.add_empty_window();
+ let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
// Receive an OnTypeFormatting request as the host's language server.
- // Return some formattings from the host's language server.
+ // Return some formatting from the host's language server.
fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|params, _| async move {
assert_eq!(
@@ -1220,16 +1211,15 @@ async fn test_on_input_format_from_host_to_guest(
},
);
- // Open the buffer on the guest and see that the formattings worked
+ // Open the buffer on the guest and see that the formatting worked
let buffer_b = project_b
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let mut cx_a = VisualTestContext::from_window(window_a, cx_a);
// Type a on type formatting trigger character as the guest.
cx_a.focus_view(&editor_a);
- editor_a.update(&mut cx_a, |editor, cx| {
+ editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(">", cx);
});
@@ -1241,7 +1231,7 @@ async fn test_on_input_format_from_host_to_guest(
});
// Undo should remove LSP edits first
- editor_a.update(&mut cx_a, |editor, cx| {
+ editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a>~< }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a> }");
@@ -1252,7 +1242,7 @@ async fn test_on_input_format_from_host_to_guest(
assert_eq!(buffer.text(), "fn main() { a> }")
});
- editor_a.update(&mut cx_a, |editor, cx| {
+ editor_a.update(cx_a, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a> }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
@@ -1323,23 +1313,21 @@ async fn test_on_input_format_from_guest_to_host(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
- let window_b = cx_b.add_empty_window();
- let editor_b = window_b.build_view(cx_b, |cx| {
- Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
- });
+ let cx_b = cx_b.add_empty_window();
+ let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx));
let fake_language_server = fake_language_servers.next().await.unwrap();
executor.run_until_parked();
- let mut cx_b = VisualTestContext::from_window(window_b, cx_b);
+
// Type a on type formatting trigger character as the guest.
cx_b.focus_view(&editor_b);
- editor_b.update(&mut cx_b, |editor, cx| {
+ editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input(":", cx);
});
// Receive an OnTypeFormatting request as the host's language server.
- // Return some formattings from the host's language server.
+ // Return some formatting from the host's language server.
executor.start_waiting();
fake_language_server
.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
@@ -1362,7 +1350,7 @@ async fn test_on_input_format_from_guest_to_host(
.unwrap();
executor.finish_waiting();
- // Open the buffer on the host and see that the formattings worked
+ // Open the buffer on the host and see that the formatting worked
let buffer_a = project_a
.update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
@@ -1374,7 +1362,7 @@ async fn test_on_input_format_from_guest_to_host(
});
// Undo should remove LSP edits first
- editor_b.update(&mut cx_b, |editor, cx| {
+ editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a:~: }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a: }");
@@ -1385,7 +1373,7 @@ async fn test_on_input_format_from_guest_to_host(
assert_eq!(buffer.text(), "fn main() { a: }")
});
- editor_b.update(&mut cx_b, |editor, cx| {
+ editor_b.update(cx_b, |editor, cx| {
assert_eq!(editor.text(cx), "fn main() { a: }");
editor.undo(&Undo, cx);
assert_eq!(editor.text(cx), "fn main() { a }");
@@ -1836,7 +1824,7 @@ async fn test_inlay_hint_refresh_is_forwarded(
assert_eq!(
inlay_cache.version(),
1,
- "Should update cache verison after first hints"
+ "Should update cache version after first hints"
);
});
@@ -249,7 +249,7 @@ async fn test_basic_following(
executor.run_until_parked();
cx_c.cx.update(|_| {});
- weak_workspace_c.assert_dropped();
+ weak_workspace_c.assert_released();
// Clients A and B see that client B is following A, and client C is not present in the followers.
executor.run_until_parked();
@@ -1229,7 +1229,9 @@ async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppCont
});
// When client B moves, it automatically stops following client A.
- editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
+ editor_b2.update(cx_b, |editor, cx| {
+ editor.move_right(&editor::actions::MoveRight, cx)
+ });
assert_eq!(
workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
None
@@ -1735,6 +1737,11 @@ async fn test_following_into_excluded_file(
vec![18..17]
);
+ editor_for_excluded_a.update(cx_a, |editor, cx| {
+ editor.select_right(&Default::default(), cx);
+ });
+ executor.run_until_parked();
+
// Changes from B to the excluded file are replicated in A's editor
editor_for_excluded_b.update(cx_b, |editor, cx| {
editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
@@ -1743,7 +1750,7 @@ async fn test_following_into_excluded_file(
editor_for_excluded_a.update(cx_a, |editor, cx| {
assert_eq!(
editor.text(cx),
- "new commit messag\nCo-Authored-By: B <b@b.b>"
+ "new commit message\nCo-Authored-By: B <b@b.b>"
);
});
}
@@ -111,7 +111,7 @@ impl ChannelModal {
.detach();
}
- fn set_channel_visiblity(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
+ fn set_channel_visibility(&mut self, selection: &Selection, cx: &mut ViewContext<Self>) {
self.channel_store.update(cx, |channel_store, cx| {
channel_store
.set_channel_visibility(
@@ -189,7 +189,7 @@ impl Render for ChannelModal {
ui::Selection::Unselected
},
)
- .on_click(cx.listener(Self::set_channel_visiblity)),
+ .on_click(cx.listener(Self::set_channel_visibility)),
)
.child(Label::new("Public").size(LabelSize::Small)),
)
@@ -486,7 +486,7 @@ impl CollabTitlebarItem {
Avatar::new(user.avatar_uri.clone())
.grayscale(!is_present)
.border_color(if is_speaking {
- cx.theme().status().info_border
+ cx.theme().status().info
} else {
// We draw the border in a transparent color rather to avoid
// the layout shift that would come with adding/removing the border.
@@ -308,11 +308,7 @@ impl EventEmitter<Event> for Copilot {}
impl Copilot {
pub fn global(cx: &AppContext) -> Option<Model<Self>> {
- if cx.has_global::<Model<Self>>() {
- Some(cx.global::<Model<Self>>().clone())
- } else {
- None
- }
+ cx.try_global::<Model<Self>>().map(|model| model.clone())
}
fn start(
@@ -373,10 +369,11 @@ impl Copilot {
#[cfg(any(test, feature = "test-support"))]
pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
+ use lsp::FakeLanguageServer;
use node_runtime::FakeNodeRuntime;
let (server, fake_server) =
- LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
+ FakeLanguageServer::new("copilot".into(), Default::default(), cx.to_async());
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
let node_runtime = FakeNodeRuntime::new();
let this = cx.new_model(|cx| Self {
@@ -1,7 +1,7 @@
use crate::sign_in::CopilotCodeVerification;
use anyhow::Result;
use copilot::{Copilot, SignOut, Status};
-use editor::{scroll::autoscroll::Autoscroll, Editor};
+use editor::{scroll::Autoscroll, Editor};
use fs::Fs;
use gpui::{
div, Action, AnchorCorner, AppContext, AsyncWindowContext, Entity, IntoElement, ParentElement,
@@ -96,7 +96,7 @@ impl CopilotCodeVerification {
.items_center()
.child(Headline::new("Use Github Copilot in Zed.").size(HeadlineSize::Large))
.child(
- Label::new("Using Copilot requres an active subscription on Github.")
+ Label::new("Using Copilot requires an active subscription on Github.")
.color(Color::Muted),
)
.child(Self::render_device_code(data, cx))
@@ -139,7 +139,7 @@ impl CopilotCodeVerification {
"You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.",
).color(Color::Warning))
.child(
- Button::new("copilot-subscribe-button", "Subscibe on Github")
+ Button::new("copilot-subscribe-button", "Subscribe on Github")
.full_width()
.on_click(|_, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
)
@@ -8,7 +8,7 @@ use editor::{
diagnostic_block_renderer,
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
highlight_diagnostic_message,
- scroll::autoscroll::Autoscroll,
+ scroll::Autoscroll,
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use futures::future::try_join_all;
@@ -80,7 +80,7 @@ impl Render for DiagnosticIndicator {
Button::new("diagnostic_message", message)
.label_size(LabelSize::Small)
.tooltip(|cx| {
- Tooltip::for_action("Next Diagnostic", &editor::GoToDiagnostic, cx)
+ Tooltip::for_action("Next Diagnostic", &editor::actions::GoToDiagnostic, cx)
})
.on_click(cx.listener(|this, _, cx| {
this.go_to_next_diagnostic(cx);
@@ -0,0 +1,218 @@
+//! This module contains all actions supported by [`Editor`].
+use super::*;
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct SelectNext {
+ #[serde(default)]
+ pub replace_newest: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct SelectPrevious {
+ #[serde(default)]
+ pub replace_newest: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct SelectAllMatches {
+ #[serde(default)]
+ pub replace_newest: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct SelectToBeginningOfLine {
+ #[serde(default)]
+ pub(super) stop_at_soft_wraps: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct MovePageUp {
+ #[serde(default)]
+ pub(super) center_cursor: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct MovePageDown {
+ #[serde(default)]
+ pub(super) center_cursor: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct SelectToEndOfLine {
+ #[serde(default)]
+ pub(super) stop_at_soft_wraps: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct ToggleCodeActions {
+ #[serde(default)]
+ pub deployed_from_indicator: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct ConfirmCompletion {
+ #[serde(default)]
+ pub item_ix: Option<usize>,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct ConfirmCodeAction {
+ #[serde(default)]
+ pub item_ix: Option<usize>,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct ToggleComments {
+ #[serde(default)]
+ pub advance_downwards: bool,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct FoldAt {
+ pub buffer_row: u32,
+}
+
+#[derive(PartialEq, Clone, Deserialize, Default)]
+pub struct UnfoldAt {
+ pub buffer_row: u32,
+}
+impl_actions!(
+ editor,
+ [
+ SelectNext,
+ SelectPrevious,
+ SelectAllMatches,
+ SelectToBeginningOfLine,
+ MovePageUp,
+ MovePageDown,
+ SelectToEndOfLine,
+ ToggleCodeActions,
+ ConfirmCompletion,
+ ConfirmCodeAction,
+ ToggleComments,
+ FoldAt,
+ UnfoldAt
+ ]
+);
+
+gpui::actions!(
+ editor,
+ [
+ AddSelectionAbove,
+ AddSelectionBelow,
+ Backspace,
+ Cancel,
+ ConfirmRename,
+ ContextMenuFirst,
+ ContextMenuLast,
+ ContextMenuNext,
+ ContextMenuPrev,
+ ConvertToKebabCase,
+ ConvertToLowerCamelCase,
+ ConvertToLowerCase,
+ ConvertToSnakeCase,
+ ConvertToTitleCase,
+ ConvertToUpperCamelCase,
+ ConvertToUpperCase,
+ Copy,
+ CopyHighlightJson,
+ CopyPath,
+ CopyRelativePath,
+ Cut,
+ CutToEndOfLine,
+ Delete,
+ DeleteLine,
+ DeleteToBeginningOfLine,
+ DeleteToEndOfLine,
+ DeleteToNextSubwordEnd,
+ DeleteToNextWordEnd,
+ DeleteToPreviousSubwordStart,
+ DeleteToPreviousWordStart,
+ DuplicateLine,
+ ExpandMacroRecursively,
+ FindAllReferences,
+ Fold,
+ FoldSelectedRanges,
+ Format,
+ GoToDefinition,
+ GoToDefinitionSplit,
+ GoToDiagnostic,
+ GoToHunk,
+ GoToPrevDiagnostic,
+ GoToPrevHunk,
+ GoToTypeDefinition,
+ GoToTypeDefinitionSplit,
+ HalfPageDown,
+ HalfPageUp,
+ Hover,
+ Indent,
+ JoinLines,
+ LineDown,
+ LineUp,
+ MoveDown,
+ MoveLeft,
+ MoveLineDown,
+ MoveLineUp,
+ MoveRight,
+ MoveToBeginning,
+ MoveToBeginningOfLine,
+ MoveToEnclosingBracket,
+ MoveToEnd,
+ MoveToEndOfLine,
+ MoveToEndOfParagraph,
+ MoveToNextSubwordEnd,
+ MoveToNextWordEnd,
+ MoveToPreviousSubwordStart,
+ MoveToPreviousWordStart,
+ MoveToStartOfParagraph,
+ MoveUp,
+ Newline,
+ NewlineAbove,
+ NewlineBelow,
+ NextScreen,
+ OpenExcerpts,
+ Outdent,
+ PageDown,
+ PageUp,
+ Paste,
+ Redo,
+ RedoSelection,
+ Rename,
+ RestartLanguageServer,
+ RevealInFinder,
+ ReverseLines,
+ ScrollCursorBottom,
+ ScrollCursorCenter,
+ ScrollCursorTop,
+ SelectAll,
+ SelectDown,
+ SelectLargerSyntaxNode,
+ SelectLeft,
+ SelectLine,
+ SelectRight,
+ SelectSmallerSyntaxNode,
+ SelectToBeginning,
+ SelectToEnd,
+ SelectToEndOfParagraph,
+ SelectToNextSubwordEnd,
+ SelectToNextWordEnd,
+ SelectToPreviousSubwordStart,
+ SelectToPreviousWordStart,
+ SelectToStartOfParagraph,
+ SelectUp,
+ ShowCharacterPalette,
+ ShowCompletions,
+ ShuffleLines,
+ SortLinesCaseInsensitive,
+ SortLinesCaseSensitive,
+ SplitSelectionIntoLines,
+ Tab,
+ TabPrev,
+ ToggleInlayHints,
+ ToggleSoftWrap,
+ Transpose,
+ Undo,
+ UndoSelection,
+ UnfoldLines,
+ ]
+);
@@ -1,3 +1,22 @@
+//! This module defines where the text should be displayed in an [`Editor`][Editor].
+//!
+//! Not literally though - rendering, layout and all that jazz is a responsibility of [`EditorElement`][EditorElement].
+//! Instead, [`DisplayMap`] decides where Inlays/Inlay hints are displayed, when
+//! to apply a soft wrap, where to add fold indicators, whether there are any tabs in the buffer that
+//! we display as spaces and where to display custom blocks (like diagnostics).
+//! Seems like a lot? That's because it is. [`DisplayMap`] is conceptually made up
+//! of several smaller structures that form a hierarchy (starting at the bottom):
+//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
+//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
+//! - [`TabMap`] that keeps track of hard tabs in a buffer.
+//! - [`WrapMap`] that handles soft wrapping.
+//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
+//! - [`DisplayMap`] that adds background highlights to the regions of text.
+//! Each one of those builds on top of preceding map.
+//!
+//! [Editor]: crate::Editor
+//! [EditorElement]: crate::element::EditorElement
+
mod block_map;
mod fold_map;
mod inlay_map;
@@ -30,7 +49,8 @@ pub use block_map::{
};
pub use self::fold_map::{Fold, FoldPoint};
-pub use self::inlay_map::{Inlay, InlayOffset, InlayPoint};
+pub use self::inlay_map::{InlayOffset, InlayPoint};
+pub(crate) use inlay_map::Inlay;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FoldStatus {
@@ -220,7 +240,7 @@ impl DisplayMap {
.insert(Some(type_id), Arc::new((style, ranges)));
}
- pub fn highlight_inlays(
+ pub(crate) fn highlight_inlays(
&mut self,
type_id: TypeId,
highlights: Vec<InlayHighlight>,
@@ -258,11 +278,11 @@ impl DisplayMap {
.update(cx, |map, cx| map.set_wrap_width(width, cx))
}
- pub fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
+ pub(crate) fn current_inlays(&self) -> impl Iterator<Item = &Inlay> {
self.inlay_map.current_inlays()
}
- pub fn splice_inlays(
+ pub(crate) fn splice_inlays(
&mut self,
to_remove: Vec<InlayId>,
to_insert: Vec<Inlay>,
@@ -306,7 +326,7 @@ impl DisplayMap {
}
#[derive(Debug, Default)]
-pub struct Highlights<'a> {
+pub(crate) struct Highlights<'a> {
pub text_highlights: Option<&'a TextHighlights>,
pub inlay_highlights: Option<&'a InlayHighlights>,
pub inlay_highlight_style: Option<HighlightStyle>,
@@ -880,8 +900,9 @@ impl DisplaySnapshot {
self.text_highlights.get(&Some(type_id)).cloned()
}
+ #[allow(unused)]
#[cfg(any(test, feature = "test-support"))]
- pub fn inlay_highlights<Tag: ?Sized + 'static>(
+ pub(crate) fn inlay_highlights<Tag: ?Sized + 'static>(
&self,
) -> Option<&HashMap<InlayId, (HighlightStyle, InlayHighlight)>> {
let type_id = TypeId::of::<Tag>();
@@ -969,24 +990,6 @@ impl ToDisplayPoint for Anchor {
}
}
-pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator<Item = u32> {
- let max_row = display_map.max_point().row();
- let start_row = display_row + 1;
- let mut current = None;
- std::iter::from_fn(move || {
- if current == None {
- current = Some(start_row);
- } else {
- current = Some(current.unwrap() + 1)
- }
- if current.unwrap() > max_row {
- None
- } else {
- current
- }
- })
-}
-
#[cfg(test)]
pub mod tests {
use super::*;
@@ -582,7 +582,7 @@ impl BlockSnapshot {
.collect()
}
- pub fn chunks<'a>(
+ pub(crate) fn chunks<'a>(
&'a self,
rows: Range<u32>,
language_aware: bool,
@@ -71,10 +71,10 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
}
}
-pub struct FoldMapWriter<'a>(&'a mut FoldMap);
+pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
impl<'a> FoldMapWriter<'a> {
- pub fn fold<T: ToOffset>(
+ pub(crate) fn fold<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
) -> (FoldSnapshot, Vec<FoldEdit>) {
@@ -129,7 +129,7 @@ impl<'a> FoldMapWriter<'a> {
(self.0.snapshot.clone(), edits)
}
- pub fn unfold<T: ToOffset>(
+ pub(crate) fn unfold<T: ToOffset>(
&mut self,
ranges: impl IntoIterator<Item = Range<T>>,
inclusive: bool,
@@ -178,14 +178,14 @@ impl<'a> FoldMapWriter<'a> {
}
}
-pub struct FoldMap {
+pub(crate) struct FoldMap {
snapshot: FoldSnapshot,
ellipses_color: Option<Hsla>,
next_fold_id: FoldId,
}
impl FoldMap {
- pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
+ pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) {
let this = Self {
snapshot: FoldSnapshot {
folds: Default::default(),
@@ -655,7 +655,7 @@ impl FoldSnapshot {
}
}
- pub fn chunks<'a>(
+ pub(crate) fn chunks<'a>(
&'a self,
range: Range<FoldOffset>,
language_aware: bool,
@@ -35,8 +35,8 @@ enum Transform {
}
#[derive(Debug, Clone)]
-pub struct Inlay {
- pub id: InlayId,
+pub(crate) struct Inlay {
+ pub(crate) id: InlayId,
pub position: Anchor,
pub text: text::Rope,
}
@@ -1016,7 +1016,7 @@ impl InlaySnapshot {
(line_end - line_start) as u32
}
- pub fn chunks<'a>(
+ pub(crate) fn chunks<'a>(
&'a self,
range: Range<InlayOffset>,
language_aware: bool,
@@ -568,7 +568,7 @@ impl WrapSnapshot {
Patch::new(wrap_edits)
}
- pub fn chunks<'a>(
+ pub(crate) fn chunks<'a>(
&'a self,
rows: Range<u32>,
language_aware: bool,
@@ -1,3 +1,18 @@
+#![allow(rustdoc::private_intra_doc_links)]
+//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
+//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
+//! It comes in different flavors: single line, multiline and a fixed height one.
+//!
+//! Editor contains of multiple large submodules:
+//! * [`element`] β the place where all rendering happens
+//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
+//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
+//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
+//!
+//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
+//!
+//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides it's behaviour.
+pub mod actions;
mod blink_manager;
pub mod display_map;
mod editor_settings;
@@ -14,13 +29,14 @@ pub mod movement;
mod persistence;
mod rust_analyzer_ext;
pub mod scroll;
-pub mod selections_collection;
+mod selections_collection;
#[cfg(test)]
mod editor_tests;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
use ::git::diff::DiffHunk;
+pub(crate) use actions::*;
use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager;
@@ -32,14 +48,13 @@ use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
-pub use element::{
- Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles,
-};
+use element::LineWithInvisibles;
+pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine};
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display;
use gpui::{
- actions, div, impl_actions, point, prelude::*, px, relative, rems, size, uniform_list, Action,
+ 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,
@@ -51,7 +66,7 @@ use hover_popover::{hide_hover, HoverState};
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
-pub use language::{char_kind, CharKind};
+use language::{char_kind, CharKind};
use language::{
language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
@@ -74,9 +89,7 @@ use parking_lot::RwLock;
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*;
use rpc::proto::{self, *};
-use scroll::{
- autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
-};
+use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore};
@@ -113,10 +126,12 @@ const MAX_LINE_LEN: usize = 1024;
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
+#[doc(hidden)]
pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
+#[doc(hidden)]
pub const DOCUMENT_HIGHLIGHTS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
-pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
+pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
pub fn render_parsed_markdown(
element_id: impl Into<ElementId>,
@@ -181,103 +196,8 @@ pub fn render_parsed_markdown(
})
}
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectNext {
- #[serde(default)]
- pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectPrevious {
- #[serde(default)]
- pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectAllMatches {
- #[serde(default)]
- pub replace_newest: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectToBeginningOfLine {
- #[serde(default)]
- stop_at_soft_wraps: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct MovePageUp {
- #[serde(default)]
- center_cursor: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct MovePageDown {
- #[serde(default)]
- center_cursor: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct SelectToEndOfLine {
- #[serde(default)]
- stop_at_soft_wraps: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ToggleCodeActions {
- #[serde(default)]
- pub deployed_from_indicator: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ConfirmCompletion {
- #[serde(default)]
- pub item_ix: Option<usize>,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ConfirmCodeAction {
- #[serde(default)]
- pub item_ix: Option<usize>,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct ToggleComments {
- #[serde(default)]
- pub advance_downwards: bool,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct FoldAt {
- pub buffer_row: u32,
-}
-
-#[derive(PartialEq, Clone, Deserialize, Default)]
-pub struct UnfoldAt {
- pub buffer_row: u32,
-}
-
-impl_actions!(
- editor,
- [
- SelectNext,
- SelectPrevious,
- SelectAllMatches,
- SelectToBeginningOfLine,
- MovePageUp,
- MovePageDown,
- SelectToEndOfLine,
- ToggleCodeActions,
- ConfirmCompletion,
- ConfirmCodeAction,
- ToggleComments,
- FoldAt,
- UnfoldAt
- ]
-);
-
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub enum InlayId {
+pub(crate) enum InlayId {
Suggestion(usize),
Hint(usize),
}
@@ -291,128 +211,6 @@ impl InlayId {
}
}
-actions!(
- editor,
- [
- AddSelectionAbove,
- AddSelectionBelow,
- Backspace,
- Cancel,
- ConfirmRename,
- ContextMenuFirst,
- ContextMenuLast,
- ContextMenuNext,
- ContextMenuPrev,
- ConvertToKebabCase,
- ConvertToLowerCamelCase,
- ConvertToLowerCase,
- ConvertToSnakeCase,
- ConvertToTitleCase,
- ConvertToUpperCamelCase,
- ConvertToUpperCase,
- Copy,
- CopyHighlightJson,
- CopyPath,
- CopyRelativePath,
- Cut,
- CutToEndOfLine,
- Delete,
- DeleteLine,
- DeleteToBeginningOfLine,
- DeleteToEndOfLine,
- DeleteToNextSubwordEnd,
- DeleteToNextWordEnd,
- DeleteToPreviousSubwordStart,
- DeleteToPreviousWordStart,
- DuplicateLine,
- ExpandMacroRecursively,
- FindAllReferences,
- Fold,
- FoldSelectedRanges,
- Format,
- GoToDefinition,
- GoToDefinitionSplit,
- GoToDiagnostic,
- GoToHunk,
- GoToPrevDiagnostic,
- GoToPrevHunk,
- GoToTypeDefinition,
- GoToTypeDefinitionSplit,
- HalfPageDown,
- HalfPageUp,
- Hover,
- Indent,
- JoinLines,
- LineDown,
- LineUp,
- MoveDown,
- MoveLeft,
- MoveLineDown,
- MoveLineUp,
- MoveRight,
- MoveToBeginning,
- MoveToBeginningOfLine,
- MoveToEnclosingBracket,
- MoveToEnd,
- MoveToEndOfLine,
- MoveToEndOfParagraph,
- MoveToNextSubwordEnd,
- MoveToNextWordEnd,
- MoveToPreviousSubwordStart,
- MoveToPreviousWordStart,
- MoveToStartOfParagraph,
- MoveUp,
- Newline,
- NewlineAbove,
- NewlineBelow,
- NextScreen,
- OpenExcerpts,
- Outdent,
- PageDown,
- PageUp,
- Paste,
- Redo,
- RedoSelection,
- Rename,
- RestartLanguageServer,
- RevealInFinder,
- ReverseLines,
- ScrollCursorBottom,
- ScrollCursorCenter,
- ScrollCursorTop,
- SelectAll,
- SelectDown,
- SelectLargerSyntaxNode,
- SelectLeft,
- SelectLine,
- SelectRight,
- SelectSmallerSyntaxNode,
- SelectToBeginning,
- SelectToEnd,
- SelectToEndOfParagraph,
- SelectToNextSubwordEnd,
- SelectToNextWordEnd,
- SelectToPreviousSubwordStart,
- SelectToPreviousWordStart,
- SelectToStartOfParagraph,
- SelectUp,
- ShowCharacterPalette,
- ShowCompletions,
- ShuffleLines,
- SortLinesCaseInsensitive,
- SortLinesCaseSensitive,
- SplitSelectionIntoLines,
- Tab,
- TabPrev,
- ToggleInlayHints,
- ToggleSoftWrap,
- Transpose,
- Undo,
- UndoSelection,
- UnfoldLines,
- ]
-);
-
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
@@ -489,7 +287,7 @@ pub enum SelectPhase {
}
#[derive(Clone, Debug)]
-pub enum SelectMode {
+pub(crate) enum SelectMode {
Character,
Word(Range<Anchor>),
Line(Range<Anchor>),
@@ -760,6 +558,7 @@ struct SnippetState {
active_index: usize,
}
+#[doc(hidden)]
pub struct RenameState {
pub range: Range<Anchor>,
pub old_name: Arc<str>,
@@ -1499,7 +1298,7 @@ impl CodeActionsMenu {
}
}
-pub struct CopilotState {
+pub(crate) struct CopilotState {
excerpt_id: Option<ExcerptId>,
pending_refresh: Task<Option<()>>,
pending_cycling_refresh: Task<Option<()>>,
@@ -1619,15 +1418,13 @@ pub struct ClipboardSelection {
}
#[derive(Debug)]
-pub struct NavigationData {
+pub(crate) struct NavigationData {
cursor_anchor: Anchor,
cursor_position: Point,
scroll_anchor: ScrollAnchor,
scroll_top_row: u32,
}
-pub struct EditorCreated(pub View<Editor>);
-
enum GotoDefinitionKind {
Symbol,
Type,
@@ -8125,7 +7922,7 @@ impl Editor {
}
}
- pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
+ pub fn fold(&mut self, _: &actions::Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new();
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -8484,7 +8281,7 @@ impl Editor {
cx.notify();
}
- pub fn highlight_inlay_background<T: 'static>(
+ pub(crate) fn highlight_inlay_background<T: 'static>(
&mut self,
ranges: Vec<InlayHighlight>,
color_fetcher: fn(&ThemeColors) -> Hsla,
@@ -8691,7 +8488,7 @@ impl Editor {
cx.notify();
}
- pub fn highlight_inlays<T: 'static>(
+ pub(crate) fn highlight_inlays<T: 'static>(
&mut self,
highlights: Vec<InlayHighlight>,
style: HighlightStyle,
@@ -8736,7 +8533,7 @@ impl Editor {
) {
match event {
multi_buffer::Event::Edited {
- sigleton_buffer_edited,
+ singleton_buffer_edited,
} => {
self.refresh_active_diagnostics(cx);
self.refresh_code_actions(cx);
@@ -8746,7 +8543,7 @@ impl Editor {
cx.emit(EditorEvent::BufferEdited);
cx.emit(SearchEvent::MatchesInvalidated);
- if *sigleton_buffer_edited {
+ if *singleton_buffer_edited {
if let Some(project) = &self.project {
let project = project.read(cx);
let languages_affected = multibuffer
@@ -9899,7 +9696,7 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
(text_without_backticks.into(), code_ranges)
}
-pub fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
+fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
match (severity, valid) {
(DiagnosticSeverity::ERROR, true) => colors.error,
(DiagnosticSeverity::ERROR, false) => colors.error,
@@ -9958,7 +9755,7 @@ pub fn styled_runs_for_code_label<'a>(
})
}
-pub fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
+pub(crate) fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator<Item = &'a str> + 'a {
let mut index = 0;
let mut codepoints = text.char_indices().peekable();
@@ -456,6 +456,7 @@ impl EditorElement {
event: &MouseUpEvent,
position_map: &PositionMap,
text_bounds: Bounds<Pixels>,
+ interactive_bounds: &InteractiveBounds,
stacking_order: &StackingOrder,
cx: &mut ViewContext<Editor>,
) {
@@ -466,7 +467,8 @@ impl EditorElement {
editor.select(SelectPhase::End, cx);
}
- if !pending_nonempty_selections
+ if interactive_bounds.visibly_contains(&event.position, cx)
+ && !pending_nonempty_selections
&& event.modifiers.command
&& text_bounds.contains(&event.position)
&& cx.was_top_layer(&event.position, stacking_order)
@@ -2542,15 +2544,14 @@ impl EditorElement {
let interactive_bounds = interactive_bounds.clone();
move |event: &MouseUpEvent, phase, cx| {
- if phase == DispatchPhase::Bubble
- && interactive_bounds.visibly_contains(&event.position, cx)
- {
+ if phase == DispatchPhase::Bubble {
editor.update(cx, |editor, cx| {
Self::mouse_up(
editor,
event,
&position_map,
text_bounds,
+ &interactive_bounds,
&stacking_order,
cx,
)
@@ -2599,7 +2600,7 @@ impl EditorElement {
}
#[derive(Debug)]
-pub struct LineWithInvisibles {
+pub(crate) struct LineWithInvisibles {
pub line: ShapedLine,
invisibles: Vec<Invisible>,
}
@@ -1,3 +1,11 @@
+/// Stores and updates all data received from LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint">textDocument/inlayHint</a> requests.
+/// Has nothing to do with other inlays, e.g. copilot suggestions βΒ those are stored elsewhere.
+/// On every update, cache may query for more inlay hints and update inlays on the screen.
+///
+/// Inlays stored on screen are in [`crate::display_map::inlay_map`] and this cache is the only way to update any inlay hint data in the visible hints in the inlay map.
+/// For determining the update to the `inlay_map`, the cache requires a list of visible inlay hints β all other hints are not relevant and their separate updates are not influencing the cache work.
+///
+/// Due to the way the data is stored for both visible inlays and the cache, every inlay (and inlay hint) collection is editor-specific, so a single buffer may have multiple sets of inlays of open on different panes.
use std::{
cmp,
ops::{ControlFlow, Range},
@@ -39,7 +47,7 @@ struct TasksForRanges {
}
#[derive(Debug)]
-pub struct CachedExcerptHints {
+struct CachedExcerptHints {
version: usize,
buffer_version: Global,
buffer_id: u64,
@@ -47,15 +55,30 @@ pub struct CachedExcerptHints {
hints_by_id: HashMap<InlayId, InlayHint>,
}
+/// A logic to apply when querying for new inlay hints and deciding what to do with the old entries in the cache in case of conflicts.
#[derive(Debug, Clone, Copy)]
-pub enum InvalidationStrategy {
+pub(super) enum InvalidationStrategy {
+ /// Hints reset is <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh">requested</a> by the LSP server.
+ /// Demands to re-query all inlay hints needed and invalidate all cached entries, but does not require instant update with invalidation.
+ ///
+ /// Despite nothing forbids language server from sending this request on every edit, it is expected to be sent only when certain internal server state update, invisible for the editor otherwise.
RefreshRequested,
+ /// Multibuffer excerpt(s) and/or singleton buffer(s) were edited at least on one place.
+ /// Neither editor nor LSP is able to tell which open file hints' are not affected, so all of them have to be invalidated, re-queried and do that fast enough to avoid being slow, but also debounce to avoid loading hints on every fast keystroke sequence.
BufferEdited,
+ /// A new file got opened/new excerpt was added to a multibuffer/a [multi]buffer was scrolled to a new position.
+ /// No invalidation should be done at all, all new hints are added to the cache.
+ ///
+ /// A special case is the settings change: in addition to LSP capabilities, Zed allows omitting certain hint kinds (defined by the corresponding LSP part: type/parameter/other).
+ /// This does not lead to cache invalidation, but would require cache usage for determining which hints are not displayed and issuing an update to inlays on the screen.
None,
}
-#[derive(Debug, Default)]
-pub struct InlaySplice {
+/// A splice to send into the `inlay_map` for updating the visible inlays on the screen.
+/// "Visible" inlays may not be displayed in the buffer right away, but those are ready to be displayed on further buffer scroll, pane item activations, etc. right away without additional LSP queries or settings changes.
+/// The data in the cache is never used directly for displaying inlays on the screen, to avoid races with updates from LSP queries and sync overhead.
+/// Splice is picked to help avoid extra hint flickering and "jumps" on the screen.
+pub(super) struct InlaySplice {
pub to_remove: Vec<InlayId>,
pub to_insert: Vec<Inlay>,
}
@@ -237,7 +260,7 @@ impl TasksForRanges {
}
impl InlayHintCache {
- pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
+ pub(super) fn new(inlay_hint_settings: InlayHintSettings) -> Self {
Self {
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled,
@@ -248,7 +271,10 @@ impl InlayHintCache {
}
}
- pub fn update_settings(
+ /// Checks inlay hint settings for enabled hint kinds and general enabled state.
+ /// Generates corresponding inlay_map splice updates on settings changes.
+ /// Does not update inlay hint cache state on disabling or inlay hint kinds change: only reenabling forces new LSP queries.
+ pub(super) fn update_settings(
&mut self,
multi_buffer: &Model<MultiBuffer>,
new_hint_settings: InlayHintSettings,
@@ -299,7 +325,11 @@ impl InlayHintCache {
}
}
- pub fn spawn_hint_refresh(
+ /// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
+ /// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
+ /// followed by the delayed queries of the same range above and below the visible one.
+ /// This way, concequent refresh invocations are less likely to trigger LSP queries for the invisible ranges.
+ pub(super) fn spawn_hint_refresh(
&mut self,
reason: &'static str,
excerpts_to_query: HashMap<ExcerptId, (Model<Buffer>, Global, Range<usize>)>,
@@ -460,7 +490,11 @@ impl InlayHintCache {
}
}
- pub fn remove_excerpts(&mut self, excerpts_removed: Vec<ExcerptId>) -> Option<InlaySplice> {
+ /// Completely forget of certain excerpts that were removed from the multibuffer.
+ pub(super) fn remove_excerpts(
+ &mut self,
+ excerpts_removed: Vec<ExcerptId>,
+ ) -> Option<InlaySplice> {
let mut to_remove = Vec::new();
for excerpt_to_remove in excerpts_removed {
self.update_tasks.remove(&excerpt_to_remove);
@@ -480,7 +514,7 @@ impl InlayHintCache {
}
}
- pub fn clear(&mut self) {
+ pub(super) fn clear(&mut self) {
if !self.update_tasks.is_empty() || !self.hints.is_empty() {
self.version += 1;
}
@@ -488,7 +522,7 @@ impl InlayHintCache {
self.hints.clear();
}
- pub fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option<InlayHint> {
+ pub(super) fn hint_by_id(&self, excerpt_id: ExcerptId, hint_id: InlayId) -> Option<InlayHint> {
self.hints
.get(&excerpt_id)?
.read()
@@ -516,7 +550,8 @@ impl InlayHintCache {
self.version
}
- pub fn spawn_hint_resolve(
+ /// Queries a certain hint from the cache for extra data via the LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#inlayHint_resolve">resolve</a> request.
+ pub(super) fn spawn_hint_resolve(
&self,
buffer_id: u64,
excerpt_id: ExcerptId,
@@ -925,14 +960,14 @@ async fn fetch_and_update_hints(
log::trace!("Fetched hints: {new_hints:?}");
let background_task_buffer_snapshot = buffer_snapshot.clone();
- let backround_fetch_range = fetch_range.clone();
+ let background_fetch_range = fetch_range.clone();
let new_update = cx
.background_executor()
.spawn(async move {
calculate_hint_updates(
query.excerpt_id,
invalidate,
- backround_fetch_range,
+ background_fetch_range,
new_hints,
&background_task_buffer_snapshot,
cached_excerpt_hints,
@@ -1199,7 +1234,7 @@ pub mod tests {
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use crate::{
- scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
+ scroll::{scroll_amount::ScrollAmount, Autoscroll},
ExcerptRange,
};
use futures::StreamExt;
@@ -1449,7 +1484,7 @@ pub mod tests {
assert_eq!(
editor.inlay_hint_cache().version,
edits_made,
- "Cache version should udpate once after the work task is done"
+ "Cache version should update once after the work task is done"
);
});
}
@@ -1599,7 +1634,7 @@ pub mod tests {
assert_eq!(
expected_hints,
cached_hint_labels(editor),
- "Markdown editor should have a separate verison, repeating Rust editor rules"
+ "Markdown editor should have a separate version, repeating Rust editor rules"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
@@ -2612,7 +2647,7 @@ pub mod tests {
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
- assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
+ assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the version");
});
_ = editor.update(cx, |editor, cx| {
@@ -2728,7 +2763,7 @@ pub mod tests {
expected_hints,
cached_hint_labels(editor),
"After multibuffer edit, editor gets scolled back to the last selection; \
- all hints should be invalidated and requeried for all of its visible excerpts"
+ all hints should be invalidated and required for all of its visible excerpts"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
@@ -69,7 +69,7 @@ pub enum GoToDefinitionLink {
}
#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct InlayHighlight {
+pub(crate) struct InlayHighlight {
pub inlay: InlayId,
pub inlay_position: Anchor,
pub range: Range<usize>,
@@ -1,3 +1,6 @@
+//! Movement module contains helper functions for calculating intended position
+//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
+
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
use crate::{char_kind, CharKind, EditorStyle, ToOffset, ToPoint};
use gpui::{px, Pixels, TextSystem};
@@ -5,6 +8,9 @@ use language::Point;
use std::{ops::Range, sync::Arc};
+/// Defines search strategy for items in `movement` module.
+/// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas
+/// `FindRange::MultiLine` keeps going until the end of a string.
#[derive(Debug, PartialEq)]
pub enum FindRange {
SingleLine,
@@ -14,11 +20,13 @@ pub enum FindRange {
/// TextLayoutDetails encompasses everything we need to move vertically
/// taking into account variable width characters.
pub struct TextLayoutDetails {
- pub text_system: Arc<TextSystem>,
- pub editor_style: EditorStyle,
- pub rem_size: Pixels,
+ pub(crate) text_system: Arc<TextSystem>,
+ pub(crate) editor_style: EditorStyle,
+ pub(crate) rem_size: Pixels,
}
+/// Returns a column to the left of the current point, wrapping
+/// to the previous line if that point is at the start of line.
pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
if point.column() > 0 {
*point.column_mut() -= 1;
@@ -29,6 +37,8 @@ pub fn left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
map.clip_point(point, Bias::Left)
}
+/// Returns a column to the left of the current point, doing nothing if
+/// that point is already at the start of line.
pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
if point.column() > 0 {
*point.column_mut() -= 1;
@@ -36,6 +46,8 @@ pub fn saturating_left(map: &DisplaySnapshot, mut point: DisplayPoint) -> Displa
map.clip_point(point, Bias::Left)
}
+/// Returns a column to the right of the current point, wrapping
+/// to the next line if that point is at the end of line.
pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
let max_column = map.line_len(point.row());
if point.column() < max_column {
@@ -47,11 +59,14 @@ pub fn right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
map.clip_point(point, Bias::Right)
}
+/// Returns a column to the right of the current point, not performing any wrapping
+/// if that point is already at the end of line.
pub fn saturating_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint {
*point.column_mut() += 1;
map.clip_point(point, Bias::Right)
}
+/// Returns a display point for the preceding displayed line (which might be a soft-wrapped line).
pub fn up(
map: &DisplaySnapshot,
start: DisplayPoint,
@@ -69,6 +84,7 @@ pub fn up(
)
}
+/// Returns a display point for the next displayed line (which might be a soft-wrapped line).
pub fn down(
map: &DisplaySnapshot,
start: DisplayPoint,
@@ -86,7 +102,7 @@ pub fn down(
)
}
-pub fn up_by_rows(
+pub(crate) fn up_by_rows(
map: &DisplaySnapshot,
start: DisplayPoint,
row_count: u32,
@@ -125,7 +141,7 @@ pub fn up_by_rows(
)
}
-pub fn down_by_rows(
+pub(crate) fn down_by_rows(
map: &DisplaySnapshot,
start: DisplayPoint,
row_count: u32,
@@ -161,6 +177,10 @@ pub fn down_by_rows(
)
}
+/// Returns a position of the start of line.
+/// If `stop_at_soft_boundaries` is true, the returned position is that of the
+/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped).
+/// Otherwise it's always going to be the start of a logical line.
pub fn line_beginning(
map: &DisplaySnapshot,
display_point: DisplayPoint,
@@ -177,6 +197,10 @@ pub fn line_beginning(
}
}
+/// Returns the last indented position on a given line.
+/// If `stop_at_soft_boundaries` is true, the returned [`DisplayPoint`] is that of a
+/// displayed line (e.g. if there's soft wrap it's gonna be returned),
+/// otherwise it's always going to be a start of a logical line.
pub fn indented_line_beginning(
map: &DisplaySnapshot,
display_point: DisplayPoint,
@@ -201,6 +225,11 @@ pub fn indented_line_beginning(
}
}
+/// Returns a position of the end of line.
+
+/// If `stop_at_soft_boundaries` is true, the returned position is that of the
+/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped).
+/// Otherwise it's always going to be the end of a logical line.
pub fn line_end(
map: &DisplaySnapshot,
display_point: DisplayPoint,
@@ -217,6 +246,8 @@ pub fn line_end(
}
}
+/// Returns a position of the previous word boundary, where a word character is defined as either
+/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS).
pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let scope = map.buffer_snapshot.language_scope_at(raw_point);
@@ -227,6 +258,9 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa
})
}
+/// Returns a position of the previous subword boundary, where a subword is defined as a run of
+/// word characters of the same "subkind" - where subcharacter kinds are '_' character,
+/// lowerspace characters and uppercase characters.
pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let scope = map.buffer_snapshot.language_scope_at(raw_point);
@@ -240,6 +274,8 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis
})
}
+/// Returns a position of the next word boundary, where a word character is defined as either
+/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS).
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let scope = map.buffer_snapshot.language_scope_at(raw_point);
@@ -250,6 +286,9 @@ pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint
})
}
+/// Returns a position of the next subword boundary, where a subword is defined as a run of
+/// word characters of the same "subkind" - where subcharacter kinds are '_' character,
+/// lowerspace characters and uppercase characters.
pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let scope = map.buffer_snapshot.language_scope_at(raw_point);
@@ -263,6 +302,8 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
})
}
+/// Returns a position of the start of the current paragraph, where a paragraph
+/// is defined as a run of non-blank lines.
pub fn start_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
@@ -290,6 +331,8 @@ pub fn start_of_paragraph(
DisplayPoint::zero()
}
+/// Returns a position of the end of the current paragraph, where a paragraph
+/// is defined as a run of non-blank lines.
pub fn end_of_paragraph(
map: &DisplaySnapshot,
display_point: DisplayPoint,
@@ -376,6 +419,9 @@ pub fn find_boundary(
map.clip_point(offset.to_display_point(map), Bias::Right)
}
+/// Returns an iterator over the characters following a given offset in the [`DisplaySnapshot`].
+/// The returned value also contains a range of the start/end of a returned character in
+/// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer.
pub fn chars_after(
map: &DisplaySnapshot,
mut offset: usize,
@@ -387,6 +433,9 @@ pub fn chars_after(
})
}
+/// Returns a reverse iterator over the characters following a given offset in the [`DisplaySnapshot`].
+/// The returned value also contains a range of the start/end of a returned character in
+/// the [`DisplaySnapshot`]. The offsets are relative to the start of a buffer.
pub fn chars_before(
map: &DisplaySnapshot,
mut offset: usize,
@@ -400,7 +449,7 @@ pub fn chars_before(
})
}
-pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
+pub(crate) fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
let raw_point = point.to_point(map);
let scope = map.buffer_snapshot.language_scope_at(raw_point);
let ix = map.clip_point(point, Bias::Left).to_offset(map, Bias::Left);
@@ -413,7 +462,10 @@ pub fn is_inside_word(map: &DisplaySnapshot, point: DisplayPoint) -> bool {
prev_char_kind.zip(next_char_kind) == Some((CharKind::Word, CharKind::Word))
}
-pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<DisplayPoint> {
+pub(crate) fn surrounding_word(
+ map: &DisplaySnapshot,
+ position: DisplayPoint,
+) -> Range<DisplayPoint> {
let position = map
.clip_point(position, Bias::Left)
.to_offset(map, Bias::Left);
@@ -429,6 +481,12 @@ pub fn surrounding_word(map: &DisplaySnapshot, position: DisplayPoint) -> Range<
start..end
}
+/// Returns a list of lines (represented as a [`DisplayPoint`] range) contained
+/// within a passed range.
+///
+/// The line ranges are **always* going to be in bounds of a requested range, which means that
+/// the first and the last lines might not necessarily represent the
+/// full range of a logical line (as their `.start`/`.end` values are clipped to those of a passed in range).
pub fn split_display_range_by_lines(
map: &DisplaySnapshot,
range: Range<DisplayPoint>,
@@ -1,6 +1,6 @@
-pub mod actions;
-pub mod autoscroll;
-pub mod scroll_amount;
+mod actions;
+pub(crate) mod autoscroll;
+pub(crate) mod scroll_amount;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
@@ -9,8 +9,10 @@ use crate::{
Anchor, DisplayPoint, Editor, EditorEvent, EditorMode, InlayHintRefreshReason,
MultiBufferSnapshot, ToPoint,
};
+pub use autoscroll::{Autoscroll, AutoscrollStrategy};
use gpui::{point, px, AppContext, Entity, Pixels, Task, ViewContext};
use language::{Bias, Point};
+pub use scroll_amount::ScrollAmount;
use std::{
cmp::Ordering,
time::{Duration, Instant},
@@ -18,11 +20,6 @@ use std::{
use util::ResultExt;
use workspace::{ItemId, WorkspaceId};
-use self::{
- autoscroll::{Autoscroll, AutoscrollStrategy},
- scroll_amount::ScrollAmount,
-};
-
pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
pub const VERTICAL_SCROLL_MARGIN: f32 = 3.;
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
@@ -175,7 +175,7 @@ impl Editor {
true
}
- pub fn autoscroll_horizontally(
+ pub(crate) fn autoscroll_horizontally(
&mut self,
start_row: u32,
viewport_width: Pixels,
@@ -99,7 +99,7 @@ impl SelectionsCollection {
.map(|pending| pending.map(|p| p.summary::<D>(&self.buffer(cx))))
}
- pub fn pending_mode(&self) -> Option<SelectMode> {
+ pub(crate) fn pending_mode(&self) -> Option<SelectMode> {
self.pending.as_ref().map(|pending| pending.mode.clone())
}
@@ -398,7 +398,7 @@ impl<'a> MutableSelectionsCollection<'a> {
}
}
- pub fn set_pending_anchor_range(&mut self, range: Range<Anchor>, mode: SelectMode) {
+ pub(crate) fn set_pending_anchor_range(&mut self, range: Range<Anchor>, mode: SelectMode) {
self.collection.pending = Some(PendingSelection {
selection: Selection {
id: post_inc(&mut self.collection.next_selection_id),
@@ -412,7 +412,11 @@ impl<'a> MutableSelectionsCollection<'a> {
self.selections_changed = true;
}
- pub fn set_pending_display_range(&mut self, range: Range<DisplayPoint>, mode: SelectMode) {
+ pub(crate) fn set_pending_display_range(
+ &mut self,
+ range: Range<DisplayPoint>,
+ mode: SelectMode,
+ ) {
let (start, end, reversed) = {
let display_map = self.display_map();
let buffer = self.buffer();
@@ -448,7 +452,7 @@ impl<'a> MutableSelectionsCollection<'a> {
self.selections_changed = true;
}
- pub fn set_pending(&mut self, selection: Selection<Anchor>, mode: SelectMode) {
+ pub(crate) fn set_pending(&mut self, selection: Selection<Anchor>, mode: SelectMode) {
self.collection.pending = Some(PendingSelection { selection, mode });
self.selections_changed = true;
}
@@ -855,7 +859,7 @@ impl<'a> DerefMut for MutableSelectionsCollection<'a> {
}
// Panics if passed selections are not in order
-pub fn resolve_multiple<'a, D, I>(
+pub(crate) fn resolve_multiple<'a, D, I>(
selections: I,
snapshot: &MultiBufferSnapshot,
) -> impl 'a + Iterator<Item = Selection<D>>
@@ -57,18 +57,14 @@ impl FeatureFlagAppExt for AppContext {
}
fn has_flag<T: FeatureFlag>(&self) -> bool {
- if self.has_global::<FeatureFlags>() {
- self.global::<FeatureFlags>().has_flag(T::NAME)
- } else {
- false
- }
+ self.try_global::<FeatureFlags>()
+ .map(|flags| flags.has_flag(T::NAME))
+ .unwrap_or(false)
}
fn is_staff(&self) -> bool {
- if self.has_global::<FeatureFlags>() {
- return self.global::<FeatureFlags>().staff;
- } else {
- false
- }
+ self.try_global::<FeatureFlags>()
+ .map(|flags| flags.staff)
+ .unwrap_or(false)
}
}
@@ -1,5 +1,5 @@
use collections::HashMap;
-use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
+use editor::{scroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{
actions, rems, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
@@ -29,7 +29,7 @@ pub trait GitRepository: Send {
fn branch_name(&self) -> Option<String>;
/// Get the statuses of all of the files in the index that start with the given
- /// path and have changes with resepect to the HEAD commit. This is fast because
+ /// path and have changes with respect to the HEAD commit. This is fast because
/// the index stores hashes of trees, so that unchanged directories can be skipped.
fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus>;
@@ -1,4 +1,4 @@
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
+use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Editor};
use gpui::{
actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
@@ -0,0 +1,35 @@
+use gpui::{prelude::*, App, AppContext, EventEmitter, Model, ModelContext};
+
+struct Counter {
+ count: usize,
+}
+
+struct Change {
+ increment: usize,
+}
+
+impl EventEmitter<Change> for Counter {}
+
+fn main() {
+ App::new().run(|cx: &mut AppContext| {
+ let counter: Model<Counter> = cx.new_model(|_cx| Counter { count: 0 });
+ let subscriber = cx.new_model(|cx: &mut ModelContext<Counter>| {
+ cx.subscribe(&counter, |subscriber, _emitter, event, _cx| {
+ subscriber.count += event.increment * 2;
+ })
+ .detach();
+
+ Counter {
+ count: counter.read(cx).count * 2,
+ }
+ });
+
+ counter.update(cx, |counter, cx| {
+ counter.count += 2;
+ cx.notify();
+ cx.emit(Change { increment: 2 });
+ });
+
+ assert_eq!(subscriber.read(cx).count, 4);
+ });
+}
@@ -1,3 +1,5 @@
+#![deny(missing_docs)]
+
mod async_context;
mod entity_map;
mod model_context;
@@ -43,6 +45,9 @@ use util::{
ResultExt,
};
+/// The duration for which futures returned from [AppContext::on_app_context] or [ModelContext::on_app_quit] can run before the application fully quits.
+pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(100);
+
/// Temporary(?) wrapper around [`RefCell<AppContext>`] to help us debug any double borrows.
/// Strongly consider removing after stabilization.
#[doc(hidden)]
@@ -106,14 +111,24 @@ pub struct App(Rc<AppCell>);
/// configured, you'll start the app with `App::run`.
impl App {
/// Builds an app with the given asset source.
- pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
+ pub fn new() -> Self {
Self(AppContext::new(
current_platform(),
- asset_source,
+ Arc::new(()),
http::client(),
))
}
+ /// Assign
+ pub fn with_assets(self, asset_source: impl AssetSource) -> Self {
+ let mut context_lock = self.0.borrow_mut();
+ let asset_source = Arc::new(asset_source);
+ context_lock.asset_source = asset_source.clone();
+ context_lock.svg_renderer = SvgRenderer::new(asset_source);
+ drop(context_lock);
+ self
+ }
+
/// Start the application. The provided callback will be called once the
/// app is fully launched.
pub fn run<F>(self, on_finish_launching: F)
@@ -187,6 +202,9 @@ type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()
type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
+/// Contains the state of the full application, and passed as a reference to a variety of callbacks.
+/// Other contexts such as [ModelContext], [WindowContext], and [ViewContext] deref to this type, making it the most general context type.
+/// You need a reference to an `AppContext` to access the state of a [Model].
pub struct AppContext {
pub(crate) this: Weak<AppCell>,
pub(crate) platform: Rc<dyn Platform>,
@@ -312,7 +330,7 @@ impl AppContext {
let futures = futures::future::join_all(futures);
if self
.background_executor
- .block_with_timeout(Duration::from_millis(100), futures)
+ .block_with_timeout(SHUTDOWN_TIMEOUT, futures)
.is_err()
{
log::error!("timed out waiting on app_will_quit");
@@ -446,6 +464,7 @@ impl AppContext {
.collect()
}
+ /// Returns a handle to the window that is currently focused at the platform level, if one exists.
pub fn active_window(&self) -> Option<AnyWindowHandle> {
self.platform.active_window()
}
@@ -474,14 +493,17 @@ impl AppContext {
self.platform.activate(ignoring_other_apps);
}
+ /// Hide the application at the platform level.
pub fn hide(&self) {
self.platform.hide();
}
+ /// Hide other applications at the platform level.
pub fn hide_other_apps(&self) {
self.platform.hide_other_apps();
}
+ /// Unhide other applications at the platform level.
pub fn unhide_other_apps(&self) {
self.platform.unhide_other_apps();
}
@@ -521,18 +543,25 @@ impl AppContext {
self.platform.open_url(url);
}
+ /// Returns the full pathname of the current app bundle.
+ /// If the app is not being run from a bundle, returns an error.
pub fn app_path(&self) -> Result<PathBuf> {
self.platform.app_path()
}
+ /// Returns the file URL of the executable with the specified name in the application bundle
pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
self.platform.path_for_auxiliary_executable(name)
}
+ /// Returns the maximum duration in which a second mouse click must occur for an event to be a double-click event.
pub fn double_click_interval(&self) -> Duration {
self.platform.double_click_interval()
}
+ /// Displays a platform modal for selecting paths.
+ /// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel.
+ /// If cancelled, a `None` will be relayed instead.
pub fn prompt_for_paths(
&self,
options: PathPromptOptions,
@@ -540,22 +569,30 @@ impl AppContext {
self.platform.prompt_for_paths(options)
}
+ /// Displays a platform modal for selecting a new path where a file can be saved.
+ /// The provided directory will be used to set the iniital location.
+ /// When a path is selected, it is relayed asynchronously via the returned oneshot channel.
+ /// If cancelled, a `None` will be relayed instead.
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
self.platform.prompt_for_new_path(directory)
}
+ /// Reveals the specified path at the platform level, such as in Finder on macOS.
pub fn reveal_path(&self, path: &Path) {
self.platform.reveal_path(path)
}
+ /// Returns whether the user has configured scrollbars to auto-hide at the platform level.
pub fn should_auto_hide_scrollbars(&self) -> bool {
self.platform.should_auto_hide_scrollbars()
}
+ /// Restart the application.
pub fn restart(&self) {
self.platform.restart()
}
+ /// Returns the local timezone at the platform level.
pub fn local_timezone(&self) -> UtcOffset {
self.platform.local_timezone()
}
@@ -745,7 +782,7 @@ impl AppContext {
}
/// Spawns the future returned by the given function on the thread pool. The closure will be invoked
- /// with AsyncAppContext, which allows the application state to be accessed across await points.
+ /// with [AsyncAppContext], which allows the application state to be accessed across await points.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
@@ -896,6 +933,8 @@ impl AppContext {
self.globals_by_type.insert(global_type, lease.global);
}
+ /// Arrange for the given function to be invoked whenever a view of the specified type is created.
+ /// The function will be passed a mutable reference to the view along with an appropriate context.
pub fn observe_new_views<V: 'static>(
&mut self,
on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
@@ -915,6 +954,8 @@ impl AppContext {
subscription
}
+ /// Observe the release of a model or view. The callback is invoked after the model or view
+ /// has no more strong references but before it has been dropped.
pub fn observe_release<E, T>(
&mut self,
handle: &E,
@@ -935,6 +976,9 @@ impl AppContext {
subscription
}
+ /// Register a callback to be invoked when a keystroke is received by the application
+ /// in any window. Note that this fires after all other action and event mechanisms have resolved
+ /// and that this API will not be invoked if the event's propagation is stopped.
pub fn observe_keystrokes(
&mut self,
f: impl FnMut(&KeystrokeEvent, &mut WindowContext) + 'static,
@@ -958,6 +1002,7 @@ impl AppContext {
self.pending_effects.push_back(Effect::Refresh);
}
+ /// Clear all key bindings in the app.
pub fn clear_key_bindings(&mut self) {
self.keymap.lock().clear();
self.pending_effects.push_back(Effect::Refresh);
@@ -992,6 +1037,7 @@ impl AppContext {
self.propagate_event = true;
}
+ /// Build an action from some arbitrary data, typically a keymap entry.
pub fn build_action(
&self,
name: &str,
@@ -1000,10 +1046,16 @@ impl AppContext {
self.actions.build_action(name, data)
}
+ /// Get a list of all action names that have been registered.
+ /// in the application. Note that registration only allows for
+ /// actions to be built dynamically, and is unrelated to binding
+ /// actions in the element tree.
pub fn all_action_names(&self) -> &[SharedString] {
self.actions.all_action_names()
}
+ /// Register a callback to be invoked when the application is about to quit.
+ /// It is not possible to cancel the quit event at this point.
pub fn on_app_quit<Fut>(
&mut self,
mut on_quit: impl FnMut(&mut AppContext) -> Fut + 'static,
@@ -1039,6 +1091,8 @@ impl AppContext {
}
}
+ /// Checks if the given action is bound in the current context, as defined by the app's current focus,
+ /// the bindings in the element tree, and any global action listeners.
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
if let Some(window) = self.active_window() {
if let Ok(window_action_available) =
@@ -1052,10 +1106,13 @@ impl AppContext {
.contains_key(&action.as_any().type_id())
}
+ /// Set 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());
}
+ /// Dispatch an action to the currently active window or global action handler
+ /// See [action::Action] for more information on how actions work
pub fn dispatch_action(&mut self, action: &dyn Action) {
if let Some(active_window) = self.active_window() {
active_window
@@ -1110,6 +1167,7 @@ impl AppContext {
}
}
+ /// Is there currently something being dragged?
pub fn has_active_drag(&self) -> bool {
self.active_drag.is_some()
}
@@ -1119,7 +1177,7 @@ impl Context for AppContext {
type Result<T> = T;
/// Build an entity that is owned by the application. The given function will be invoked with
- /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
+ /// a `ModelContext` and must return an object representing the entity. A `Model` handle will be returned,
/// which can be used to access the entity in a context.
fn new_model<T: 'static>(
&mut self,
@@ -1262,8 +1320,14 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
/// Contains state associated with an active drag operation, started by dragging an element
/// within the window or by dragging into the app from the underlying platform.
pub struct AnyDrag {
+ /// The view used to render this drag
pub view: AnyView,
+
+ /// The value of the dragged item, to be dropped
pub value: Box<dyn Any>,
+
+ /// This is used to render the dragged item in the same place
+ /// on the original element that the drag was initiated
pub cursor_offset: Point<Pixels>,
}
@@ -1271,12 +1335,19 @@ pub struct AnyDrag {
/// tooltip behavior on a custom element. Otherwise, use [Div::tooltip].
#[derive(Clone)]
pub struct AnyTooltip {
+ /// The view used to display the tooltip
pub view: AnyView,
+
+ /// The offset from the cursor to use, relative to the parent view
pub cursor_offset: Point<Pixels>,
}
+/// A keystroke event, and potentially the associated action
#[derive(Debug)]
pub struct KeystrokeEvent {
+ /// The keystroke that occurred
pub keystroke: Keystroke,
+
+ /// The action that was resolved for the keystroke, if any
pub action: Option<Box<dyn Action>>,
}
@@ -7,6 +7,9 @@ use anyhow::{anyhow, Context as _};
use derive_more::{Deref, DerefMut};
use std::{future::Future, rc::Weak};
+/// An async-friendly version of [AppContext] with a static lifetime so it can be held across `await` points in async code.
+/// You're provided with an instance when calling [AppContext::spawn], and you can also create one with [AppContext::to_async].
+/// Internally, this holds a weak reference to an `AppContext`, so its methods are fallible to protect against cases where the [AppContext] is dropped.
#[derive(Clone)]
pub struct AsyncAppContext {
pub(crate) app: Weak<AppCell>,
@@ -139,6 +142,8 @@ impl AsyncAppContext {
self.foreground_executor.spawn(f(self.clone()))
}
+ /// Determine whether global state of the specified type has been assigned.
+ /// Returns an error if the `AppContext` has been dropped.
pub fn has_global<G: 'static>(&self) -> Result<bool> {
let app = self
.app
@@ -148,6 +153,9 @@ impl AsyncAppContext {
Ok(app.has_global::<G>())
}
+ /// Reads the global state of the specified type, passing it to the given callback.
+ /// Panics if no global state of the specified type has been assigned.
+ /// Returns an error if the `AppContext` has been dropped.
pub fn read_global<G: 'static, R>(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result<R> {
let app = self
.app
@@ -157,6 +165,9 @@ impl AsyncAppContext {
Ok(read(app.global(), &app))
}
+ /// Reads the global state of the specified type, passing it to the given callback.
+ /// Similar to [read_global], but returns an error instead of panicking if no state of the specified type has been assigned.
+ /// Returns an error if no state of the specified type has been assigned the `AppContext` has been dropped.
pub fn try_read_global<G: 'static, R>(
&self,
read: impl FnOnce(&G, &AppContext) -> R,
@@ -166,6 +177,8 @@ impl AsyncAppContext {
Some(read(app.try_global()?, &app))
}
+ /// A convenience method for [AppContext::update_global]
+ /// for updating the global state of the specified type.
pub fn update_global<G: 'static, R>(
&mut self,
update: impl FnOnce(&mut G, &mut AppContext) -> R,
@@ -179,6 +192,8 @@ impl AsyncAppContext {
}
}
+/// A cloneable, owned handle to the application context,
+/// composed with the window associated with the current task.
#[derive(Clone, Deref, DerefMut)]
pub struct AsyncWindowContext {
#[deref]
@@ -188,14 +203,16 @@ pub struct AsyncWindowContext {
}
impl AsyncWindowContext {
- pub fn window_handle(&self) -> AnyWindowHandle {
- self.window
- }
-
pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self {
Self { app, window }
}
+ /// Get the handle of the window this context is associated with.
+ pub fn window_handle(&self) -> AnyWindowHandle {
+ self.window
+ }
+
+ /// A convenience method for [WindowContext::update()]
pub fn update<R>(
&mut self,
update: impl FnOnce(AnyView, &mut WindowContext) -> R,
@@ -203,10 +220,12 @@ impl AsyncWindowContext {
self.app.update_window(self.window, update)
}
+ /// A convenience method for [WindowContext::on_next_frame()]
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
self.window.update(self, |_, cx| cx.on_next_frame(f)).ok();
}
+ /// A convenience method for [AppContext::global()]
pub fn read_global<G: 'static, R>(
&mut self,
read: impl FnOnce(&G, &WindowContext) -> R,
@@ -214,6 +233,8 @@ impl AsyncWindowContext {
self.window.update(self, |_, cx| read(cx.global(), cx))
}
+ /// A convenience method for [AppContext::update_global()]
+ /// for updating the global state of the specified type.
pub fn update_global<G, R>(
&mut self,
update: impl FnOnce(&mut G, &mut WindowContext) -> R,
@@ -224,6 +245,8 @@ impl AsyncWindowContext {
self.window.update(self, |_, cx| cx.update_global(update))
}
+ /// Schedule a future to be executed on the main thread. This is used for collecting
+ /// the results of background tasks and updating the UI.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncWindowContext) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
@@ -31,6 +31,7 @@ impl From<u64> for EntityId {
}
impl EntityId {
+ /// Converts this entity id to a [u64]
pub fn as_u64(self) -> u64 {
self.0.as_ffi()
}
@@ -140,7 +141,7 @@ impl EntityMap {
}
}
-pub struct Lease<'a, T> {
+pub(crate) struct Lease<'a, T> {
entity: Option<Box<dyn Any>>,
pub model: &'a Model<T>,
entity_type: PhantomData<T>,
@@ -169,8 +170,9 @@ impl<'a, T> Drop for Lease<'a, T> {
}
#[derive(Deref, DerefMut)]
-pub struct Slot<T>(Model<T>);
+pub(crate) struct Slot<T>(Model<T>);
+/// A dynamically typed reference to a model, which can be downcast into a `Model<T>`.
pub struct AnyModel {
pub(crate) entity_id: EntityId,
pub(crate) entity_type: TypeId,
@@ -195,14 +197,17 @@ impl AnyModel {
}
}
+ /// Returns the id associated with this model.
pub fn entity_id(&self) -> EntityId {
self.entity_id
}
+ /// Returns the [TypeId] associated with this model.
pub fn entity_type(&self) -> TypeId {
self.entity_type
}
+ /// Converts this model handle into a weak variant, which does not prevent it from being released.
pub fn downgrade(&self) -> AnyWeakModel {
AnyWeakModel {
entity_id: self.entity_id,
@@ -211,6 +216,8 @@ impl AnyModel {
}
}
+ /// Converts this model handle into a strongly-typed model handle of the given type.
+ /// If this model handle is not of the specified type, returns itself as an error variant.
pub fn downcast<T: 'static>(self) -> Result<Model<T>, AnyModel> {
if TypeId::of::<T>() == self.entity_type {
Ok(Model {
@@ -274,7 +281,7 @@ impl Drop for AnyModel {
entity_map
.write()
.leak_detector
- .handle_dropped(self.entity_id, self.handle_id)
+ .handle_released(self.entity_id, self.handle_id)
}
}
}
@@ -307,6 +314,8 @@ impl std::fmt::Debug for AnyModel {
}
}
+/// A strong, well typed reference to a struct which is managed
+/// by GPUI
#[derive(Deref, DerefMut)]
pub struct Model<T> {
#[deref]
@@ -368,10 +377,12 @@ impl<T: 'static> Model<T> {
self.any_model
}
+ /// Grab a reference to this entity from the context.
pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
cx.entities.read(self)
}
+ /// Read the entity referenced by this model with the given function.
pub fn read_with<R, C: Context>(
&self,
cx: &C,
@@ -437,6 +448,7 @@ impl<T> PartialEq<WeakModel<T>> for Model<T> {
}
}
+/// A type erased, weak reference to a model.
#[derive(Clone)]
pub struct AnyWeakModel {
pub(crate) entity_id: EntityId,
@@ -445,10 +457,12 @@ pub struct AnyWeakModel {
}
impl AnyWeakModel {
+ /// Get the entity ID associated with this weak reference.
pub fn entity_id(&self) -> EntityId {
self.entity_id
}
+ /// Check if this weak handle can be upgraded, or if the model has already been dropped
pub fn is_upgradable(&self) -> bool {
let ref_count = self
.entity_ref_counts
@@ -458,6 +472,7 @@ impl AnyWeakModel {
ref_count > 0
}
+ /// Upgrade this weak model reference to a strong reference.
pub fn upgrade(&self) -> Option<AnyModel> {
let ref_counts = &self.entity_ref_counts.upgrade()?;
let ref_counts = ref_counts.read();
@@ -485,14 +500,15 @@ impl AnyWeakModel {
})
}
+ /// Assert that model referenced by this weak handle has been released.
#[cfg(any(test, feature = "test-support"))]
- pub fn assert_dropped(&self) {
+ pub fn assert_released(&self) {
self.entity_ref_counts
.upgrade()
.unwrap()
.write()
.leak_detector
- .assert_dropped(self.entity_id);
+ .assert_released(self.entity_id);
if self
.entity_ref_counts
@@ -527,6 +543,7 @@ impl PartialEq for AnyWeakModel {
impl Eq for AnyWeakModel {}
+/// A weak reference to a model of the given type.
#[derive(Deref, DerefMut)]
pub struct WeakModel<T> {
#[deref]
@@ -617,12 +634,12 @@ lazy_static::lazy_static! {
#[cfg(any(test, feature = "test-support"))]
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
-pub struct HandleId {
+pub(crate) struct HandleId {
id: u64, // id of the handle itself, not the pointed at object
}
#[cfg(any(test, feature = "test-support"))]
-pub struct LeakDetector {
+pub(crate) struct LeakDetector {
next_handle_id: u64,
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
}
@@ -641,12 +658,12 @@ impl LeakDetector {
handle_id
}
- pub fn handle_dropped(&mut self, entity_id: EntityId, handle_id: HandleId) {
+ pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) {
let handles = self.entity_handles.entry(entity_id).or_default();
handles.remove(&handle_id);
}
- pub fn assert_dropped(&mut self, entity_id: EntityId) {
+ pub fn assert_released(&mut self, entity_id: EntityId) {
let handles = self.entity_handles.entry(entity_id).or_default();
if !handles.is_empty() {
for (_, backtrace) in handles {
@@ -11,6 +11,7 @@ use std::{
future::Future,
};
+/// The app context, with specialized behavior for the given model.
#[derive(Deref, DerefMut)]
pub struct ModelContext<'a, T> {
#[deref]
@@ -24,20 +25,24 @@ impl<'a, T: 'static> ModelContext<'a, T> {
Self { app, model_state }
}
+ /// The entity id of the model backing this context.
pub fn entity_id(&self) -> EntityId {
self.model_state.entity_id
}
+ /// Returns a handle to the model belonging to this context.
pub fn handle(&self) -> Model<T> {
self.weak_model()
.upgrade()
.expect("The entity must be alive if we have a model context")
}
+ /// Returns a weak handle to the model belonging to this context.
pub fn weak_model(&self) -> WeakModel<T> {
self.model_state.clone()
}
+ /// Arranges for the given function to be called whenever [ModelContext::notify] or [ViewContext::notify] is called with the given model or view.
pub fn observe<W, E>(
&mut self,
entity: &E,
@@ -59,6 +64,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
})
}
+ /// Subscribe to an event type from another model or view
pub fn subscribe<T2, E, Evt>(
&mut self,
entity: &E,
@@ -81,6 +87,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
})
}
+ /// Register a callback to be invoked when GPUI releases this model.
pub fn on_release(
&mut self,
on_release: impl FnOnce(&mut T, &mut AppContext) + 'static,
@@ -99,6 +106,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
subscription
}
+ /// Register a callback to be run on the release of another model or view
pub fn observe_release<T2, E>(
&mut self,
entity: &E,
@@ -124,6 +132,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
subscription
}
+ /// Register a callback to for updates to the given global
pub fn observe_global<G: 'static>(
&mut self,
mut f: impl FnMut(&mut T, &mut ModelContext<'_, T>) + 'static,
@@ -140,6 +149,8 @@ impl<'a, T: 'static> ModelContext<'a, T> {
subscription
}
+ /// Arrange for the given function to be invoked whenever the application is quit.
+ /// The future returned from this callback will be polled for up to [gpui::SHUTDOWN_TIMEOUT] until the app fully quits.
pub fn on_app_quit<Fut>(
&mut self,
mut on_quit: impl FnMut(&mut T, &mut ModelContext<T>) -> Fut + 'static,
@@ -165,6 +176,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
subscription
}
+ /// Tell GPUI that this model has changed and observers of it should be notified.
pub fn notify(&mut self) {
if self
.app
@@ -177,6 +189,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
}
}
+ /// Update the given global
pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
where
G: 'static,
@@ -187,6 +200,9 @@ impl<'a, T: 'static> ModelContext<'a, T> {
result
}
+ /// Spawn the future returned by the given function.
+ /// The function is provided a weak handle to the model owned by this context and a context that can be held across await points.
+ /// The returned task must be held or detached.
pub fn spawn<Fut, R>(&self, f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut) -> Task<R>
where
T: 'static,
@@ -199,6 +215,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
}
impl<'a, T> ModelContext<'a, T> {
+ /// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts.
pub fn emit<Evt>(&mut self, event: Evt)
where
T: EventEmitter<Evt>,
@@ -1,11 +1,11 @@
#![deny(missing_docs)]
use crate::{
- div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
- BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
- IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task,
- TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext,
- WindowContext, WindowHandle, WindowOptions,
+ Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
+ AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter,
+ ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point,
+ Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View,
+ ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
@@ -167,10 +167,14 @@ impl TestAppContext {
}
/// Adds a new window with no content.
- pub fn add_empty_window(&mut self) -> AnyWindowHandle {
+ pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
let mut cx = self.app.borrow_mut();
- cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {}))
- .any_handle
+ let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ()));
+ drop(cx);
+ let cx = Box::new(VisualTestContext::from_window(*window.deref(), self));
+ cx.run_until_parked();
+ // it might be nice to try and cleanup these at the end of each test.
+ Box::leak(cx)
}
/// Adds a new window, and returns its root view and a `VisualTestContext` which can be used
@@ -564,6 +568,11 @@ pub struct VisualTestContext {
}
impl<'a> VisualTestContext {
+ /// Get the underlying window handle underlying this context.
+ pub fn handle(&self) -> AnyWindowHandle {
+ self.window
+ }
+
/// Provides the `WindowContext` for the duration of the closure.
pub fn update<R>(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R {
self.cx.update_window(self.window, |_, cx| f(cx)).unwrap()
@@ -609,6 +618,36 @@ impl<'a> VisualTestContext {
self.cx.simulate_input(self.window, input)
}
+ /// Draw an element to the window. Useful for simulating events or actions
+ pub fn draw(
+ &mut self,
+ origin: Point<Pixels>,
+ space: Size<AvailableSpace>,
+ f: impl FnOnce(&mut WindowContext) -> AnyElement,
+ ) {
+ self.update(|cx| {
+ let entity_id = cx
+ .window
+ .root_view
+ .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.refresh();
+ })
+ }
+
+ /// Simulate an event from the platform, e.g. a SrollWheelEvent
+ /// Make sure you've called [VisualTestContext::draw] first!
+ pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
+ self.test_window(self.window)
+ .simulate_input(event.to_platform_input());
+ self.background_executor.run_until_parked();
+ }
+
/// Simulates the user blurring the window.
pub fn deactivate_window(&mut self) {
if Some(self.window) == self.test_platform.active_window() {
@@ -763,12 +802,3 @@ impl AnyWindowHandle {
self.update(cx, |_, cx| cx.new_view(build_view)).unwrap()
}
}
-
-/// An EmptyView for testing.
-pub struct EmptyView {}
-
-impl Render for EmptyView {
- fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> impl IntoElement {
- div()
- }
-}
@@ -115,6 +115,12 @@ pub trait Render: 'static + Sized {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
}
+impl Render for () {
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+ ()
+ }
+}
+
/// 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.
pub trait RenderOnce: 'static {
@@ -30,6 +30,7 @@ struct StateInner {
logical_scroll_top: Option<ListOffset>,
alignment: ListAlignment,
overdraw: Pixels,
+ reset: bool,
#[allow(clippy::type_complexity)]
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
}
@@ -92,11 +93,17 @@ impl ListState {
alignment: orientation,
overdraw,
scroll_handler: None,
+ reset: false,
})))
}
+ /// Reset this instantiation of the list state.
+ ///
+ /// Note that this will cause scroll events to be dropped until the next paint.
pub fn reset(&self, element_count: usize) {
let state = &mut *self.0.borrow_mut();
+ state.reset = true;
+
state.logical_scroll_top = None;
state.items = SumTree::new();
state
@@ -152,11 +159,13 @@ impl ListState {
scroll_top.item_ix = item_count;
scroll_top.offset_in_item = px(0.);
}
+
state.logical_scroll_top = Some(scroll_top);
}
pub fn scroll_to_reveal_item(&self, ix: usize) {
let state = &mut *self.0.borrow_mut();
+
let mut scroll_top = state.logical_scroll_top();
let height = state
.last_layout_bounds
@@ -187,9 +196,9 @@ impl ListState {
/// Get the bounds for the given item in window coordinates.
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
let state = &*self.0.borrow();
+
let bounds = state.last_layout_bounds.unwrap_or_default();
let scroll_top = state.logical_scroll_top();
-
if ix < scroll_top.item_ix {
return None;
}
@@ -230,6 +239,12 @@ impl StateInner {
delta: Point<Pixels>,
cx: &mut WindowContext,
) {
+ // Drop scroll events after a reset, since we can't calculate
+ // the new logical scroll top without the item heights
+ if self.reset {
+ return;
+ }
+
let scroll_max = (self.items.summary().height - height).max(px(0.));
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
.max(px(0.))
@@ -325,6 +340,8 @@ impl Element for List {
) {
let state = &mut *self.state.0.borrow_mut();
+ state.reset = false;
+
// If the width of the list has changed, invalidate all cached item heights
if state.last_layout_bounds.map_or(true, |last_bounds| {
last_bounds.size.width != bounds.size.width
@@ -346,8 +363,9 @@ impl Element for List {
height: AvailableSpace::MinContent,
};
- // Render items after the scroll top, including those in the trailing overdraw
let mut cursor = old_items.cursor::<Count>();
+
+ // Render items after the scroll top, including those in the trailing overdraw
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
for (ix, item) in cursor.by_ref().enumerate() {
let visible_height = rendered_height - scroll_top.offset_in_item;
@@ -461,6 +479,7 @@ impl Element for List {
let list_state = self.state.clone();
let height = bounds.size.height;
+
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& bounds.contains(&event.position)
@@ -562,3 +581,49 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
self.0.partial_cmp(&other.height).unwrap()
}
}
+
+#[cfg(test)]
+mod test {
+
+ use gpui::{ScrollDelta, ScrollWheelEvent};
+
+ use crate::{self as gpui, TestAppContext};
+
+ #[gpui::test]
+ fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) {
+ use crate::{div, list, point, px, size, Element, ListState, Styled};
+
+ let cx = cx.add_empty_window();
+
+ let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| {
+ div().h(px(10.)).w_full().into_any()
+ });
+
+ // Ensure that the list is scrolled to the top
+ state.scroll_to(gpui::ListOffset {
+ item_ix: 0,
+ offset_in_item: px(0.0),
+ });
+
+ // Paint
+ cx.draw(
+ point(px(0.), px(0.)),
+ size(px(100.), px(20.)).into(),
+ |_| list(state.clone()).w_full().h_full().z_index(10).into_any(),
+ );
+
+ // Reset
+ state.reset(5);
+
+ // And then receive a scroll event _before_ the next paint
+ cx.simulate_event(ScrollWheelEvent {
+ position: point(px(1.), px(1.)),
+ delta: ScrollDelta::Pixels(point(px(0.), px(-500.))),
+ ..Default::default()
+ });
+
+ // Scroll position should stay at the top of the list
+ assert_eq!(state.logical_scroll_top().item_ix, 0);
+ assert_eq!(state.logical_scroll_top().offset_in_item, px(0.));
+ }
+}
@@ -109,9 +109,10 @@ type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
/// BackgroundExecutor lets you run things on background threads.
/// In production this is a thread pool with no ordering guarantees.
-/// In tests this is simalated by running tasks one by one in a deterministic
+/// In tests this is simulated by running tasks one by one in a deterministic
/// (but arbitrary) order controlled by the `SEED` environment variable.
impl BackgroundExecutor {
+ #[doc(hidden)]
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher }
}
@@ -149,7 +150,7 @@ impl BackgroundExecutor {
Task::Spawned(task)
}
- /// Used by the test harness to run an async test in a syncronous fashion.
+ /// Used by the test harness to run an async test in a synchronous fashion.
#[cfg(any(test, feature = "test-support"))]
#[track_caller]
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
@@ -276,7 +277,7 @@ impl BackgroundExecutor {
/// Returns a task that will complete after the given duration.
/// Depending on other concurrent tasks the elapsed duration may be longer
- /// than reqested.
+ /// than requested.
pub fn timer(&self, duration: Duration) -> Task<()> {
let (runnable, task) = async_task::spawn(async move {}, {
let dispatcher = self.dispatcher.clone();
@@ -1,8 +1,14 @@
use crate::{
- div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
+ point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
};
use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
+use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
+
+pub trait InputEvent: Sealed + 'static {
+ fn to_platform_input(self) -> PlatformInput;
+}
+pub trait KeyEvent: InputEvent {}
+pub trait MouseEvent: InputEvent {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyDownEvent {
@@ -10,16 +16,40 @@ pub struct KeyDownEvent {
pub is_held: bool,
}
+impl Sealed for KeyDownEvent {}
+impl InputEvent for KeyDownEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::KeyDown(self)
+ }
+}
+impl KeyEvent for KeyDownEvent {}
+
#[derive(Clone, Debug)]
pub struct KeyUpEvent {
pub keystroke: Keystroke,
}
+impl Sealed for KeyUpEvent {}
+impl InputEvent for KeyUpEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::KeyUp(self)
+ }
+}
+impl KeyEvent for KeyUpEvent {}
+
#[derive(Clone, Debug, Default)]
pub struct ModifiersChangedEvent {
pub modifiers: Modifiers,
}
+impl Sealed for ModifiersChangedEvent {}
+impl InputEvent for ModifiersChangedEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::ModifiersChanged(self)
+ }
+}
+impl KeyEvent for ModifiersChangedEvent {}
+
impl Deref for ModifiersChangedEvent {
type Target = Modifiers;
@@ -30,9 +60,10 @@ impl Deref for ModifiersChangedEvent {
/// The phase of a touch motion event.
/// Based on the winit enum of the same name.
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Default)]
pub enum TouchPhase {
Started,
+ #[default]
Moved,
Ended,
}
@@ -45,6 +76,14 @@ pub struct MouseDownEvent {
pub click_count: usize,
}
+impl Sealed for MouseDownEvent {}
+impl InputEvent for MouseDownEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::MouseDown(self)
+ }
+}
+impl MouseEvent for MouseDownEvent {}
+
#[derive(Clone, Debug, Default)]
pub struct MouseUpEvent {
pub button: MouseButton,
@@ -53,38 +92,20 @@ pub struct MouseUpEvent {
pub click_count: usize,
}
+impl Sealed for MouseUpEvent {}
+impl InputEvent for MouseUpEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::MouseUp(self)
+ }
+}
+impl MouseEvent for MouseUpEvent {}
+
#[derive(Clone, Debug, Default)]
pub struct ClickEvent {
pub down: MouseDownEvent,
pub up: MouseUpEvent,
}
-pub struct Drag<S, R, V, E>
-where
- R: Fn(&mut V, &mut ViewContext<V>) -> E,
- V: 'static,
- E: IntoElement,
-{
- pub state: S,
- pub render_drag_handle: R,
- view_element_types: PhantomData<(V, E)>,
-}
-
-impl<S, R, V, E> Drag<S, R, V, E>
-where
- R: Fn(&mut V, &mut ViewContext<V>) -> E,
- V: 'static,
- E: Element,
-{
- pub fn new(state: S, render_drag_handle: R) -> Self {
- Drag {
- state,
- render_drag_handle,
- view_element_types: Default::default(),
- }
- }
-}
-
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton {
Left,
@@ -130,13 +151,21 @@ pub struct MouseMoveEvent {
pub modifiers: Modifiers,
}
+impl Sealed for MouseMoveEvent {}
+impl InputEvent for MouseMoveEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::MouseMove(self)
+ }
+}
+impl MouseEvent for MouseMoveEvent {}
+
impl MouseMoveEvent {
pub fn dragging(&self) -> bool {
self.pressed_button == Some(MouseButton::Left)
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
pub struct ScrollWheelEvent {
pub position: Point<Pixels>,
pub delta: ScrollDelta,
@@ -144,6 +173,14 @@ pub struct ScrollWheelEvent {
pub touch_phase: TouchPhase,
}
+impl Sealed for ScrollWheelEvent {}
+impl InputEvent for ScrollWheelEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::ScrollWheel(self)
+ }
+}
+impl MouseEvent for ScrollWheelEvent {}
+
impl Deref for ScrollWheelEvent {
type Target = Modifiers;
@@ -201,6 +238,14 @@ pub struct MouseExitEvent {
pub modifiers: Modifiers,
}
+impl Sealed for MouseExitEvent {}
+impl InputEvent for MouseExitEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::MouseExited(self)
+ }
+}
+impl MouseEvent for MouseExitEvent {}
+
impl Deref for MouseExitEvent {
type Target = Modifiers;
@@ -220,7 +265,7 @@ impl ExternalPaths {
impl Render for ExternalPaths {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
- div() // Intentionally left empty because the platform will render icons for the dragged files
+ () // Intentionally left empty because the platform will render icons for the dragged files
}
}
@@ -239,8 +284,16 @@ pub enum FileDropEvent {
Exited,
}
+impl Sealed for FileDropEvent {}
+impl InputEvent for FileDropEvent {
+ fn to_platform_input(self) -> PlatformInput {
+ PlatformInput::FileDrop(self)
+ }
+}
+impl MouseEvent for FileDropEvent {}
+
#[derive(Clone, Debug)]
-pub enum InputEvent {
+pub enum PlatformInput {
KeyDown(KeyDownEvent),
KeyUp(KeyUpEvent),
ModifiersChanged(ModifiersChangedEvent),
@@ -252,19 +305,19 @@ pub enum InputEvent {
FileDrop(FileDropEvent),
}
-impl InputEvent {
+impl PlatformInput {
pub fn position(&self) -> Option<Point<Pixels>> {
match self {
- InputEvent::KeyDown { .. } => None,
- InputEvent::KeyUp { .. } => None,
- InputEvent::ModifiersChanged { .. } => None,
- InputEvent::MouseDown(event) => Some(event.position),
- InputEvent::MouseUp(event) => Some(event.position),
- InputEvent::MouseMove(event) => Some(event.position),
- InputEvent::MouseExited(event) => Some(event.position),
- InputEvent::ScrollWheel(event) => Some(event.position),
- InputEvent::FileDrop(FileDropEvent::Exited) => None,
- InputEvent::FileDrop(
+ 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, .. },
@@ -274,29 +327,29 @@ impl InputEvent {
pub fn mouse_event(&self) -> Option<&dyn Any> {
match self {
- InputEvent::KeyDown { .. } => None,
- InputEvent::KeyUp { .. } => None,
- InputEvent::ModifiersChanged { .. } => None,
- InputEvent::MouseDown(event) => Some(event),
- InputEvent::MouseUp(event) => Some(event),
- InputEvent::MouseMove(event) => Some(event),
- InputEvent::MouseExited(event) => Some(event),
- InputEvent::ScrollWheel(event) => Some(event),
- InputEvent::FileDrop(event) => Some(event),
+ PlatformInput::KeyDown { .. } => None,
+ PlatformInput::KeyUp { .. } => None,
+ PlatformInput::ModifiersChanged { .. } => None,
+ PlatformInput::MouseDown(event) => Some(event),
+ PlatformInput::MouseUp(event) => Some(event),
+ PlatformInput::MouseMove(event) => Some(event),
+ PlatformInput::MouseExited(event) => Some(event),
+ PlatformInput::ScrollWheel(event) => Some(event),
+ PlatformInput::FileDrop(event) => Some(event),
}
}
pub fn keyboard_event(&self) -> Option<&dyn Any> {
match self {
- InputEvent::KeyDown(event) => Some(event),
- InputEvent::KeyUp(event) => Some(event),
- InputEvent::ModifiersChanged(event) => Some(event),
- InputEvent::MouseDown(_) => None,
- InputEvent::MouseUp(_) => None,
- InputEvent::MouseMove(_) => None,
- InputEvent::MouseExited(_) => None,
- InputEvent::ScrollWheel(_) => None,
- InputEvent::FileDrop(_) => None,
+ PlatformInput::KeyDown(event) => Some(event),
+ PlatformInput::KeyUp(event) => Some(event),
+ PlatformInput::ModifiersChanged(event) => Some(event),
+ PlatformInput::MouseDown(_) => None,
+ PlatformInput::MouseUp(_) => None,
+ PlatformInput::MouseMove(_) => None,
+ PlatformInput::MouseExited(_) => None,
+ PlatformInput::ScrollWheel(_) => None,
+ PlatformInput::FileDrop(_) => None,
}
}
}
@@ -209,7 +209,6 @@ mod tests {
);
assert!(!matcher.has_pending_keystrokes());
- eprintln!("PROBLEM AREA");
// If a is prefixed, C will not be dispatched because there
// was a pending binding for it
assert_eq!(
@@ -445,7 +444,7 @@ mod tests {
KeyMatch::Some(vec![Box::new(Dollar)])
);
- // handle Brazillian quote (quote key then space key)
+ // handle Brazilian quote (quote key then space key)
assert_eq!(
matcher.match_keystroke(
&Keystroke::parse("space->\"").unwrap(),
@@ -454,7 +453,7 @@ mod tests {
KeyMatch::Some(vec![Box::new(Quote)])
);
- // handle ctrl+` on a brazillian keyboard
+ // handle ctrl+` on a brazilian keyboard
assert_eq!(
matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
KeyMatch::Some(vec![Box::new(Backtick)])
@@ -7,7 +7,7 @@ mod test;
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
- FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels,
+ FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput,
Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
Size, TaskLabel,
};
@@ -88,7 +88,7 @@ pub(crate) trait Platform: 'static {
fn on_resign_active(&self, callback: Box<dyn FnMut()>);
fn on_quit(&self, callback: Box<dyn FnMut()>);
fn on_reopen(&self, callback: Box<dyn FnMut()>);
- fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
+ fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
@@ -114,15 +114,20 @@ pub(crate) trait Platform: 'static {
fn delete_credentials(&self, url: &str) -> Result<()>;
}
+/// A handle to a platform's display, e.g. a monitor or laptop screen.
pub trait PlatformDisplay: Send + Sync + Debug {
+ /// Get the ID for this display
fn id(&self) -> DisplayId;
+
/// Returns a stable identifier for this display that can be persisted and used
/// across system restarts.
fn uuid(&self) -> Result<Uuid>;
- fn as_any(&self) -> &dyn Any;
+
+ /// Get the bounds for this display
fn bounds(&self) -> Bounds<GlobalPixels>;
}
+/// An opaque identifier for a hardware display
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
pub struct DisplayId(pub(crate) u32);
@@ -134,7 +139,7 @@ impl Debug for DisplayId {
unsafe impl Send for DisplayId {}
-pub trait PlatformWindow {
+pub(crate) trait PlatformWindow {
fn bounds(&self) -> WindowBounds;
fn content_size(&self) -> Size<Pixels>;
fn scale_factor(&self) -> f32;
@@ -155,7 +160,7 @@ pub trait PlatformWindow {
fn zoom(&self);
fn toggle_full_screen(&self);
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
- fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>);
+ fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>);
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>);
@@ -175,6 +180,9 @@ pub trait PlatformWindow {
}
}
+/// This type is public so that our test macro can generate and use it, but it should not
+/// be considered part of our public API.
+#[doc(hidden)]
pub trait PlatformDispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
@@ -190,9 +198,10 @@ pub trait PlatformDispatcher: Send + Sync {
}
}
-pub trait PlatformTextSystem: Send + Sync {
+pub(crate) trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
fn all_font_names(&self) -> Vec<String>;
+ fn all_font_families(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
@@ -214,15 +223,21 @@ pub trait PlatformTextSystem: Send + Sync {
) -> Vec<usize>;
}
+/// Basic metadata about the current application and operating system.
#[derive(Clone, Debug)]
pub struct AppMetadata {
+ /// The name of the current operating system
pub os_name: &'static str,
+
+ /// The operating system's version
pub os_version: Option<SemanticVersion>,
+
+ /// The current version of the application
pub app_version: Option<SemanticVersion>,
}
#[derive(PartialEq, Eq, Hash, Clone)]
-pub enum AtlasKey {
+pub(crate) enum AtlasKey {
Glyph(RenderGlyphParams),
Svg(RenderSvgParams),
Image(RenderImageParams),
@@ -262,19 +277,17 @@ impl From<RenderImageParams> for AtlasKey {
}
}
-pub trait PlatformAtlas: Send + Sync {
+pub(crate) trait PlatformAtlas: Send + Sync {
fn get_or_insert_with<'a>(
&self,
key: &AtlasKey,
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
) -> Result<AtlasTile>;
-
- fn clear(&self);
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[repr(C)]
-pub struct AtlasTile {
+pub(crate) struct AtlasTile {
pub(crate) texture_id: AtlasTextureId,
pub(crate) tile_id: TileId,
pub(crate) bounds: Bounds<DevicePixels>,
@@ -19,7 +19,7 @@ impl Keystroke {
// 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 brazillian keyboard).
+ // specific character (for example `"` is typed as `" space` on a brazilian keyboard).
pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
let mut possibilities = SmallVec::new();
match self.ime_key.as_ref() {
@@ -10,7 +10,7 @@ mod open_type;
mod platform;
mod text_system;
mod window;
-mod window_appearence;
+mod window_appearance;
use crate::{px, size, GlobalPixels, Pixels, Size};
use cocoa::{
@@ -11,7 +11,6 @@ use core_graphics::{
geometry::{CGPoint, CGRect, CGSize},
};
use objc::{msg_send, sel, sel_impl};
-use std::any::Any;
use uuid::Uuid;
#[derive(Debug)]
@@ -154,10 +153,6 @@ impl PlatformDisplay for MacDisplay {
]))
}
- fn as_any(&self) -> &dyn Any {
- self
- }
-
fn bounds(&self) -> Bounds<GlobalPixels> {
unsafe {
let native_bounds = CGDisplayBounds(self.0);
@@ -1,7 +1,7 @@
use crate::{
- point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
- MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
- Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
+ point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
+ MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels,
+ PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase,
};
use cocoa::{
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
@@ -82,7 +82,7 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers {
}
}
-impl InputEvent {
+impl PlatformInput {
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
let event_type = native_event.eventType();
@@ -74,20 +74,6 @@ impl PlatformAtlas for MetalAtlas {
Ok(tile)
}
}
-
- fn clear(&self) {
- let mut lock = self.0.lock();
- lock.tiles_by_key.clear();
- for texture in &mut lock.monochrome_textures {
- texture.clear();
- }
- for texture in &mut lock.polychrome_textures {
- texture.clear();
- }
- for texture in &mut lock.path_textures {
- texture.clear();
- }
- }
}
impl MetalAtlasState {
@@ -1,8 +1,8 @@
use super::{events::key_to_native, BoolExt};
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
- ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker,
- MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
+ ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
+ MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
};
use anyhow::anyhow;
@@ -153,7 +153,7 @@ pub struct MacPlatformState {
resign_active: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
- event: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+ event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
will_open_menu: Option<Box<dyn FnMut()>>,
@@ -637,7 +637,7 @@ impl Platform for MacPlatform {
self.0.lock().reopen = Some(callback);
}
- fn on_event(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+ fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.0.lock().event = Some(callback);
}
@@ -976,7 +976,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
unsafe {
- if let Some(event) = InputEvent::from_native(native_event, None) {
+ if let Some(event) = PlatformInput::from_native(native_event, None) {
let platform = get_mac_platform(this);
let mut lock = platform.0.lock();
if let Some(mut callback) = lock.event.take() {
@@ -85,11 +85,24 @@ impl PlatformTextSystem for MacTextSystem {
};
let mut names = BTreeSet::new();
for descriptor in descriptors.into_iter() {
- names.insert(descriptor.display_name());
+ names.insert(descriptor.font_name());
+ names.insert(descriptor.family_name());
+ names.insert(descriptor.style_name());
+ }
+ if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() {
+ names.extend(fonts_in_memory);
}
names.into_iter().collect()
}
+ fn all_font_families(&self) -> Vec<String> {
+ self.0
+ .read()
+ .system_source
+ .all_families()
+ .expect("core text should never return an error")
+ }
+
fn font_id(&self, font: &Font) -> Result<FontId> {
let lock = self.0.upgradable_read();
if let Some(font_id) = lock.font_selections.get(font) {
@@ -1,9 +1,9 @@
use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
use crate::{
display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths,
- FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke,
- Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+ FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers,
+ ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+ PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
};
use block::ConcreteBlock;
@@ -319,7 +319,7 @@ struct MacWindowState {
renderer: MetalRenderer,
kind: WindowKind,
request_frame_callback: Option<Box<dyn FnMut()>>,
- event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+ event_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
activate_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
@@ -333,7 +333,7 @@ struct MacWindowState {
synthetic_drag_counter: usize,
last_fresh_keydown: Option<Keystroke>,
traffic_light_position: Option<Point<Pixels>>,
- previous_modifiers_changed_event: Option<InputEvent>,
+ previous_modifiers_changed_event: Option<PlatformInput>,
// State tracking what the IME did after the last request
ime_state: ImeState,
// Retains the last IME Text
@@ -928,7 +928,7 @@ impl PlatformWindow for MacWindow {
self.0.as_ref().lock().request_frame_callback = Some(callback);
}
- fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
+ fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.0.as_ref().lock().event_callback = Some(callback);
}
@@ -1053,9 +1053,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
let mut lock = window_state.as_ref().lock();
let window_height = lock.content_size().height;
- let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
+ let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
- if let Some(InputEvent::KeyDown(event)) = event {
+ if let Some(PlatformInput::KeyDown(event)) = event {
// For certain keystrokes, macOS will first dispatch a "key equivalent" event.
// If that event isn't handled, it will then dispatch a "key down" event. GPUI
// makes no distinction between these two types of events, so we need to ignore
@@ -1102,7 +1102,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
.flatten()
.is_some();
if !is_composing {
- handled = callback(InputEvent::KeyDown(event));
+ handled = callback(PlatformInput::KeyDown(event));
}
if !handled {
@@ -1146,11 +1146,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
let window_height = lock.content_size().height;
- let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
+ let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) };
if let Some(mut event) = event {
match &mut event {
- InputEvent::MouseDown(
+ PlatformInput::MouseDown(
event @ MouseDownEvent {
button: MouseButton::Left,
modifiers: Modifiers { control: true, .. },
@@ -1172,7 +1172,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
// Because we map a ctrl-left_down to a right_down -> right_up let's ignore
// the ctrl-left_up to avoid having a mismatch in button down/up events if the
// user is still holding ctrl when releasing the left mouse button
- InputEvent::MouseUp(
+ PlatformInput::MouseUp(
event @ MouseUpEvent {
button: MouseButton::Left,
modifiers: Modifiers { control: true, .. },
@@ -1194,7 +1194,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
};
match &event {
- InputEvent::MouseMove(
+ PlatformInput::MouseMove(
event @ MouseMoveEvent {
pressed_button: Some(_),
..
@@ -1216,15 +1216,15 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
}
}
- InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
+ PlatformInput::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
- InputEvent::MouseUp(MouseUpEvent { .. }) => {
+ PlatformInput::MouseUp(MouseUpEvent { .. }) => {
lock.synthetic_drag_counter += 1;
}
- InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
+ PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
// Only raise modifiers changed event when they have actually changed
- if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
+ if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
modifiers: prev_modifiers,
})) = &lock.previous_modifiers_changed_event
{
@@ -1258,7 +1258,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
key: ".".into(),
ime_key: None,
};
- let event = InputEvent::KeyDown(KeyDownEvent {
+ let event = PlatformInput::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held: false,
});
@@ -1655,7 +1655,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr
if send_new_event(&window_state, {
let position = drag_event_position(&window_state, dragging_info);
let paths = external_paths_from_event(dragging_info);
- InputEvent::FileDrop(FileDropEvent::Entered { position, paths })
+ PlatformInput::FileDrop(FileDropEvent::Entered { position, paths })
}) {
window_state.lock().external_files_dragged = true;
NSDragOperationCopy
@@ -1669,7 +1669,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
let position = drag_event_position(&window_state, dragging_info);
if send_new_event(
&window_state,
- InputEvent::FileDrop(FileDropEvent::Pending { position }),
+ PlatformInput::FileDrop(FileDropEvent::Pending { position }),
) {
NSDragOperationCopy
} else {
@@ -1679,7 +1679,10 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr
extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) };
- send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
+ send_new_event(
+ &window_state,
+ PlatformInput::FileDrop(FileDropEvent::Exited),
+ );
window_state.lock().external_files_dragged = false;
}
@@ -1688,7 +1691,7 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -
let position = drag_event_position(&window_state, dragging_info);
if send_new_event(
&window_state,
- InputEvent::FileDrop(FileDropEvent::Submit { position }),
+ PlatformInput::FileDrop(FileDropEvent::Submit { position }),
) {
YES
} else {
@@ -1712,7 +1715,10 @@ fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths {
extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
let window_state = unsafe { get_window_state(this) };
- send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited));
+ send_new_event(
+ &window_state,
+ PlatformInput::FileDrop(FileDropEvent::Exited),
+ );
}
async fn synthetic_drag(
@@ -1727,7 +1733,7 @@ async fn synthetic_drag(
if lock.synthetic_drag_counter == drag_id {
if let Some(mut callback) = lock.event_callback.take() {
drop(lock);
- callback(InputEvent::MouseMove(event.clone()));
+ callback(PlatformInput::MouseMove(event.clone()));
window_state.lock().event_callback = Some(callback);
}
} else {
@@ -1737,7 +1743,7 @@ async fn synthetic_drag(
}
}
-fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
+fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: PlatformInput) -> bool {
let window_state = window_state_lock.lock().event_callback.take();
if let Some(mut callback) = window_state {
callback(e);
@@ -31,10 +31,6 @@ impl PlatformDisplay for TestDisplay {
Ok(self.uuid)
}
- fn as_any(&self) -> &dyn std::any::Any {
- unimplemented!()
- }
-
fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
self.bounds
}
@@ -239,7 +239,7 @@ impl Platform for TestPlatform {
unimplemented!()
}
- fn on_event(&self, _callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
+ fn on_event(&self, _callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
unimplemented!()
}
@@ -1,7 +1,7 @@
use crate::{
- px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
- Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
- Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
+ px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
+ Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
+ Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -19,7 +19,7 @@ pub struct TestWindowState {
platform: Weak<TestPlatform>,
sprite_atlas: Arc<dyn PlatformAtlas>,
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
- input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+ input_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
moved_callback: Option<Box<dyn FnMut()>>,
@@ -85,7 +85,7 @@ impl TestWindow {
self.0.lock().active_status_change_callback = Some(callback);
}
- pub fn simulate_input(&mut self, event: InputEvent) -> bool {
+ pub fn simulate_input(&mut self, event: PlatformInput) -> bool {
let mut lock = self.0.lock();
let Some(mut callback) = lock.input_callback.take() else {
return false;
@@ -97,7 +97,7 @@ impl TestWindow {
}
pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
- if self.simulate_input(InputEvent::KeyDown(KeyDownEvent {
+ if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
})) {
@@ -220,7 +220,7 @@ impl PlatformWindow for TestWindow {
fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
- fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
+ fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
self.0.lock().input_callback = Some(callback)
}
@@ -325,10 +325,4 @@ impl PlatformAtlas for TestAtlas {
Ok(state.tiles[key].clone())
}
-
- fn clear(&self) {
- let mut state = self.0.lock();
- state.tiles = HashMap::default();
- state.next_id = 0;
- }
}
@@ -93,7 +93,7 @@ impl Scene {
}
}
- pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+ pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
@@ -440,7 +440,7 @@ pub enum PrimitiveKind {
Surface,
}
-pub enum Primitive {
+pub(crate) enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
@@ -589,7 +589,7 @@ impl From<Shadow> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
-pub struct MonochromeSprite {
+pub(crate) struct MonochromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -622,7 +622,7 @@ impl From<MonochromeSprite> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
-pub struct PolychromeSprite {
+pub(crate) struct PolychromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
@@ -42,7 +42,7 @@ pub struct Style {
#[refineable]
pub inset: Edges<Length>,
- // Size properies
+ // Size properties
/// Sets the initial size of the item
#[refineable]
pub size: Size<Length>,
@@ -79,7 +79,7 @@ pub struct Style {
#[refineable]
pub gap: Size<DefiniteLength>,
- // Flexbox properies
+ // Flexbox properties
/// Which direction does the main axis flow in?
pub flex_direction: FlexDirection,
/// Should elements wrap, or stay in a single line?
@@ -502,7 +502,7 @@ impl Default for Style {
max_size: Size::auto(),
aspect_ratio: None,
gap: Size::default(),
- // Aligment
+ // Alignment
align_items: None,
align_self: None,
align_content: None,
@@ -13,7 +13,7 @@ use crate::{
SharedString, Size, UnderlineStyle,
};
use anyhow::anyhow;
-use collections::{FxHashMap, FxHashSet};
+use collections::{BTreeSet, FxHashMap, FxHashSet};
use core::fmt;
use itertools::Itertools;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -47,7 +47,7 @@ pub struct TextSystem {
}
impl TextSystem {
- pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+ pub(crate) fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem {
line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
platform_text_system,
@@ -66,15 +66,18 @@ impl TextSystem {
}
pub fn all_font_names(&self) -> Vec<String> {
- let mut families = self.platform_text_system.all_font_names();
- families.append(
- &mut self
- .fallback_font_stack
+ let mut names: BTreeSet<_> = self
+ .platform_text_system
+ .all_font_names()
+ .into_iter()
+ .collect();
+ names.extend(self.platform_text_system.all_font_families().into_iter());
+ names.extend(
+ self.fallback_font_stack
.iter()
- .map(|font| font.family.to_string())
- .collect(),
+ .map(|font| font.family.to_string()),
);
- families
+ names.into_iter().collect()
}
pub fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
self.platform_text_system.add_fonts(fonts)
@@ -13,7 +13,7 @@ pub struct LineWrapper {
impl LineWrapper {
pub const MAX_INDENT: u32 = 256;
- pub fn new(
+ pub(crate) fn new(
font_id: FontId,
font_size: Pixels,
text_system: Arc<dyn PlatformTextSystem>,
@@ -1,3 +1,5 @@
+#![deny(missing_docs)]
+
use crate::{
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
Bounds, ContentMask, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
@@ -11,12 +13,16 @@ use std::{
hash::{Hash, Hasher},
};
+/// A view is a piece of state that can be presented on screen by implementing the [Render] trait.
+/// Views implement [Element] and can composed with other views, and every window is created with a root view.
pub struct View<V> {
+ /// A view is just a [Model] whose type implements `Render`, and the model is accessible via this field.
pub model: Model<V>,
}
impl<V> Sealed for View<V> {}
+#[doc(hidden)]
pub struct AnyViewState {
root_style: Style,
cache_key: Option<ViewCacheKey>,
@@ -58,6 +64,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.
pub fn update<C, R>(
&self,
cx: &mut C,
@@ -69,10 +76,12 @@ impl<V: 'static> View<V> {
cx.update_view(self, f)
}
+ /// Obtain a read-only reference to this view's state.
pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
self.model.read(cx)
}
+ /// Gets a [FocusHandle] for this view when its state implements [FocusableView].
pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
where
V: FocusableView,
@@ -131,19 +140,24 @@ impl<V> PartialEq for View<V> {
impl<V> Eq for View<V> {}
+/// A weak variant of [View] which does not prevent the view from being released.
pub struct WeakView<V> {
pub(crate) model: WeakModel<V>,
}
impl<V: 'static> WeakView<V> {
+ /// Gets the entity id associated with this handle.
pub fn entity_id(&self) -> EntityId {
self.model.entity_id
}
+ /// Obtain a strong handle for the view if it hasn't been released.
pub fn upgrade(&self) -> Option<View<V>> {
Entity::upgrade_from(self)
}
+ /// Update 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,
cx: &mut C,
@@ -157,9 +171,10 @@ impl<V: 'static> WeakView<V> {
Ok(view.update(cx, f)).flatten()
}
+ /// Assert that the view referenced by this handle has been released.
#[cfg(any(test, feature = "test-support"))]
- pub fn assert_dropped(&self) {
- self.model.assert_dropped()
+ pub fn assert_released(&self) {
+ self.model.assert_released()
}
}
@@ -185,6 +200,7 @@ impl<V> PartialEq for WeakView<V> {
impl<V> Eq for WeakView<V> {}
+/// A dynamically-typed handle to a view, which can be downcast to a [View] for a specific type.
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
@@ -193,11 +209,15 @@ pub struct AnyView {
}
impl AnyView {
+ /// Indicate that this view should be cached when using it as an element.
+ /// When using this method, the view's previous layout and paint will be recycled from the previous frame if [ViewContext::notify] has not been called since it was rendered.
+ /// The one exception is when [WindowContext::refresh] is called, in which case caching is ignored.
pub fn cached(mut self) -> Self {
self.cache = true;
self
}
+ /// Convert this to a weak handle.
pub fn downgrade(&self) -> AnyWeakView {
AnyWeakView {
model: self.model.downgrade(),
@@ -205,6 +225,8 @@ impl AnyView {
}
}
+ /// Convert this to a [View] of a specific type.
+ /// If this handle does not contain a view of the specified type, returns itself in an `Err` variant.
pub fn downcast<T: 'static>(self) -> Result<View<T>, Self> {
match self.model.downcast() {
Ok(model) => Ok(View { model }),
@@ -216,10 +238,12 @@ impl AnyView {
}
}
+ /// Gets the [TypeId] of the underlying view.
pub fn entity_type(&self) -> TypeId {
self.model.entity_type
}
+ /// Gets the entity id of this handle.
pub fn entity_id(&self) -> EntityId {
self.model.entity_id()
}
@@ -337,12 +361,14 @@ 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),
}
impl AnyWeakView {
+ /// Convert to a strongly-typed handle if the referenced view has not yet been released.
pub fn upgrade(&self) -> Option<AnyView> {
let model = self.model.upgrade()?;
Some(AnyView {
@@ -5,13 +5,14 @@ use crate::{
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect,
Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla,
- ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId,
- Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent,
- Path, Pixels, PlatformAtlas, PlatformDisplay, 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,
+ 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,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -971,7 +972,7 @@ impl<'a> WindowContext<'a> {
/// 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: 'static>(
+ pub fn on_mouse_event<Event: MouseEvent>(
&mut self,
mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static,
) {
@@ -999,7 +1000,7 @@ impl<'a> WindowContext<'a> {
///
/// 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: 'static>(
+ pub fn on_key_event<Event: KeyEvent>(
&mut self,
listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
) {
@@ -1620,7 +1621,7 @@ impl<'a> WindowContext<'a> {
}
/// Dispatch a mouse or keyboard event on the window.
- pub fn dispatch_event(&mut self, event: InputEvent) -> bool {
+ pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
// Handlers may set this to false by calling `stop_propagation`.
self.app.propagate_event = true;
// Handlers may set this to true by calling `prevent_default`.
@@ -1629,37 +1630,37 @@ impl<'a> WindowContext<'a> {
let event = match event {
// Track the mouse position with our own state, since accessing the platform
// API for the mouse position can only occur on the main thread.
- InputEvent::MouseMove(mouse_move) => {
+ PlatformInput::MouseMove(mouse_move) => {
self.window.mouse_position = mouse_move.position;
self.window.modifiers = mouse_move.modifiers;
- InputEvent::MouseMove(mouse_move)
+ PlatformInput::MouseMove(mouse_move)
}
- InputEvent::MouseDown(mouse_down) => {
+ PlatformInput::MouseDown(mouse_down) => {
self.window.mouse_position = mouse_down.position;
self.window.modifiers = mouse_down.modifiers;
- InputEvent::MouseDown(mouse_down)
+ PlatformInput::MouseDown(mouse_down)
}
- InputEvent::MouseUp(mouse_up) => {
+ PlatformInput::MouseUp(mouse_up) => {
self.window.mouse_position = mouse_up.position;
self.window.modifiers = mouse_up.modifiers;
- InputEvent::MouseUp(mouse_up)
+ PlatformInput::MouseUp(mouse_up)
}
- InputEvent::MouseExited(mouse_exited) => {
+ PlatformInput::MouseExited(mouse_exited) => {
self.window.modifiers = mouse_exited.modifiers;
- InputEvent::MouseExited(mouse_exited)
+ PlatformInput::MouseExited(mouse_exited)
}
- InputEvent::ModifiersChanged(modifiers_changed) => {
+ PlatformInput::ModifiersChanged(modifiers_changed) => {
self.window.modifiers = modifiers_changed.modifiers;
- InputEvent::ModifiersChanged(modifiers_changed)
+ PlatformInput::ModifiersChanged(modifiers_changed)
}
- InputEvent::ScrollWheel(scroll_wheel) => {
+ PlatformInput::ScrollWheel(scroll_wheel) => {
self.window.mouse_position = scroll_wheel.position;
self.window.modifiers = scroll_wheel.modifiers;
- InputEvent::ScrollWheel(scroll_wheel)
+ PlatformInput::ScrollWheel(scroll_wheel)
}
// Translate dragging and dropping of external files from the operating system
// to internal drag and drop events.
- InputEvent::FileDrop(file_drop) => match file_drop {
+ PlatformInput::FileDrop(file_drop) => match file_drop {
FileDropEvent::Entered { position, paths } => {
self.window.mouse_position = position;
if self.active_drag.is_none() {
@@ -1669,7 +1670,7 @@ impl<'a> WindowContext<'a> {
cursor_offset: position,
});
}
- InputEvent::MouseMove(MouseMoveEvent {
+ PlatformInput::MouseMove(MouseMoveEvent {
position,
pressed_button: Some(MouseButton::Left),
modifiers: Modifiers::default(),
@@ -1677,7 +1678,7 @@ impl<'a> WindowContext<'a> {
}
FileDropEvent::Pending { position } => {
self.window.mouse_position = position;
- InputEvent::MouseMove(MouseMoveEvent {
+ PlatformInput::MouseMove(MouseMoveEvent {
position,
pressed_button: Some(MouseButton::Left),
modifiers: Modifiers::default(),
@@ -1686,21 +1687,21 @@ impl<'a> WindowContext<'a> {
FileDropEvent::Submit { position } => {
self.activate(true);
self.window.mouse_position = position;
- InputEvent::MouseUp(MouseUpEvent {
+ PlatformInput::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position,
modifiers: Modifiers::default(),
click_count: 1,
})
}
- FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent {
+ FileDropEvent::Exited => PlatformInput::MouseUp(MouseUpEvent {
button: MouseButton::Left,
position: Point::default(),
modifiers: Modifiers::default(),
click_count: 1,
}),
},
- InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => event,
+ PlatformInput::KeyDown(_) | PlatformInput::KeyUp(_) => event,
};
if let Some(any_mouse_event) = event.mouse_event() {
@@ -2130,7 +2131,7 @@ impl<'a> WindowContext<'a> {
.unwrap();
// Actual: Option<AnyElement> <- View
- // Requested: () <- AnyElemet
+ // Requested: () <- AnyElement
let state = state_box
.take()
.expect("element state is already on the stack");
@@ -2983,7 +2984,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
/// Add a listener for any mouse event that occurs in the window.
/// This is a fairly low level method.
/// Typically, you'll want to use methods on UI elements, which perform bounds checking etc.
- pub fn on_mouse_event<Event: 'static>(
+ pub fn on_mouse_event<Event: MouseEvent>(
&mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
@@ -2996,7 +2997,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
}
/// Register a callback to be invoked when the given Key Event is dispatched to the window.
- pub fn on_key_event<Event: 'static>(
+ pub fn on_key_event<Event: KeyEvent>(
&mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
) {
@@ -53,7 +53,7 @@ pub fn style_helpers(input: TokenStream) -> TokenStream {
/// variety of scenarios and interleavings just by changing the seed.
///
/// #[gpui::test] also takes three different arguments:
-/// - `#[gpui::test(interations=10)]` will run the test ten times with a different initial SEED.
+/// - `#[gpui::test(iterations=10)]` will run the test ten times with a different initial SEED.
/// - `#[gpui::test(retries=3)]` will run the test up to four times if it fails to try and make it pass.
/// - `#[gpui::test(on_failure="crate::test::report_failure")]` will call the specified function after the
/// tests fail so that you can write out more detail about the failure.
@@ -1,6 +1,6 @@
use anyhow::Result;
use chrono::{Datelike, Local, NaiveTime, Timelike};
-use editor::scroll::autoscroll::Autoscroll;
+use editor::scroll::Autoscroll;
use editor::Editor;
use gpui::{actions, AppContext, ViewContext, WindowContext};
use schemars::JsonSchema;
@@ -275,7 +275,7 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
let version_before_format = format_diff.base_version.clone();
buffer.apply_diff(format_diff, cx);
- // The outcome depends on the order of concurrent taks.
+ // The outcome depends on the order of concurrent tasks.
//
// If the edit occurred while searching for trailing whitespace ranges,
// then the trailing whitespace region touched by the edit is left intact.
@@ -951,7 +951,7 @@ impl LanguageRegistry {
if language.fake_adapter.is_some() {
let task = cx.spawn(|cx| async move {
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
- let (server, mut fake_server) = lsp::LanguageServer::fake(
+ let (server, mut fake_server) = lsp::FakeLanguageServer::new(
fake_adapter.name.to_string(),
fake_adapter.capabilities.clone(),
cx.clone(),
@@ -1,5 +1,5 @@
use collections::{HashMap, VecDeque};
-use editor::{Editor, EditorEvent, MoveToEnd};
+use editor::{actions::MoveToEnd, Editor, EditorEvent};
use futures::{channel::mpsc, StreamExt};
use gpui::{
actions, div, AnchorCorner, AnyElement, AppContext, Context, EventEmitter, FocusHandle,
@@ -1,4 +1,4 @@
-use editor::{scroll::autoscroll::Autoscroll, Anchor, Editor, ExcerptId};
+use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
use gpui::{
actions, canvas, div, rems, uniform_list, AnyElement, AppContext, AvailableSpace, Div,
EventEmitter, FocusHandle, FocusableView, Hsla, InteractiveElement, IntoElement, Model,
@@ -1,4 +1,4 @@
-use std::{sync::Arc, time::Duration};
+use std::time::Duration;
use futures::StreamExt;
use gpui::{actions, KeyBinding, Menu, MenuItem};
@@ -12,7 +12,7 @@ actions!(live_kit_client, [Quit]);
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
- gpui::App::production(Arc::new(())).run(|cx| {
+ gpui::App::new().run(|cx| {
#[cfg(any(test, feature = "test-support"))]
println!("USING TEST LIVEKIT");
@@ -584,7 +584,7 @@ impl LanguageServer {
Ok(Arc::new(self))
}
- /// Sends a shutdown request to the language server process and prepares the `LanguageServer` to be dropped.
+ /// Sends a shutdown request to the language server process and prepares the [`LanguageServer`] to be dropped.
pub fn shutdown(&self) -> Option<impl 'static + Send + Future<Output = Option<()>>> {
if let Some(tasks) = self.io_tasks.lock().take() {
let response_handlers = self.response_handlers.clone();
@@ -645,7 +645,7 @@ impl LanguageServer {
self.on_custom_request(T::METHOD, f)
}
- /// Register a handler to inspect all language server process stdio.
+ /// Registers a handler to inspect all language server process stdio.
#[must_use]
pub fn on_io<F>(&self, f: F) -> Subscription
where
@@ -659,17 +659,17 @@ impl LanguageServer {
}
}
- /// Removes a request handler registers via [Self::on_request].
+ /// Removes a request handler registers via [`Self::on_request`].
pub fn remove_request_handler<T: request::Request>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
- /// Removes a notification handler registers via [Self::on_notification].
+ /// Removes a notification handler registers via [`Self::on_notification`].
pub fn remove_notification_handler<T: notification::Notification>(&self) {
self.notification_handlers.lock().remove(T::METHOD);
}
- /// Checks if a notification handler has been registered via [Self::on_notification].
+ /// Checks if a notification handler has been registered via [`Self::on_notification`].
pub fn has_notification_handler<T: notification::Notification>(&self) -> bool {
self.notification_handlers.lock().contains_key(T::METHOD)
}
@@ -873,7 +873,7 @@ impl LanguageServer {
futures::select! {
response = rx.fuse() => {
let elapsed = started.elapsed();
- log::trace!("Took {elapsed:?} to recieve response to {method:?} id {id}");
+ log::trace!("Took {elapsed:?} to receive response to {method:?} id {id}");
response?
}
@@ -972,30 +972,18 @@ pub struct FakeLanguageServer {
}
#[cfg(any(test, feature = "test-support"))]
-impl LanguageServer {
- pub fn full_capabilities() -> ServerCapabilities {
- ServerCapabilities {
- document_highlight_provider: Some(OneOf::Left(true)),
- code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
- document_formatting_provider: Some(OneOf::Left(true)),
- document_range_formatting_provider: Some(OneOf::Left(true)),
- definition_provider: Some(OneOf::Left(true)),
- type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
- ..Default::default()
- }
- }
-
+impl FakeLanguageServer {
/// Construct a fake language server.
- pub fn fake(
+ pub fn new(
name: String,
capabilities: ServerCapabilities,
cx: AsyncAppContext,
- ) -> (Self, FakeLanguageServer) {
+ ) -> (LanguageServer, FakeLanguageServer) {
let (stdin_writer, stdin_reader) = async_pipe::pipe();
let (stdout_writer, stdout_reader) = async_pipe::pipe();
let (notifications_tx, notifications_rx) = channel::unbounded();
- let server = Self::new_internal(
+ let server = LanguageServer::new_internal(
LanguageServerId(0),
stdin_writer,
stdout_reader,
@@ -1008,7 +996,7 @@ impl LanguageServer {
|_| {},
);
let fake = FakeLanguageServer {
- server: Arc::new(Self::new_internal(
+ server: Arc::new(LanguageServer::new_internal(
LanguageServerId(0),
stdout_writer,
stdin_reader,
@@ -1053,14 +1041,29 @@ impl LanguageServer {
}
}
+#[cfg(any(test, feature = "test-support"))]
+impl LanguageServer {
+ pub fn full_capabilities() -> ServerCapabilities {
+ ServerCapabilities {
+ document_highlight_provider: Some(OneOf::Left(true)),
+ code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
+ document_formatting_provider: Some(OneOf::Left(true)),
+ document_range_formatting_provider: Some(OneOf::Left(true)),
+ definition_provider: Some(OneOf::Left(true)),
+ type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
+ ..Default::default()
+ }
+ }
+}
+
#[cfg(any(test, feature = "test-support"))]
impl FakeLanguageServer {
- /// See [LanguageServer::notify]
+ /// See [`LanguageServer::notify`].
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
self.server.notify::<T>(params).ok();
}
- /// See [LanguageServer::request]
+ /// See [`LanguageServer::request`].
pub async fn request<T>(&self, params: T::Params) -> Result<T::Result>
where
T: request::Request,
@@ -1070,7 +1073,7 @@ impl FakeLanguageServer {
self.server.request::<T>(params).await
}
- /// Attempts [try_receive_notification], unwrapping if it has not received the specified type yet.
+ /// Attempts [`Self::try_receive_notification`], unwrapping if it has not received the specified type yet.
pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
self.server.executor.start_waiting();
self.try_receive_notification::<T>().await.unwrap()
@@ -1188,7 +1191,7 @@ mod tests {
#[gpui::test]
async fn test_fake(cx: &mut TestAppContext) {
let (server, mut fake) =
- LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async());
+ FakeLanguageServer::new("the-lsp".to_string(), Default::default(), cx.to_async());
let (message_tx, message_rx) = channel::unbounded();
let (diagnostics_tx, diagnostics_rx) = channel::unbounded();
@@ -72,7 +72,7 @@ pub enum Event {
ids: Vec<ExcerptId>,
},
Edited {
- sigleton_buffer_edited: bool,
+ singleton_buffer_edited: bool,
},
TransactionUndone {
transaction_id: TransactionId,
@@ -1112,7 +1112,7 @@ impl MultiBuffer {
new: edit_start..edit_end,
}]);
cx.emit(Event::Edited {
- sigleton_buffer_edited: false,
+ singleton_buffer_edited: false,
});
cx.emit(Event::ExcerptsAdded {
buffer,
@@ -1138,7 +1138,7 @@ impl MultiBuffer {
new: 0..0,
}]);
cx.emit(Event::Edited {
- sigleton_buffer_edited: false,
+ singleton_buffer_edited: false,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
@@ -1348,7 +1348,7 @@ impl MultiBuffer {
self.subscriptions.publish_mut(edits);
cx.emit(Event::Edited {
- sigleton_buffer_edited: false,
+ singleton_buffer_edited: false,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
@@ -1411,7 +1411,7 @@ impl MultiBuffer {
) {
cx.emit(match event {
language::Event::Edited => Event::Edited {
- sigleton_buffer_edited: true,
+ singleton_buffer_edited: true,
},
language::Event::DirtyChanged => Event::DirtyChanged,
language::Event::Saved => Event::Saved,
@@ -4280,13 +4280,13 @@ mod tests {
events.read().as_slice(),
&[
Event::Edited {
- sigleton_buffer_edited: false
+ singleton_buffer_edited: false
},
Event::Edited {
- sigleton_buffer_edited: false
+ singleton_buffer_edited: false
},
Event::Edited {
- sigleton_buffer_edited: false
+ singleton_buffer_edited: false
}
]
);
@@ -1,6 +1,6 @@
use editor::{
- display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt,
- DisplayPoint, Editor, EditorMode, ToPoint,
+ display_map::ToDisplayPoint, scroll::Autoscroll, Anchor, AnchorRangeExt, DisplayPoint, Editor,
+ EditorMode, ToPoint,
};
use fuzzy::StringMatch;
use gpui::{
@@ -4,7 +4,7 @@ use gpui::{
FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
View, ViewContext, WindowContext,
};
-use std::{cmp, sync::Arc};
+use std::sync::Arc;
use ui::{prelude::*, v_flex, Color, Divider, Label, ListItem, ListItemSpacing, ListSeparator};
use workspace::ModalView;
@@ -103,7 +103,7 @@ impl<D: PickerDelegate> Picker<D> {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
- let ix = cmp::min(index + 1, count - 1);
+ let ix = if index == count - 1 { 0 } else { index + 1 };
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
cx.notify();
@@ -114,7 +114,7 @@ impl<D: PickerDelegate> Picker<D> {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
- let ix = index.saturating_sub(1);
+ let ix = if index == 0 { count - 1 } else { index - 1 };
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
cx.notify();
@@ -2253,7 +2253,7 @@ impl LspCommand for InlayHints {
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
// `typescript-language-server` adds padding to the left for type hints, turning
// `const foo: boolean` into `const foo : boolean` which looks odd.
- // `rust-analyzer` does not have the padding for this case, and we have to accomodate both.
+ // `rust-analyzer` does not have the padding for this case, and we have to accommodate both.
//
// We could trim the whole string, but being pessimistic on par with the situation above,
// there might be a hint with multiple whitespaces at the end(s) which we need to display properly.
@@ -5578,7 +5578,7 @@ impl Project {
// 3. We run a scan over all the candidate buffers on multiple background threads.
// We cannot assume that there will even be a match - while at least one match
// is guaranteed for files obtained from FS, the buffers we got from memory (unsaved files/unnamed buffers) might not have a match at all.
- // There is also an auxilliary background thread responsible for result gathering.
+ // There is also an auxiliary background thread responsible for result gathering.
// This is where the sorted list of buffers comes into play to maintain sorted order; Whenever this background thread receives a notification (buffer has/doesn't have matches),
// it keeps it around. It reports matches in sorted order, though it accepts them in unsorted order as well.
// As soon as the match info on next position in sorted order becomes available, it reports it (if it's a match) or skips to the next
@@ -8550,7 +8550,7 @@ fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str {
break;
} else {
if i > 0 {
- // Acount for separator prior to this part
+ // Account for separator prior to this part
literal_end += path::MAIN_SEPARATOR.len_utf8();
}
literal_end += part.len();
@@ -9,7 +9,7 @@ use std::sync::Arc;
pub struct ProjectSettings {
/// Configuration for language servers.
///
- /// The following settings can be overriden for specific language servers:
+ /// The following settings can be overridden for specific language servers:
/// - initialization_options
/// To override settings for a language, add an entry for that language server's
/// name to the lsp value.
@@ -42,9 +42,9 @@ impl FileAssociations {
}
pub fn get_icon(path: &Path, cx: &AppContext) -> Option<Arc<str>> {
- let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+ let this = cx.try_global::<Self>()?;
- // FIXME: Associate a type with the languages and have the file's langauge
+ // FIXME: Associate a type with the languages and have the file's language
// override these associations
maybe!({
let suffix = path.icon_suffix()?;
@@ -58,7 +58,7 @@ impl FileAssociations {
}
pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
- let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+ let this = cx.try_global::<Self>()?;
let key = if expanded {
EXPANDED_DIRECTORY_TYPE
@@ -72,7 +72,7 @@ impl FileAssociations {
}
pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
- let this = cx.has_global::<Self>().then(|| cx.global::<Self>())?;
+ let this = cx.try_global::<Self>()?;
let key = if expanded {
EXPANDED_CHEVRON_TYPE
@@ -3,7 +3,7 @@ mod project_panel_settings;
use settings::Settings;
use db::kvp::KEY_VALUE_STORE;
-use editor::{scroll::autoscroll::Autoscroll, Cancel, Editor};
+use editor::{actions::Cancel, scroll::Autoscroll, Editor};
use file_associations::FileAssociations;
use anyhow::{anyhow, Result};
@@ -1,4 +1,4 @@
-use editor::{scroll::autoscroll::Autoscroll, styled_runs_for_code_label, Bias, Editor};
+use editor::{scroll::Autoscroll, styled_runs_for_code_label, Bias, Editor};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, rems, AppContext, DismissEvent, FontWeight, Model, ParentElement, StyledText, Task,
@@ -45,13 +45,13 @@ impl Render for QuickActionBar {
"toggle inlay hints",
IconName::InlayHint,
editor.read(cx).inlay_hints_enabled(),
- Box::new(editor::ToggleInlayHints),
+ Box::new(editor::actions::ToggleInlayHints),
"Toggle Inlay Hints",
{
let editor = editor.clone();
move |_, cx| {
editor.update(cx, |editor, cx| {
- editor.toggle_inlay_hints(&editor::ToggleInlayHints, cx);
+ editor.toggle_inlay_hints(&editor::actions::ToggleInlayHints, cx);
});
}
},
@@ -32,7 +32,7 @@ impl RecentProjects {
fn new(delegate: RecentProjectsDelegate, rem_width: f32, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.new_view(|cx| Picker::new(delegate, cx));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
- // We do not want to block the UI on a potentially lenghty call to DB, so we're gonna swap
+ // We do not want to block the UI on a potentially lengthy call to DB, so we're gonna swap
// out workspace locations once the future runs to completion.
cx.spawn(|this, mut cx| async move {
let workspaces = WORKSPACE_DB
@@ -141,7 +141,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
})
.collect();
- let refinement_refine_assigments: Vec<TokenStream2> = fields
+ let refinement_refine_assignments: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
@@ -161,7 +161,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
})
.collect();
- let refinement_refined_assigments: Vec<TokenStream2> = fields
+ let refinement_refined_assignments: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
@@ -181,7 +181,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
})
.collect();
- let from_refinement_assigments: Vec<TokenStream2> = fields
+ let from_refinement_assignments: Vec<TokenStream2> = fields
.iter()
.map(|field| {
let name = &field.ident;
@@ -272,11 +272,11 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
type Refinement = #refinement_ident #ty_generics;
fn refine(&mut self, refinement: &Self::Refinement) {
- #( #refinement_refine_assigments )*
+ #( #refinement_refine_assignments )*
}
fn refined(mut self, refinement: Self::Refinement) -> Self {
- #( #refinement_refined_assigments )*
+ #( #refinement_refined_assignments )*
self
}
}
@@ -286,7 +286,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
{
fn from(value: #refinement_ident #ty_generics) -> Self {
Self {
- #( #from_refinement_assigments )*
+ #( #from_refinement_assignments )*
}
}
}
@@ -7,7 +7,7 @@ use crate::{
ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
};
use collections::HashMap;
-use editor::{Editor, EditorElement, EditorStyle, Tab};
+use editor::{actions::Tab, Editor, EditorElement, EditorStyle};
use futures::channel::oneshot;
use gpui::{
actions, div, impl_actions, Action, AppContext, ClickEvent, EventEmitter, FocusableView,
@@ -429,6 +429,11 @@ pub trait SearchActionsRegistrar {
&mut self,
callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
);
+
+ fn register_handler_for_dismissed_search<A: Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ );
}
type GetSearchBar<T> =
@@ -457,16 +462,62 @@ impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
}
impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
- fn register_handler<A: gpui::Action>(
+ fn register_handler<A: Action>(
&mut self,
callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
) {
let getter = self.search_getter;
self.div = self.div.take().map(|div| {
div.on_action(self.cx.listener(move |this, action, cx| {
- (getter)(this, cx)
+ let should_notify = (getter)(this, cx)
.clone()
- .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx)));
+ .map(|search_bar| {
+ search_bar.update(cx, |search_bar, cx| {
+ if search_bar.is_dismissed()
+ || search_bar.active_searchable_item.is_none()
+ {
+ false
+ } else {
+ callback(search_bar, action, cx);
+ true
+ }
+ })
+ })
+ .unwrap_or(false);
+ if should_notify {
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
+ }))
+ });
+ }
+
+ fn register_handler_for_dismissed_search<A: Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ ) {
+ let getter = self.search_getter;
+ self.div = self.div.take().map(|div| {
+ div.on_action(self.cx.listener(move |this, action, cx| {
+ let should_notify = (getter)(this, cx)
+ .clone()
+ .map(|search_bar| {
+ search_bar.update(cx, |search_bar, cx| {
+ if search_bar.is_dismissed() {
+ callback(search_bar, action, cx);
+ true
+ } else {
+ false
+ }
+ })
+ })
+ .unwrap_or(false);
+ if should_notify {
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
}))
});
}
@@ -488,44 +539,86 @@ impl SearchActionsRegistrar for Workspace {
pane.update(cx, move |this, cx| {
this.toolbar().update(cx, move |this, cx| {
if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
- search_bar.update(cx, move |this, cx| callback(this, action, cx));
- cx.notify();
+ let should_notify = search_bar.update(cx, move |search_bar, cx| {
+ if search_bar.is_dismissed()
+ || search_bar.active_searchable_item.is_none()
+ {
+ false
+ } else {
+ callback(search_bar, action, cx);
+ true
+ }
+ });
+ if should_notify {
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
+ }
+ })
+ });
+ });
+ }
+
+ fn register_handler_for_dismissed_search<A: Action>(
+ &mut self,
+ callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+ ) {
+ self.register_action(move |workspace, action: &A, cx| {
+ if workspace.has_active_modal(cx) {
+ cx.propagate();
+ return;
+ }
+
+ let pane = workspace.active_pane();
+ pane.update(cx, move |this, cx| {
+ this.toolbar().update(cx, move |this, cx| {
+ if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
+ let should_notify = search_bar.update(cx, move |search_bar, cx| {
+ if search_bar.is_dismissed() {
+ callback(search_bar, action, cx);
+ true
+ } else {
+ false
+ }
+ });
+ if should_notify {
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
}
})
});
});
}
}
+
impl BufferSearchBar {
- pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) {
+ pub fn register(registrar: &mut impl SearchActionsRegistrar) {
registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| {
if this.supported_options().case {
this.toggle_case_sensitive(action, cx);
}
});
-
registrar.register_handler(|this, action: &ToggleWholeWord, cx| {
if this.supported_options().word {
this.toggle_whole_word(action, cx);
}
});
-
registrar.register_handler(|this, action: &ToggleReplace, cx| {
if this.supported_options().replacement {
this.toggle_replace(action, cx);
}
});
-
registrar.register_handler(|this, _: &ActivateRegexMode, cx| {
if this.supported_options().regex {
this.activate_search_mode(SearchMode::Regex, cx);
}
});
-
registrar.register_handler(|this, _: &ActivateTextMode, cx| {
this.activate_search_mode(SearchMode::Text, cx);
});
-
registrar.register_handler(|this, action: &CycleMode, cx| {
if this.supported_options().regex {
// If regex is not supported then search has just one mode (text) - in that case there's no point in supporting
@@ -533,7 +626,6 @@ impl BufferSearchBar {
this.cycle_mode(action, cx)
}
});
-
registrar.register_handler(|this, action: &SelectNextMatch, cx| {
this.select_next_match(action, cx);
});
@@ -543,20 +635,20 @@ impl BufferSearchBar {
registrar.register_handler(|this, action: &SelectAllMatches, cx| {
this.select_all_matches(action, cx);
});
- registrar.register_handler(|this, _: &editor::Cancel, cx| {
- if this.dismissed {
- cx.propagate();
- } else {
- this.dismiss(&Dismiss, cx);
- }
+ registrar.register_handler(|this, _: &editor::actions::Cancel, cx| {
+ this.dismiss(&Dismiss, cx);
});
+
+ // register deploy buffer search for both search bar states, since we want to focus into the search bar
+ // when the deploy action is triggered in the buffer.
registrar.register_handler(|this, deploy, cx| {
this.deploy(deploy, cx);
+ });
+ registrar.register_handler_for_dismissed_search(|this, deploy, cx| {
+ this.deploy(deploy, cx);
})
}
- fn register(workspace: &mut Workspace) {
- Self::register_inner(workspace);
- }
+
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let query_editor = cx.new_view(|cx| Editor::single_line(cx));
cx.subscribe(&query_editor, Self::on_query_editor_event)
@@ -1081,7 +1173,7 @@ mod tests {
use super::*;
use editor::{DisplayPoint, Editor};
- use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext};
+ use gpui::{Context, Hsla, TestAppContext, VisualTestContext};
use language::Buffer;
use smol::stream::StreamExt as _;
use unindent::Unindent as _;
@@ -1114,7 +1206,7 @@ mod tests {
.unindent(),
)
});
- let (_, cx) = cx.add_window_view(|_| EmptyView {});
+ let cx = cx.add_empty_window();
let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = cx.new_view(|cx| {
@@ -1461,7 +1553,7 @@ mod tests {
"Should pick a query with multiple results"
);
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
- let window = cx.add_window(|_| EmptyView {});
+ let window = cx.add_window(|_| ());
let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
@@ -1657,7 +1749,7 @@ mod tests {
"#
.unindent();
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text));
- let (_, cx) = cx.add_window_view(|_| EmptyView {});
+ let cx = cx.add_empty_window();
let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx));
@@ -85,7 +85,7 @@ mod tests {
assert_eq!(
search_history.current(),
None,
- "No current selection should be set fo the default search history"
+ "No current selection should be set for the default search history"
);
search_history.add("rust".to_string());
@@ -7,15 +7,15 @@ use crate::{
use anyhow::{Context as _, Result};
use collections::HashMap;
use editor::{
- items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent,
- MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN,
+ actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor, EditorEvent,
+ MultiBuffer, MAX_TAB_TITLE_LEN,
};
use editor::{EditorElement, EditorStyle};
use gpui::{
- actions, div, AnyElement, AnyView, AppContext, Context as _, Element, EntityId, EventEmitter,
- FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement, IntoElement,
- KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled,
- Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
+ actions, div, Action, AnyElement, AnyView, AppContext, Context as _, Element, EntityId,
+ EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Hsla, InteractiveElement,
+ IntoElement, KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString,
+ Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
WhiteSpace, WindowContext,
};
use menu::Confirm;
@@ -36,6 +36,7 @@ use std::{
time::{Duration, Instant},
};
use theme::ThemeSettings;
+use workspace::{DeploySearch, NewSearch};
use ui::{
h_flex, prelude::*, v_flex, Icon, IconButton, IconName, Label, LabelCommon, LabelSize,
@@ -60,10 +61,62 @@ struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
pub fn init(cx: &mut AppContext) {
cx.set_global(ActiveSettings::default());
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
- workspace
- .register_action(ProjectSearchView::new_search)
- .register_action(ProjectSearchView::deploy_search)
- .register_action(ProjectSearchBar::search_in_new);
+ register_workspace_action(workspace, move |search_bar, _: &ToggleFilters, cx| {
+ search_bar.toggle_filters(cx);
+ });
+ register_workspace_action(workspace, move |search_bar, _: &ToggleCaseSensitive, cx| {
+ search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
+ });
+ register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, cx| {
+ search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
+ });
+ register_workspace_action(workspace, move |search_bar, action: &ToggleReplace, cx| {
+ search_bar.toggle_replace(action, cx)
+ });
+ register_workspace_action(workspace, move |search_bar, _: &ActivateRegexMode, cx| {
+ search_bar.activate_search_mode(SearchMode::Regex, cx)
+ });
+ register_workspace_action(workspace, move |search_bar, _: &ActivateTextMode, cx| {
+ search_bar.activate_search_mode(SearchMode::Text, cx)
+ });
+ register_workspace_action(
+ workspace,
+ move |search_bar, _: &ActivateSemanticMode, cx| {
+ search_bar.activate_search_mode(SearchMode::Semantic, cx)
+ },
+ );
+ register_workspace_action(workspace, move |search_bar, action: &CycleMode, cx| {
+ search_bar.cycle_mode(action, cx)
+ });
+ register_workspace_action(
+ workspace,
+ move |search_bar, action: &SelectNextMatch, cx| {
+ search_bar.select_next_match(action, cx)
+ },
+ );
+
+ // Only handle search_in_new if there is a search present
+ register_workspace_action_for_present_search(workspace, |workspace, action, cx| {
+ ProjectSearchView::search_in_new(workspace, action, cx)
+ });
+
+ // Both on present and dismissed search, we need to unconditionally handle those actions to focus from the editor.
+ workspace.register_action(move |workspace, action: &DeploySearch, cx| {
+ if workspace.has_active_modal(cx) {
+ cx.propagate();
+ return;
+ }
+ ProjectSearchView::deploy_search(workspace, action, cx);
+ cx.notify();
+ });
+ workspace.register_action(move |workspace, action: &NewSearch, cx| {
+ if workspace.has_active_modal(cx) {
+ cx.propagate();
+ return;
+ }
+ ProjectSearchView::new_search(workspace, action, cx);
+ cx.notify();
+ });
})
.detach();
}
@@ -960,6 +1013,37 @@ impl ProjectSearchView {
Self::existing_or_new_search(workspace, existing, cx)
}
+ fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
+ if let Some(search_view) = workspace
+ .active_item(cx)
+ .and_then(|item| item.downcast::<ProjectSearchView>())
+ {
+ let new_query = search_view.update(cx, |search_view, cx| {
+ let new_query = search_view.build_search_query(cx);
+ if new_query.is_some() {
+ if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
+ search_view.query_editor.update(cx, |editor, cx| {
+ editor.set_text(old_query.as_str(), cx);
+ });
+ search_view.search_options = SearchOptions::from_query(&old_query);
+ }
+ }
+ new_query
+ });
+ if let Some(new_query) = new_query {
+ let model = cx.new_model(|cx| {
+ let mut model = ProjectSearch::new(workspace.project().clone(), cx);
+ model.search(new_query, cx);
+ model
+ });
+ workspace.add_item(
+ Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
+ cx,
+ );
+ }
+ }
+ }
+
// Add another search tab to the workspace.
fn new_search(
workspace: &mut Workspace,
@@ -1262,17 +1346,11 @@ impl ProjectSearchView {
}
}
-impl Default for ProjectSearchBar {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl ProjectSearchBar {
pub fn new() -> Self {
Self {
- active_project_search: Default::default(),
- subscription: Default::default(),
+ active_project_search: None,
+ subscription: None,
}
}
@@ -1303,42 +1381,11 @@ impl ProjectSearchBar {
}
}
- fn search_in_new(workspace: &mut Workspace, _: &SearchInNew, cx: &mut ViewContext<Workspace>) {
- if let Some(search_view) = workspace
- .active_item(cx)
- .and_then(|item| item.downcast::<ProjectSearchView>())
- {
- let new_query = search_view.update(cx, |search_view, cx| {
- let new_query = search_view.build_search_query(cx);
- if new_query.is_some() {
- if let Some(old_query) = search_view.model.read(cx).active_query.clone() {
- search_view.query_editor.update(cx, |editor, cx| {
- editor.set_text(old_query.as_str(), cx);
- });
- search_view.search_options = SearchOptions::from_query(&old_query);
- }
- }
- new_query
- });
- if let Some(new_query) = new_query {
- let model = cx.new_model(|cx| {
- let mut model = ProjectSearch::new(workspace.project().clone(), cx);
- model.search(new_query, cx);
- model
- });
- workspace.add_item(
- Box::new(cx.new_view(|cx| ProjectSearchView::new(model, cx, None))),
- cx,
- );
- }
- }
- }
-
- fn tab(&mut self, _: &editor::Tab, cx: &mut ViewContext<Self>) {
+ fn tab(&mut self, _: &editor::actions::Tab, cx: &mut ViewContext<Self>) {
self.cycle_field(Direction::Next, cx);
}
- fn tab_previous(&mut self, _: &editor::TabPrev, cx: &mut ViewContext<Self>) {
+ fn tab_previous(&mut self, _: &editor::actions::TabPrev, cx: &mut ViewContext<Self>) {
self.cycle_field(Direction::Prev, cx);
}
@@ -1502,6 +1549,22 @@ impl ProjectSearchBar {
}
}
+ pub fn select_next_match(&mut self, _: &SelectNextMatch, cx: &mut ViewContext<Self>) {
+ if let Some(search) = self.active_project_search.as_ref() {
+ search.update(cx, |this, cx| {
+ this.select_match(Direction::Next, cx);
+ })
+ }
+ }
+
+ fn select_prev_match(&mut self, _: &SelectPrevMatch, cx: &mut ViewContext<Self>) {
+ if let Some(search) = self.active_project_search.as_ref() {
+ search.update(cx, |this, cx| {
+ this.select_match(Direction::Prev, cx);
+ })
+ }
+ }
+
fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> {
let previous_query_keystrokes = cx
.bindings_for_action(&PreviousHistoryQuery {})
@@ -1870,6 +1933,8 @@ impl Render for ProjectSearchBar {
}))
})
})
+ .on_action(cx.listener(Self::select_next_match))
+ .on_action(cx.listener(Self::select_prev_match))
.child(
h_flex()
.justify_between()
@@ -1963,6 +2028,60 @@ impl ToolbarItemView for ProjectSearchBar {
}
}
+fn register_workspace_action<A: Action>(
+ workspace: &mut Workspace,
+ callback: fn(&mut ProjectSearchBar, &A, &mut ViewContext<ProjectSearchBar>),
+) {
+ workspace.register_action(move |workspace, action: &A, cx| {
+ if workspace.has_active_modal(cx) {
+ cx.propagate();
+ return;
+ }
+
+ workspace.active_pane().update(cx, |pane, cx| {
+ pane.toolbar().update(cx, move |workspace, cx| {
+ if let Some(search_bar) = workspace.item_of_type::<ProjectSearchBar>() {
+ search_bar.update(cx, move |search_bar, cx| {
+ if search_bar.active_project_search.is_some() {
+ callback(search_bar, action, cx);
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
+ });
+ }
+ });
+ })
+ });
+}
+
+fn register_workspace_action_for_present_search<A: Action>(
+ workspace: &mut Workspace,
+ callback: fn(&mut Workspace, &A, &mut ViewContext<Workspace>),
+) {
+ workspace.register_action(move |workspace, action: &A, cx| {
+ if workspace.has_active_modal(cx) {
+ cx.propagate();
+ return;
+ }
+
+ let should_notify = workspace
+ .active_pane()
+ .read(cx)
+ .toolbar()
+ .read(cx)
+ .item_of_type::<ProjectSearchBar>()
+ .map(|search_bar| search_bar.read(cx).active_project_search.is_some())
+ .unwrap_or(false);
+ if should_notify {
+ callback(workspace, action, cx);
+ cx.notify();
+ } else {
+ cx.propagate();
+ }
+ });
+}
+
#[cfg(test)]
pub mod tests {
use super::*;
@@ -2579,7 +2698,7 @@ pub mod tests {
);
assert!(
search_view_2.query_editor.focus_handle(cx).is_focused(cx),
- "Focus should be moved into query editor fo the new window"
+ "Focus should be moved into query editor of the new window"
);
});
}).unwrap();
@@ -10,7 +10,7 @@ nDCG@k:
- "The relevance of result is represented by a score (also known as a 'grade') that is assigned to the search query. The scores of these results are then discounted based on their position in the search results -- did they get recommended first or last?"
MRR@k:
-- "Mean reciprocal rank quantifies the rank of the first relevant item found in teh recommendation list."
+- "Mean reciprocal rank quantifies the rank of the first relevant item found in the recommendation list."
MAP@k:
- "Mean average precision averages the precision@k metric at each relevant item position in the recommendation list.
@@ -76,7 +76,7 @@ pub struct CodeContextRetriever {
// Every match has an item, this represents the fundamental treesitter symbol and anchors the search
// Every match has one or more 'name' captures. These indicate the display range of the item for deduplication.
-// If there are preceeding comments, we track this with a context capture
+// If there are preceding comments, we track this with a context capture
// If there is a piece that should be collapsed in hierarchical queries, we capture it with a collapse capture
// If there is a piece that should be kept inside a collapsed node, we capture it with a keep capture
#[derive(Debug, Clone)]
@@ -275,11 +275,8 @@ pub struct SearchResult {
impl SemanticIndex {
pub fn global(cx: &mut AppContext) -> Option<Model<SemanticIndex>> {
- if cx.has_global::<Model<Self>>() {
- Some(cx.global::<Model<SemanticIndex>>().clone())
- } else {
- None
- }
+ cx.try_global::<Model<Self>>()
+ .map(|semantic_index| semantic_index.clone())
}
pub fn authenticate(&mut self, cx: &mut AppContext) -> bool {
@@ -110,7 +110,7 @@ async fn test_semantic_index(cx: &mut TestAppContext) {
cx,
);
- // Test Include Files Functonality
+ // Test Include Files Functionality
let include_files = vec![PathMatcher::new("*.rs").unwrap()];
let exclude_files = vec![PathMatcher::new("*.rs").unwrap()];
let rust_only_search_results = semantic_index
@@ -576,7 +576,7 @@ async fn test_code_context_retrieval_lua() {
setmetatable(classdef, { __index = baseclass })
-- All class instances have a reference to the class object.
classdef.class = classdef
- --- Recursivly allocates the inheritance tree of the instance.
+ --- Recursively allocates the inheritance tree of the instance.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc(mastertable)
@@ -607,7 +607,7 @@ async fn test_code_context_retrieval_lua() {
setmetatable(classdef, { __index = baseclass })
-- All class instances have a reference to the class object.
classdef.class = classdef
- --- Recursivly allocates the inheritance tree of the instance.
+ --- Recursively allocates the inheritance tree of the instance.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc(mastertable)
@@ -617,7 +617,7 @@ async fn test_code_context_retrieval_lua() {
end"#.unindent(),
114),
(r#"
- --- Recursivly allocates the inheritance tree of the instance.
+ --- Recursively allocates the inheritance tree of the instance.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc(mastertable)
@@ -626,7 +626,7 @@ async fn test_code_context_retrieval_lua() {
-- Any functions this instance does not know of will 'look up' to the superclass definition.
setmetatable(instance, { __index = classdef, __newindex = mastertable })
return instance
- end"#.unindent(), 809),
+ end"#.unindent(), 810),
]
);
}
@@ -191,7 +191,7 @@ mod test {
fn migrations_dont_rerun() {
let connection = Connection::open_memory(Some("migrations_dont_rerun"));
- // Create migration which clears a tabl
+ // Create migration which clears a table
// Manually create the table for that migration with a row
connection
@@ -10,7 +10,11 @@ pub struct AutoHeightEditorStory {
impl AutoHeightEditorStory {
pub fn new(cx: &mut WindowContext) -> View<Self> {
- cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+ cx.bind_keys([KeyBinding::new(
+ "enter",
+ editor::actions::Newline,
+ Some("Editor"),
+ )]);
cx.new_view(|cx| Self {
editor: cx.new_view(|cx| {
let mut editor = Editor::auto_height(3, cx);
@@ -65,7 +65,7 @@ impl Render for TextStory {
))
)
.usage(indoc! {r##"
- // NOTE: When rendering text in a horizonal flex container,
+ // 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
@@ -149,7 +149,7 @@ impl Render for TextStory {
// "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 horizonal flex container,
+// // 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())
@@ -60,8 +60,7 @@ fn main() {
});
let theme_name = args.theme.unwrap_or("One Dark".to_string());
- let asset_source = Arc::new(Assets);
- gpui::App::production(asset_source).run(move |cx| {
+ gpui::App::new().with_assets(Assets).run(move |cx| {
load_embedded_fonts(cx).unwrap();
let mut store = SettingsStore::default();
@@ -387,7 +387,7 @@ impl Render for TerminalPanel {
},
cx,
);
- BufferSearchBar::register_inner(&mut registrar);
+ BufferSearchBar::register(&mut registrar);
registrar.into_div().size_full().child(self.pane.clone())
}
}
@@ -2,7 +2,7 @@ mod persistence;
pub mod terminal_element;
pub mod terminal_panel;
-use editor::{scroll::autoscroll::Autoscroll, Editor};
+use editor::{scroll::Autoscroll, Editor};
use gpui::{
div, impl_actions, overlay, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle,
FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton, MouseDownEvent, Pixels,
@@ -357,7 +357,7 @@ impl TerminalView {
}
}
- fn select_all(&mut self, _: &editor::SelectAll, cx: &mut ViewContext<Self>) {
+ fn select_all(&mut self, _: &editor::actions::SelectAll, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| term.select_all());
cx.notify();
}
@@ -674,9 +674,9 @@ impl Render for TerminalView {
self.can_navigate_to_selected_word,
)),
)
- .children(self.context_menu.as_ref().map(|(menu, positon, _)| {
+ .children(self.context_menu.as_ref().map(|(menu, position, _)| {
overlay()
- .position(*positon)
+ .position(*position)
.anchor(gpui::AnchorCorner::TopLeft)
.child(menu.clone())
}))
@@ -127,7 +127,7 @@ impl SyntaxTheme {
}
}
- // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
+ // TODO: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
@@ -188,7 +188,7 @@ fn main() -> Result<()> {
let zed1_themes_path = PathBuf::from_str("assets/themes")?;
- let zed1_theme_familes = [
+ let zed1_theme_families = [
"Andromeda",
"Atelier",
"Ayu",
@@ -207,7 +207,7 @@ fn main() -> Result<()> {
);
let mut zed1_themes_by_family: IndexMap<String, Vec<UserTheme>> = IndexMap::from_iter(
- zed1_theme_familes
+ zed1_theme_families
.into_iter()
.map(|family| (family.to_string(), Vec::new())),
);
@@ -111,7 +111,7 @@ pub enum ButtonStyle {
#[default]
Subtle,
- /// Used for buttons that only change forground color on hover and active states.
+ /// Used for buttons that only change foreground color on hover and active states.
///
/// TODO: Better docs for this.
Transparent,
@@ -12,7 +12,7 @@ use smallvec::SmallVec;
/// user's mouse.)
///
/// Example: A "new" menu with options like "new file", "new folder", etc,
-/// Linear's "Display" menu, a profile menu that appers when you click your avatar.
+/// Linear's "Display" menu, a profile menu that appears when you click your avatar.
///
/// Related elements:
///
@@ -113,7 +113,7 @@ impl Render for IconButtonStory {
StoryContainer::new(
"Icon Button",
- "crates/ui2/src/components/stories/icon_button.rs",
+ "crates/ui/src/components/stories/icon_button.rs",
)
.child(StorySection::new().children(buttons))
.child(
@@ -9,7 +9,7 @@ impl Render for ToggleButtonStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
StoryContainer::new(
"Toggle Button",
- "crates/ui2/src/components/stories/toggle_button.rs",
+ "crates/ui/src/components/stories/toggle_button.rs",
)
.child(
StorySection::new().child(
@@ -85,7 +85,7 @@ impl LayerIndex {
}
}
-/// An appropriate z-index for the given layer based on its intended useage.
+/// An appropriate z-index for the given layer based on its intended usage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementIndex {
Effect,
@@ -342,7 +342,7 @@ impl PickerDelegate for BranchListDelegate {
}
let status = repo.change_branch(¤t_pick);
if status.is_err() {
- this.delegate.display_error_toast(format!("Failed to chec branch '{current_pick}', check for conflicts or unstashed files"), cx);
+ this.delegate.display_error_toast(format!("Failed to check branch '{current_pick}', check for conflicts or unstashed files"), cx);
status?;
}
this.cancel(&Default::default(), cx);
@@ -1,5 +1,5 @@
use command_palette::CommandInterceptResult;
-use editor::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
+use editor::actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive};
use gpui::{impl_actions, Action, AppContext, ViewContext};
use serde_derive::Deserialize;
use workspace::{SaveIntent, Workspace};
@@ -204,25 +204,31 @@ pub fn command_interceptor(mut query: &str, _: &AppContext) -> Option<CommandInt
// quickfix / loclist (merged together for now)
"cl" | "cli" | "clis" | "clist" => ("clist", diagnostics::Deploy.boxed_clone()),
- "cc" => ("cc", editor::Hover.boxed_clone()),
- "ll" => ("ll", editor::Hover.boxed_clone()),
- "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
- "lne" | "lnex" | "lnext" => ("cnext", editor::GoToDiagnostic.boxed_clone()),
-
- "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => {
- ("cprevious", editor::GoToPrevDiagnostic.boxed_clone())
+ "cc" => ("cc", editor::actions::Hover.boxed_clone()),
+ "ll" => ("ll", editor::actions::Hover.boxed_clone()),
+ "cn" | "cne" | "cnex" | "cnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()),
+ "lne" | "lnex" | "lnext" => ("cnext", editor::actions::GoToDiagnostic.boxed_clone()),
+
+ "cpr" | "cpre" | "cprev" | "cprevi" | "cprevio" | "cpreviou" | "cprevious" => (
+ "cprevious",
+ editor::actions::GoToPrevDiagnostic.boxed_clone(),
+ ),
+ "cN" | "cNe" | "cNex" | "cNext" => {
+ ("cNext", editor::actions::GoToPrevDiagnostic.boxed_clone())
}
- "cN" | "cNe" | "cNex" | "cNext" => ("cNext", editor::GoToPrevDiagnostic.boxed_clone()),
- "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => {
- ("lprevious", editor::GoToPrevDiagnostic.boxed_clone())
+ "lp" | "lpr" | "lpre" | "lprev" | "lprevi" | "lprevio" | "lpreviou" | "lprevious" => (
+ "lprevious",
+ editor::actions::GoToPrevDiagnostic.boxed_clone(),
+ ),
+ "lN" | "lNe" | "lNex" | "lNext" => {
+ ("lNext", editor::actions::GoToPrevDiagnostic.boxed_clone())
}
- "lN" | "lNe" | "lNex" | "lNext" => ("lNext", editor::GoToPrevDiagnostic.boxed_clone()),
// modify the buffer (should accept [range])
"j" | "jo" | "joi" | "join" => ("join", JoinLines.boxed_clone()),
"d" | "de" | "del" | "dele" | "delet" | "delete" | "dl" | "dell" | "delel" | "deletl"
| "deletel" | "dp" | "dep" | "delp" | "delep" | "deletp" | "deletep" => {
- ("delete", editor::DeleteLine.boxed_clone())
+ ("delete", editor::actions::DeleteLine.boxed_clone())
}
"sor" | "sor " | "sort" | "sort " => ("sort", SortLinesCaseSensitive.boxed_clone()),
"sor i" | "sort i" => ("sort i", SortLinesCaseInsensitive.boxed_clone()),
@@ -1,5 +1,5 @@
use crate::{normal::repeat, state::Mode, Vim};
-use editor::{scroll::autoscroll::Autoscroll, Bias};
+use editor::{scroll::Autoscroll, Bias};
use gpui::{actions, Action, ViewContext};
use language::SelectionGoal;
use workspace::Workspace;
@@ -28,11 +28,10 @@ impl ModeIndicator {
fn update_mode(&mut self, cx: &mut ViewContext<Self>) {
// Vim doesn't exist in some tests
- if !cx.has_global::<Vim>() {
+ let Some(vim) = cx.try_global::<Vim>() else {
return;
- }
+ };
- let vim = Vim::read(cx);
if vim.enabled {
self.mode = Some(vim.state().mode);
} else {
@@ -1,11 +1,10 @@
use editor::{
- char_kind,
display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint},
movement::{self, find_boundary, find_preceding_boundary, FindRange, TextLayoutDetails},
- Bias, CharKind, DisplayPoint, ToOffset,
+ Bias, DisplayPoint, ToOffset,
};
use gpui::{actions, impl_actions, px, ViewContext, WindowContext};
-use language::{Point, Selection, SelectionGoal};
+use language::{char_kind, CharKind, Point, Selection, SelectionGoal};
use serde::Deserialize;
use workspace::Workspace;
@@ -18,7 +18,7 @@ use crate::{
Vim,
};
use collections::HashSet;
-use editor::scroll::autoscroll::Autoscroll;
+use editor::scroll::Autoscroll;
use editor::{Bias, DisplayPoint};
use gpui::{actions, ViewContext, WindowContext};
use language::SelectionGoal;
@@ -1,4 +1,4 @@
-use editor::scroll::autoscroll::Autoscroll;
+use editor::scroll::Autoscroll;
use gpui::ViewContext;
use language::{Bias, Point};
use workspace::Workspace;
@@ -1,13 +1,12 @@
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
use editor::{
- char_kind,
display_map::DisplaySnapshot,
movement::{self, FindRange, TextLayoutDetails},
- scroll::autoscroll::Autoscroll,
- CharKind, DisplayPoint,
+ scroll::Autoscroll,
+ DisplayPoint,
};
use gpui::WindowContext;
-use language::Selection;
+use language::{char_kind, CharKind, Selection};
pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option<usize>, cx: &mut WindowContext) {
// Some motions ignore failure when switching to normal mode
@@ -1,6 +1,6 @@
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
use collections::{HashMap, HashSet};
-use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
+use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias};
use gpui::WindowContext;
use language::Point;
@@ -1,6 +1,6 @@
use std::ops::Range;
-use editor::{scroll::autoscroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
+use editor::{scroll::Autoscroll, MultiBufferSnapshot, ToOffset, ToPoint};
use gpui::{impl_actions, ViewContext, WindowContext};
use language::{Bias, Point};
use serde::Deserialize;
@@ -1,8 +1,7 @@
use std::{borrow::Cow, cmp};
use editor::{
- display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, ClipboardSelection,
- DisplayPoint,
+ display_map::ToDisplayPoint, movement, scroll::Autoscroll, ClipboardSelection, DisplayPoint,
};
use gpui::{impl_actions, ViewContext};
use language::{Bias, SelectionGoal};
@@ -12,7 +12,7 @@ actions!(vim, [Repeat, EndRepeat]);
fn should_replay(action: &Box<dyn Action>) -> bool {
// skip so that we don't leave the character palette open
- if editor::ShowCharacterPalette.partial_eq(&**action) {
+ if editor::actions::ShowCharacterPalette.partial_eq(&**action) {
return false;
}
true
@@ -152,7 +152,7 @@ pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {
let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);
- // if we came from insert mode we're just doing repititions 2 onwards.
+ // if we came from insert mode we're just doing repetitions 2 onwards.
if from_insert_mode {
count -= 1;
new_actions[0] = actions[0].clone();
@@ -1,7 +1,7 @@
use crate::Vim;
use editor::{
display_map::ToDisplayPoint,
- scroll::{scroll_amount::ScrollAmount, VERTICAL_SCROLL_MARGIN},
+ scroll::{ScrollAmount, VERTICAL_SCROLL_MARGIN},
DisplayPoint, Editor,
};
use gpui::{actions, ViewContext};
@@ -278,7 +278,7 @@ fn parse_replace_all(query: &str) -> Replacement {
return Replacement::default();
}
- let Some(delimeter) = chars.next() else {
+ let Some(delimiter) = chars.next() else {
return Replacement::default();
};
@@ -301,13 +301,13 @@ fn parse_replace_all(query: &str) -> Replacement {
buffer.push('$')
// unescape escaped parens
} else if phase == 0 && c == '(' || c == ')' {
- } else if c != delimeter {
+ } else if c != delimiter {
buffer.push('\\')
}
buffer.push(c)
} else if c == '\\' {
escaped = true;
- } else if c == delimeter {
+ } else if c == delimiter {
if phase == 0 {
buffer = &mut replacement;
phase = 1;
@@ -1,13 +1,12 @@
use std::ops::Range;
use editor::{
- char_kind,
display_map::{DisplaySnapshot, ToDisplayPoint},
movement::{self, FindRange},
- Bias, CharKind, DisplayPoint,
+ Bias, DisplayPoint,
};
use gpui::{actions, impl_actions, ViewContext, WindowContext};
-use language::Selection;
+use language::{char_kind, CharKind, Selection};
use serde::Deserialize;
use workspace::Workspace;
@@ -359,7 +359,7 @@ impl NeovimConnection {
// to add one to the end in visual mode.
match mode {
Some(Mode::VisualBlock) if selection_row != cursor_row => {
- // in zed we fake a block selecrtion by using multiple cursors (one per line)
+ // in zed we fake a block selection by using multiple cursors (one per line)
// this code emulates that.
// to deal with casees where the selection is not perfectly rectangular we extract
// the content of the selection via the "a register to get the shape correctly.
@@ -332,7 +332,7 @@ impl Vim {
}
}
- /// Explicitly record one action (equiavlent to start_recording and stop_recording)
+ /// Explicitly record one action (equivalents to start_recording and stop_recording)
pub fn record_current_action(&mut self, cx: &mut WindowContext) {
self.start_recording(cx);
self.stop_recording();
@@ -5,7 +5,7 @@ use collections::HashMap;
use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
movement,
- scroll::autoscroll::Autoscroll,
+ scroll::Autoscroll,
Bias, DisplayPoint, Editor,
};
use gpui::{actions, ViewContext, WindowContext};
@@ -201,15 +201,13 @@ pub fn visual_block_motion(
let mut row = tail.row();
loop {
- let layed_out_line = map.layout_row(row, &text_layout_details);
+ let laid_out_line = map.layout_row(row, &text_layout_details);
let start = DisplayPoint::new(
row,
- layed_out_line.closest_index_for_x(positions.start) as u32,
- );
- let mut end = DisplayPoint::new(
- row,
- layed_out_line.closest_index_for_x(positions.end) as u32,
+ laid_out_line.closest_index_for_x(positions.start) as u32,
);
+ let mut end =
+ DisplayPoint::new(row, laid_out_line.closest_index_for_x(positions.end) as u32);
if end <= start {
if start.column() == map.line_len(start.row()) {
end = start;
@@ -218,7 +216,7 @@ pub fn visual_block_motion(
}
}
- if positions.start <= layed_out_line.width {
+ if positions.start <= laid_out_line.width {
let selection = Selection {
id: s.new_selection_id(),
start: start.to_point(map),
@@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::Settings;
-/// Base key bindings scheme. Base keymaps can be overriden with user keymaps.
+/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
///
/// Default: VSCode
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
@@ -586,13 +586,9 @@ impl<T: Item> ItemHandle for View<T> {
}
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
- if cx.has_global::<FollowableItemBuilders>() {
- let builders = cx.global::<FollowableItemBuilders>();
- let item = self.to_any();
- Some(builders.get(&item.entity_type())?.1(&item))
- } else {
- None
- }
+ let builders = cx.try_global::<FollowableItemBuilders>()?;
+ let item = self.to_any();
+ Some(builders.get(&item.entity_type())?.1(&item))
}
fn on_release(
@@ -195,7 +195,7 @@ struct NavHistoryState {
next_timestamp: Arc<AtomicUsize>,
}
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub enum NavigationMode {
Normal,
GoingBack,
@@ -1462,7 +1462,7 @@ impl Pane {
.icon_size(IconSize::Small)
.on_click({
let view = cx.view().clone();
- move |_, cx| view.update(cx, Self::navigate_backward)
+ move |_, cx| view.update(cx, Self::navigate_forward)
})
.disabled(!self.can_navigate_forward())
.tooltip(|cx| Tooltip::for_action("Go Forward", &GoForward, cx)),
@@ -1485,7 +1485,7 @@ impl Pane {
.child(
div()
.min_w_6()
- // HACK: This empty child is currently necessary to force the drop traget to appear
+ // HACK: This empty child is currently necessary to force the drop target to appear
// despite us setting a min width above.
.child("")
.h_full()
@@ -698,6 +698,7 @@ mod element {
workspace
.update(cx, |this, cx| this.schedule_serialize(cx))
.log_err();
+ cx.stop_propagation();
cx.refresh();
}
@@ -754,8 +755,10 @@ mod element {
workspace
.update(cx, |this, cx| this.schedule_serialize(cx))
.log_err();
+
cx.refresh();
}
+ cx.stop_propagation();
}
}
});
@@ -616,8 +616,8 @@ impl Workspace {
let modal_layer = cx.new_view(|_| ModalLayer::new());
let mut active_call = None;
- if cx.has_global::<Model<ActiveCall>>() {
- let call = cx.global::<Model<ActiveCall>>().clone();
+ if let Some(call) = cx.try_global::<Model<ActiveCall>>() {
+ let call = call.clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
active_call = Some((call, subscriptions));
@@ -2617,7 +2617,7 @@ impl Workspace {
// If the item belongs to a particular project, then it should
// only be included if this project is shared, and the follower
- // is in thie project.
+ // is in the project.
//
// Some items, like channel notes, do not belong to a particular
// project, so they should be included regardless of whether the
@@ -3686,11 +3686,8 @@ impl WorkspaceStore {
update: proto::update_followers::Variant,
cx: &AppContext,
) -> Option<()> {
- if !cx.has_global::<Model<ActiveCall>>() {
- return None;
- }
-
- let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
+ let active_call = cx.try_global::<Model<ActiveCall>>()?;
+ let room_id = active_call.read(cx).room()?.read(cx).id();
let follower_ids: Vec<_> = self
.followers
.iter()
@@ -53,39 +53,46 @@ pub fn app_menus() -> Vec<Menu<'static>> {
Menu {
name: "Edit",
items: vec![
- MenuItem::os_action("Undo", editor::Undo, OsAction::Undo),
- MenuItem::os_action("Redo", editor::Redo, OsAction::Redo),
+ MenuItem::os_action("Undo", editor::actions::Undo, OsAction::Undo),
+ MenuItem::os_action("Redo", editor::actions::Redo, OsAction::Redo),
MenuItem::separator(),
- MenuItem::os_action("Cut", editor::Cut, OsAction::Cut),
- MenuItem::os_action("Copy", editor::Copy, OsAction::Copy),
- MenuItem::os_action("Paste", editor::Paste, OsAction::Paste),
+ MenuItem::os_action("Cut", editor::actions::Cut, OsAction::Cut),
+ MenuItem::os_action("Copy", editor::actions::Copy, OsAction::Copy),
+ MenuItem::os_action("Paste", editor::actions::Paste, OsAction::Paste),
MenuItem::separator(),
MenuItem::action("Find", search::buffer_search::Deploy { focus: true }),
MenuItem::action("Find In Project", workspace::NewSearch),
MenuItem::separator(),
- MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()),
- MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette),
+ MenuItem::action(
+ "Toggle Line Comment",
+ editor::actions::ToggleComments::default(),
+ ),
+ MenuItem::action("Emoji & Symbols", editor::actions::ShowCharacterPalette),
],
},
Menu {
name: "Selection",
items: vec![
- MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll),
- MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode),
- MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode),
+ MenuItem::os_action(
+ "Select All",
+ editor::actions::SelectAll,
+ OsAction::SelectAll,
+ ),
+ MenuItem::action("Expand Selection", editor::actions::SelectLargerSyntaxNode),
+ MenuItem::action("Shrink Selection", editor::actions::SelectSmallerSyntaxNode),
MenuItem::separator(),
- MenuItem::action("Add Cursor Above", editor::AddSelectionAbove),
- MenuItem::action("Add Cursor Below", editor::AddSelectionBelow),
+ MenuItem::action("Add Cursor Above", editor::actions::AddSelectionAbove),
+ MenuItem::action("Add Cursor Below", editor::actions::AddSelectionBelow),
MenuItem::action(
"Select Next Occurrence",
- editor::SelectNext {
+ editor::actions::SelectNext {
replace_newest: false,
},
),
MenuItem::separator(),
- MenuItem::action("Move Line Up", editor::MoveLineUp),
- MenuItem::action("Move Line Down", editor::MoveLineDown),
- MenuItem::action("Duplicate Selection", editor::DuplicateLine),
+ MenuItem::action("Move Line Up", editor::actions::MoveLineUp),
+ MenuItem::action("Move Line Down", editor::actions::MoveLineDown),
+ MenuItem::action("Duplicate Selection", editor::actions::DuplicateLine),
],
},
Menu {
@@ -124,13 +131,13 @@ pub fn app_menus() -> Vec<Menu<'static>> {
MenuItem::action("Go to File", file_finder::Toggle),
// MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
MenuItem::action("Go to Symbol in Editor", outline::Toggle),
- MenuItem::action("Go to Definition", editor::GoToDefinition),
- MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition),
- MenuItem::action("Find All References", editor::FindAllReferences),
+ MenuItem::action("Go to Definition", editor::actions::GoToDefinition),
+ MenuItem::action("Go to Type Definition", editor::actions::GoToTypeDefinition),
+ MenuItem::action("Find All References", editor::actions::FindAllReferences),
MenuItem::action("Go to Line/Column", go_to_line::Toggle),
MenuItem::separator(),
- MenuItem::action("Next Problem", editor::GoToDiagnostic),
- MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic),
+ MenuItem::action("Next Problem", editor::actions::GoToDiagnostic),
+ MenuItem::action("Previous Problem", editor::actions::GoToPrevDiagnostic),
],
},
Menu {
@@ -67,7 +67,7 @@ fn main() {
}
log::info!("========== starting zed ==========");
- let app = App::production(Arc::new(Assets));
+ let app = App::new().with_assets(Assets);
let (installation_id, existing_installation_id_found) = app
.background_executor()
@@ -102,13 +102,15 @@ fn main() {
let open_listener = listener.clone();
app.on_open_urls(move |urls, _| open_listener.open_urls(&urls));
app.on_reopen(move |cx| {
- if cx.has_global::<Weak<AppState>>() {
- if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
- workspace::open_new(&app_state, cx, |workspace, cx| {
- Editor::new_file(workspace, &Default::default(), cx)
- })
- .detach();
- }
+ if let Some(app_state) = cx
+ .try_global::<Weak<AppState>>()
+ .map(|app_state| app_state.upgrade())
+ .flatten()
+ {
+ workspace::open_new(&app_state, cx, |workspace, cx| {
+ Editor::new_file(workspace, &Default::default(), cx)
+ })
+ .detach();
}
});
@@ -1,7 +1,7 @@
use anyhow::{anyhow, Context, Result};
use cli::{ipc, IpcHandshake};
use cli::{ipc::IpcSender, CliRequest, CliResponse};
-use editor::scroll::autoscroll::Autoscroll;
+use editor::scroll::Autoscroll;
use editor::Editor;
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures::channel::{mpsc, oneshot};
@@ -730,7 +730,7 @@ fn open_bundled_file(
mod tests {
use super::*;
use assets::Assets;
- use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor, EditorEvent};
+ use editor::{scroll::Autoscroll, DisplayPoint, Editor, EditorEvent};
use gpui::{
actions, Action, AnyWindowHandle, AppContext, AssetSource, Entity, TestAppContext,
VisualTestContext, WindowHandle,
@@ -1809,9 +1809,9 @@ mod tests {
assert!(workspace.active_item(cx).is_none());
})
.unwrap();
- editor_1.assert_dropped();
- editor_2.assert_dropped();
- buffer.assert_dropped();
+ editor_1.assert_released();
+ editor_2.assert_released();
+ buffer.assert_released();
}
#[gpui::test]
@@ -36,7 +36,7 @@ Expect this to take 30min to an hour! Some of these steps will take quite a whil
Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories
- (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos
- Keep the token in the browser tab/editor for the next two steps
-1. (Optional but reccomended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken`
+1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken`
1. Ensure the Zed.dev website is checked out in a sibling directory and install it's dependencies:
```
cd ..
@@ -87,10 +87,14 @@ This means that when releasing a new version of Zed that has changes to the RPC
1. This script will make local changes only, and print out a shell command that you can use to push the branch and tag.
1. Pushing the new tag will trigger a CI build that, when finished will upload a new versioned docker image to the DigitalOcean docker registry.
-1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`).
+1. If needing a migration:
+ - First check that the migration is valid. The database serves both preview and stable simultaneously, so new columns need to have defaults and old tables or columns can't be dropped.
+ - Then use `script/deploy-migration` <release channel> <version number> (production, staging, preview, nightly). ex: `script/deploy-migration preview 0.19.0`
+ - If there is an 'Error: container is waiting to start', you can review logs manually with: `kubectl --namespace <environment> logs <pod name>` to make sure the mgiration ran successfully.
+1. Once that CI job completes, you will be able to run the following command to deploy that docker image. The script takes two arguments: an environment (`production`, `preview`, or `staging`), and a version number (e.g. `0.10.1`):
- ```
- script/deploy preview 0.10.1
- ```
+```
+script/deploy preview 0.10.1
+```
-1. This command should complete quickly, updating the given environment to use the given version number of the `collab` server.
+1. This command should complete quickly, updating the given environment to use the given version number of the `collab` server.
@@ -4,7 +4,7 @@ This doc is a work in progress!
## Defining syntax highlighting rules
-We use tree-sitter queries to match certian properties to highlight.
+We use tree-sitter queries to match certain properties to highlight.
### Simple Example:
@@ -4,15 +4,18 @@
[Feedback](./feedback.md)
# Configuring Zed
+
- [Settings](./configuring_zed.md)
- [Vim Mode](./configuring_zed__configuring_vim.md)
# Using Zed
+
- [Workflows]()
- [Collaboration]()
- [Using AI]()
# Contributing to Zed
+
- [How to Contribute]()
- [Building from Source](./developing_zed__building_zed.md)
- [Local Collaboration](./developing_zed__local_collaboration.md)
@@ -4,7 +4,7 @@
Folder-specific settings are used to override Zed's global settings for files within a specific directory in the project panel. To get started, create a `.zed` subdirectory and add a `settings.json` within it. It should be noted that folder-specific settings don't need to live only a project's root, but can be defined at multiple levels in the project hierarchy. In setups like this, Zed will find the configuration nearest to the file you are working in and apply those settings to it. In most cases, this level of flexibility won't be needed and a single configuration for all files in a project is all that is required; the `Zed > Settings > Open Local Settings` menu action is built for this case. Running this action will look for a `.zed/settings.json` file at the root of the first top-level directory in your project panel. If it does not exist, it will create it.
-The following global settings can be overriden with a folder-specific configuration:
+The following global settings can be overridden with a folder-specific configuration:
- `copilot`
- `enable_language_server`
@@ -8,7 +8,7 @@ Zed uses the [Language Server Protocol](https://microsoft.github.io/language-ser
### Defining syntax highlighting rules
-We use tree-sitter queries to match certian properties to highlight.
+We use tree-sitter queries to match certain properties to highlight.
#### Simple Example:
@@ -1,133 +1,73 @@
# Building Zed
-π§ TODO:
+## Dependencies
-- [ ] Remove ZI-specific things
-- [ ] Rework any steps that currently require a ZI-specific account
+- Install [Rust](https://www.rust-lang.org/tools/install)
+- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store
-How to build Zed from source for the first time.
+- Install [Xcode command line tools](https://developer.apple.com/xcode/resources/)
-### Prerequisites
+ ```bash
+ xcode-select --install
+ ```
-π§ TODO π§ Update for open source
+- Ensure that the Xcode command line tools are using your newly installed copy of Xcode:
-- Be added to the GitHub organization
-- Be added to the Vercel team
-- Create a [Personal Access Token](https://github.com/settings/personal-access-tokens/new) on Github
- - π§ TODO π§ What permissions are required?
- - π§ TODO π§ What changes when repo isn't private?
- - Go to https://github.com/settings/tokens and Generate new token
- - GitHub currently provides two kinds of tokens:
- - Classic Tokens, where only `repo` (Full control of private repositories) OAuth scope has to be selected
- Unfortunately, unselecting `repo` scope and selecting every its inner scope instead does not allow the token users to read from private repositories
- - (not applicable) Fine-grained Tokens, at the moment of writing, did not allow any kind of access of non-owned private repos
- - Keep the token in the browser tab/editor for the next two steps
+ ```
+ sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.
+ ```
-### Dependencies
+* Install the Rust wasm toolchain:
-- Install [Rust](https://www.rust-lang.org/tools/install)
+ ```bash
+ rustup target add wasm32-wasi
+ ```
-- Install the [GitHub CLI](https://cli.github.com/), [Livekit](https://formulae.brew.sh/formula/livekit) & [Foreman](https://formulae.brew.sh/formula/foreman)
+## Backend Dependencies
-```bash
-brew install gh
-brew install livekit
-brew install foreman
-```
+If you are developing collaborative features of Zed, you'll need to install the dependencies of zed's `collab` server:
-- Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) from the macOS App Store
+- Install [Postgres](https://postgresapp.com)
+- Install [Livekit](https://formulae.brew.sh/formula/livekit) and [Foreman](https://formulae.brew.sh/formula/foreman)
-- Install [Xcode command line tools](https://developer.apple.com/xcode/resources/)
+ ```bash
+ brew install livekit foreman
+ ```
-```bash
-xcode-select --install
-```
-
-- If `xcode-select --print-path prints /Library/Developer/CommandLineToolsβ¦` run `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer.`
+## Building Zed from Source
-* Install [Postgres](https://postgresapp.com)
+Once you have the dependencies installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/).
-* Install the wasm toolchain
+For a debug build:
-```bash
-rustup target add wasm32-wasi
```
-
-### Building Zed from Source
-
-1. Clone the `zed` repo
-
-```bash
-gh repo clone zed-industries/zed
+cargo run
```
-1. (Optional but recommended) Add your GITHUB_TOKEN to your `.zshrc` or `.bashrc` like this: `export GITHUB_TOKEN=yourGithubAPIToken`
-1. (π§ TODO π§ - Will this be relevant for open source?) Ensure the Zed.dev website is checked out in a sibling directory and install its dependencies:
+For a release build:
-```bash
-cd ..
-git clone https://github.com/zed-industries/zed.dev
-cd zed.dev && npm install
-pnpm install -g vercel
+```
+cargo run --release
```
-1. (π§ TODO π§ - Will this be relevant for open source?) Link your zed.dev project to Vercel
-
-- `vercel link`
-- Select the `zed-industries` team. If you don't have this get someone on the team to add you to it.
-- Select the `zed.dev` project
-
-1. (π§ TODO π§ - Will this be relevant for open source?) Run `vercel pull` to pull down the environment variables and project info from Vercel
-1. Open Postgres.app
-1. From `./path/to/zed/` run `GITHUB_TOKEN={yourGithubAPIToken} script/bootstrap`
-
-- You don't need to include the GITHUB_TOKEN if you exported it above.
-- Consider removing the token (if it's fine for you to recreate such tokens during occasional migrations) or store this token somewhere safe (like your Zed 1Password vault).
+And to run the tests:
-1. To run the Zed app:
- - If you are working on zed:
- - `cargo run`
- - If you are just using the latest version, but not working on zed:
- - `cargo run --release`
- - If you need to run the collaboration server locally:
- - `script/zed-local`
+```
+cargo test --workspace
+```
## Troubleshooting
-**`error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`**
-
-- Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer`
-
-**`xcrun: error: unable to find utility "metal", not a developer tool or in PATH`**
+### Error compiling metal shaders
-### `script/bootstrap`
-
-```bash
-Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)!
-Please create a new installation in /opt/homebrew using one of the
-"Alternative Installs" from:
-https://docs.brew.sh/Installation
```
+error: failed to run custom build command for gpui v0.1.0 (/Users/path/to/zed)`**
-- In that case try `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`
-
-- If Homebrew is not in your PATH:
- - Replace `{username}` with your home folder name (usually your login name)
- - `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/{username}/.zprofile`
- - `eval "$(/opt/homebrew/bin/brew shellenv)"`
-
+xcrun: error: unable to find utility "metal", not a developer tool or in PATH
```
-seeding database...
-thread 'main' panicked at 'failed to deserialize github user from 'https://api.github.com/orgs/zed-industries/teams/staff/members': reqwest::Error { kind: Decode, source: Error("invalid type: map, expected a sequence", line: 1, column: 0) }', crates/collab/src/bin/seed.rs:111:10
-```
-
-Wrong permissions for `GITHUB_TOKEN` token used, the token needs to be able to read from private repos.
-For Classic GitHub Tokens, that required OAuth scope `repo` (seacrh the scope name above for more details)
-
-Same command
-`sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`
+Try `xcode-select --switch /Applications/Xcode.app/Contents/Developer`
-### If you experience errors that mention some dependency is using unstable features
+### Cargo errors claiming that a dependency is using unstable features
Try `cargo clean` and `cargo build`,
@@ -1,22 +1,46 @@
# Local Collaboration
-## Setting up the local collaboration server
+First, make sure you've installed Zed's [backend dependencies](/developing_zed__building_zed.html#backend-dependencies).
-### Setting up for the first time?
+## Database setup
-1. Make sure you have livekit installed (`brew install livekit`)
-1. Install [Postgres](https://postgresapp.com) and run it.
-1. Then, from the root of the repo, run `script/bootstrap`.
+Before you can run the `collab` server locally, you'll need to set up a `zed` Postgres database.
-### Have a db that is out of date? / Need to migrate?
+```
+script/bootstrap
+```
-1. Make sure you have livekit installed (`brew install livekit`)
-1. Try `cd crates/collab && cargo run -- migrate` from the root of the repo.
-1. Run `script/seed-db`
+This script will set up the `zed` Postgres database, and populate it with some users. It requires internet access, because it fetches some users from the GitHub API.
-## Testing collab locally
+The script will create several *admin* users, who you'll sign in as by default when developing locally. The GitHub logins for these default admin users are specified in this file:
-1. Run `foreman start` from the root of the repo.
-1. In another terminal run `script/zed-local`.
-1. Two copies of Zed will open. Add yourself as a contact in the one that is not you.
-1. Start a collaboration session as normal with any open project.
+```
+cat crates/collab/.admins.default.json
+```
+
+To use a different set of admin users, you can create a file called `.admins.json` in the same directory:
+
+```
+cat > crates/collab/.admins.json <<JSON
+[
+ "your-github-login",
+ "another-github-login"
+]
+JSON
+```
+
+## Testing collaborative features locally
+
+In one terminal, run Zed's collaboration server and the `livekit` dev server:
+
+```
+foreman start
+```
+
+In a second terminal, run two or more instances of Zed.
+
+```
+script/zed-local -2
+```
+
+This script starts one to four instances of Zed, depending on the `-2`, `-3` or `-4` flags. Each instance will be connected to the local `collab` server, signed in as a different user from `.admins.json` or `.admins.default.json`.
@@ -4,7 +4,7 @@ set -eu
source script/lib/deploy-helpers.sh
if [[ $# < 2 ]]; then
- echo "Usage: $0 <production|staging|preview> <tag-name> (nightly is not yet supported)"
+ echo "Usage: $0 <production|preview|nightly|staging> <tag-name>"
exit 1
fi
environment=$1
@@ -2,8 +2,4 @@
set -e
cd crates/collab
-
-# Export contents of .env.toml
-eval "$(cargo run --quiet --bin dotenv)"
-
cargo run --quiet --package=collab --features seed-support --bin seed -- $@
@@ -4,16 +4,11 @@ set -eu
source script/lib/deploy-helpers.sh
if [[ $# < 1 ]]; then
- echo "Usage: $0 <production|staging|preview> (nightly is not yet supported)"
+ echo "Usage: $0 <production|preview|nightly|staging>"
exit 1
fi
environment=$1
-if [[ ${environment} == "nightly" ]]; then
- echo "nightly is not yet supported"
- exit 1
-fi
-
export_vars_for_environment ${environment}
target_zed_kube_cluster
@@ -4,6 +4,11 @@ const HELP = `
USAGE
zed-local [options] [zed args]
+SUMMARY
+ Runs 1-4 instances of Zed using a locally-running collaboration server.
+ Each instance of Zed will be signed in as a different user specified in
+ either \`.admins.json\` or \`.admins.default.json\`.
+
OPTIONS
--help Print this help message
--release Build Zed in release mode
@@ -12,6 +17,16 @@ OPTIONS
`.trim();
const { spawn, execFileSync } = require("child_process");
+const assert = require("assert");
+
+const defaultUsers = require("../crates/collab/.admins.default.json");
+let users = defaultUsers;
+try {
+ const customUsers = require("../crates/collab/.admins.json");
+ assert(customUsers.length > 0);
+ assert(customUsers.every((user) => typeof user === "string"));
+ users.splice(0, 0, ...customUsers);
+} catch (_) {}
const RESOLUTION_REGEX = /(\d+) x (\d+)/;
const DIGIT_FLAG_REGEX = /^--?(\d+)$/;
@@ -71,10 +86,6 @@ if (instanceCount > 1) {
}
}
-let users = ["nathansobo", "as-cii", "maxbrunsfeld", "iamnbutler"];
-
-const RUST_LOG = process.env.RUST_LOG || "info";
-
// If a user is specified, make sure it's first in the list
const user = process.env.ZED_IMPERSONATE;
if (user) {
@@ -88,18 +99,12 @@ const positions = [
`${instanceWidth},${instanceHeight}`,
];
-const buildArgs = (() => {
- const buildArgs = ["build"];
- if (isReleaseMode) {
- buildArgs.push("--release");
- }
-
- return buildArgs;
-})();
-const zedBinary = (() => {
- const target = isReleaseMode ? "release" : "debug";
- return `target/${target}/Zed`;
-})();
+let buildArgs = ["build"];
+let zedBinary = "target/debug/Zed";
+if (isReleaseMode) {
+ buildArgs.push("--release");
+ zedBinary = "target/release/Zed";
+}
execFileSync("cargo", buildArgs, { stdio: "inherit" });
setTimeout(() => {
@@ -115,7 +120,7 @@ setTimeout(() => {
ZED_ADMIN_API_TOKEN: "secret",
ZED_WINDOW_SIZE: `${instanceWidth},${instanceHeight}`,
PATH: process.env.PATH,
- RUST_LOG,
+ RUST_LOG: process.env.RUST_LOG || "info",
},
});
}
@@ -0,0 +1,22 @@
+[files]
+ignore-files = true
+extend-exclude = [
+ # glsl isn't recognized by this tool
+ "crates/zed/src/languages/glsl/*",
+ # File suffixes aren't typos
+ "assets/icons/file_icons/file_types.json",
+ # Not our typos
+ "assets/themes/src/vscode/*",
+ "crates/live_kit_server/*",
+ # Vim makes heavy use of partial typing tables
+ "crates/vim/*",
+ # Editor and file finder rely on partial typing and custom in-string syntax
+ "crates/file_finder/src/file_finder.rs",
+ "crates/editor/src/editor_tests.rs",
+ # :/
+ "crates/collab/migrations/20231009181554_add_release_channel_to_rooms.sql",
+]
+
+[default]
+extend-ignore-re = ["ba"]
+check-filename = true