Compare commits

..

23 Commits

Author SHA1 Message Date
Max Brunsfeld
c1c67a5025 zed 0.97.6 2023-08-08 12:35:38 -07:00
Piotr Osiewicz
cbaf7cc972 Piotr/optimize search selections with a limit (#2831)
/cc @nathansobo @maxbrunsfeld 

Release Notes:
- Fixed scrollbar selections causing noticeable slowdowns with large
quantities of selections.
2023-08-08 12:34:37 -07:00
Max Brunsfeld
c24c4063e3 Make LspAdapter::process_diagnostics synchronous (#2829)
When editing rust code, the project diagnostics view sometimes fails to
update, so that you have to close the view and re-open it to see the
correct state.

This PR fixes one possible cause of that problem. There was an async
step in between *receiving* diagnostics from the language server and
updating the diagnostics, due to an async call to
`LspAdapter::process_diagnostics`. This could cause the following
sequence of events to happen:

1. Rust-analyzer sends us new diagnostics for a file `a.rs`
2. We call `process_diagnostics` with those diagnostics
3. Rust-analyzer sends us a `WorkDoneProgress` message, indicating that
the "flycheck" (aka `cargo check`) process has completed
4. We update the project diagnostics view due to this message.
5. The `process_diagnostics` call for `a.rs` completes
6. 💥 We have the new diagnostics for `a.rs`, but do not update the
project diagnostics view again.

This PR fixes this bug by simply making `process_diagnostics`
synchronous. There is no I/O or expensive computation happening in that
method. If we need to make it asynchronous in the future, we need to
introduce a queue that ensures that `publishDiagnostics` and
`workDoneProgress` messages are processed serially.

Release Notes:

- Fixed a bug where the project diagnostics view would sometimes fail to
update properly when using Rust-analyzer.
2023-08-08 12:34:28 -07:00
Joseph T. Lyons
707da9bbbf v0.97.x stable 2023-08-02 13:50:08 -04:00
Mikayla Maki
c826010008 Halve opacity on wrap guides (#2815)
Wrap guides are a little too bright as is
2023-08-02 12:52:33 -04:00
Mikayla Maki
e928e1db4e disable wrap guides in the assitant panel (#2814)
Wrap guides do not look correct in the assistant due to it's current
header styling. Disable them in that context now.

Release Notes:

- Fix a visual bug displaying when enabling wrap guides in the
assistant.
2023-08-02 12:51:34 -04:00
Joseph T. Lyons
f5b1962b0e zed 0.97.5 2023-07-28 18:13:24 -04:00
Mikayla Maki
0b13c6bc84 Make wrap guides respect scroll position (#2810)
Release Notes:

- Fixed a visual bug when scrolling with wrap guides active
2023-07-28 18:11:23 -04:00
Joseph T. Lyons
75365249cc Update release action to choose between preview and stable URL in Discord announcements
This is what ChatGPT told me, so we'll see.
2023-07-28 15:12:46 -04:00
Conrad Irwin
1c2f5162bc Don't highlight project search matches either (#2807)
@JosephTLyons this is probably worth merging alongside #2803

- vim: Fix a bug where focusing project search results unexpectedly
entered visual mode
2023-07-28 14:31:22 -04:00
Conrad Irwin
209c68c85e Fix jumping to definition in a new file (#2803)
This is broken because vim currently sets settings only on the active
editor. Fix this by correcting the range on the currently active editor.

It would be nice (at some point) to refactor how vim sets settings, but
that's for another day.

Release Notes:

- vim: Fix bug when jumping to definition in new file accidentally
entered visual mode.
2023-07-28 14:30:58 -04:00
Mikayla Maki
023a617b30 zed 0.97.4 2023-07-27 16:36:49 -07:00
Mikayla Maki
286d302273 Make mode indicator follow vim enabled state (#2802)
There was a minor visual bug introduced in
https://github.com/zed-industries/zed/pull/2801, this PR corrects it.

Release Notes:

- N/A
2023-07-27 16:32:28 -07:00
Julia
4091a2004e Avoid panic by accessing view handle by global in wrong window
View handles are window specific but this global will be doing things
in all windows, that would cause a panic when it attempted to update
a status bar mode indicator in a background window

Co-Authored-By: Mikayla Maki <mikayla@zed.dev>
2023-07-27 16:32:20 -07:00
Joseph T. Lyons
9d4a2bfb58 Publish preview releases to discord (#2800)
Release Notes:

- N/A
2023-07-27 15:26:36 -04:00
Joseph T. Lyons
33e58d47ac zed 0.97.3 2023-07-27 14:30:24 -04:00
Mikayla Maki
056282f59b Downgrade our dependency on treesitter-cpp (#2799)
Our dependency on `tree-sitter-cpp` got upgraded to an incompatible
version despite semver 'guarantees'. This pins the dependency onto the
commit of version 0.20.0

Release Notes:

- Restored language detection for C++ (preview-only)
2023-07-27 14:21:16 -04:00
Antonio Scandurra
553b9601d1 zed 0.97.2 2023-07-27 14:47:09 +02:00
Antonio Scandurra
a166f0b56a Maintain cursor stack's position correctly when ascending the tree (#2795)
This fixes a bug that could cause the cursor to incorrectly report its
start when using `slice` or `seek_forward`, and then calling `prev`. We
didn't notice this because we were not testing those three methods
together.

I suppose this could explain some of the panics we've observed because
we do use `slice`/`seek_forward` followed by `prev` calls in production.
2023-07-27 14:45:38 +02:00
Joseph T. Lyons
badc2ec0e9 zed 0.97.1 2023-07-26 18:57:44 -04:00
Mikayla Maki
9c3c719ce0 Downgrade tree sitter elm to 5.6.4 (#2794)
The tree sitter elm parser contains a c symbol which collides with other
linked symbols. This PR downgrades the tree sitter elm parser to a
version which doesn't have this problem.

Release Notes:
- Fixed crash when parsing elm files
2023-07-26 18:55:24 -04:00
Mikayla Maki
3594b5e2a8 Block extra drag events in original drag handlers (#2793)
In https://github.com/zed-industries/zed/pull/2790 I added an extra drag
event on mouse_up which signaled the end of a drag event, as mouse_up
event themselves wouldn't reliably fire if users moved their mouse too
quickly. This broke the assumptions of the terminal element. This PR
adds filters to all current on_drag handlers which removes this new
event.

Release Notes:

- Fixed a bug causing terminal links to never open (preview only)
- Fixed a bug in terminal link detection causing it to miss files with a
`-` in it
2023-07-26 18:20:50 -04:00
Joseph T. Lyons
99d0ed4a76 v0.97.x preview 2023-07-26 13:27:34 -04:00
136 changed files with 2649 additions and 5491 deletions

View File

@@ -1,5 +0,0 @@
{
"JSON": {
"tab_size": 4
}
}

625
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -79,7 +79,6 @@ resolver = "2"
anyhow = { version = "1.0.57" }
async-trait = { version = "0.1" }
ctor = { version = "0.1" }
derive_more = { version = "0.99.17" }
env_logger = { version = "0.9" }
futures = { version = "0.3" }
globset = { version = "0.4" }
@@ -112,7 +111,7 @@ tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", re
tree-sitter-c = "0.20.1"
tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev="f44509141e7e483323d2ec178f2d2e6c0fc041c1" }
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
tree-sitter-embedded-template = "0.20.0"
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
@@ -132,7 +131,6 @@ tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", r
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"}
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
tree-sitter-lua = "0.0.14"
tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "66e3e9ce9180ae08fc57372061006ef83f0abde7" }
[patch.crates-io]
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1c65ca24bc9a734ab70115188f465e12eecf224e" }

View File

@@ -1,6 +1,6 @@
# syntax = docker/dockerfile:1.2
FROM rust:1.71-bullseye as builder
FROM rust:1.70-bullseye as builder
WORKDIR app
COPY . .

View File

@@ -1,93 +0,0 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -1,179 +1,159 @@
{
"suffixes": {
"aac": "audio",
"accdb": "storage",
"bak": "backup",
"bash": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"bash_profile": "terminal",
"bashrc": "terminal",
"bmp": "image",
"c": "code",
"cc": "code",
"conf": "settings",
"cpp": "code",
"css": "code",
"csv": "storage",
"dat": "storage",
"db": "storage",
"dbf": "storage",
"dll": "storage",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"fmp": "storage",
"fp7": "storage",
"flac": "audio",
"fish": "terminal",
"frm": "storage",
"gdb": "storage",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"ib": "storage",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"ldf": "storage",
"lock": "lock",
"log": "log",
"mdb": "storage",
"md": "document",
"mdf": "storage",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"myd": "storage",
"myi": "storage",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdb": "storage",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
"prettierrc": "prettier",
"profile": "terminal",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"sav": "storage",
"scm": "code",
"sh": "terminal",
"sqlite": "storage",
"sdf": "storage",
"svelte": "template",
"svg": "image",
"swift": "code",
"ts": "typescript",
"tsx": "code",
"tiff": "image",
"toml": "toml",
"tsv": "storage",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
"zshenv": "terminal",
"zsh_histfile": "terminal",
"zsh_profile": "terminal",
"zshrc": "terminal"
"suffixes": {
"aac": "audio",
"bash": "terminal",
"bmp": "image",
"c": "code",
"conf": "settings",
"cpp": "code",
"cc": "code",
"css": "code",
"doc": "document",
"docx": "document",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
"flac": "audio",
"fish": "terminal",
"gitattributes": "vcs",
"gitignore": "vcs",
"gitmodules": "vcs",
"gif": "image",
"go": "code",
"h": "code",
"handlebars": "code",
"hbs": "template",
"htm": "template",
"html": "template",
"svelte": "template",
"hpp": "code",
"ico": "image",
"ini": "settings",
"java": "code",
"jpeg": "image",
"jpg": "image",
"js": "code",
"json": "storage",
"lock": "lock",
"log": "log",
"md": "document",
"mdx": "document",
"mp3": "audio",
"mp4": "video",
"ods": "document",
"odp": "document",
"odt": "document",
"ogg": "video",
"pdf": "document",
"php": "code",
"png": "image",
"ppt": "document",
"pptx": "document",
"prettierrc": "prettier",
"prettierignore": "prettier",
"ps1": "terminal",
"psd": "image",
"py": "code",
"rb": "code",
"rkt": "code",
"rs": "rust",
"rtf": "document",
"scm": "code",
"sh": "terminal",
"bashrc": "terminal",
"bash_profile": "terminal",
"bash_aliases": "terminal",
"bash_logout": "terminal",
"profile": "terminal",
"zshrc": "terminal",
"zshenv": "terminal",
"zsh_profile": "terminal",
"zsh_aliases": "terminal",
"zsh_histfile": "terminal",
"zlogin": "terminal",
"sql": "code",
"svg": "image",
"swift": "code",
"tiff": "image",
"toml": "toml",
"ts": "typescript",
"tsx": "code",
"txt": "document",
"wav": "audio",
"webm": "video",
"xls": "document",
"xlsx": "document",
"xml": "template",
"yaml": "settings",
"yml": "settings",
"zsh": "terminal"
},
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"types": {
"audio": {
"icon": "icons/file_icons/audio.svg"
},
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
}
"code": {
"icon": "icons/file_icons/code.svg"
},
"collapsed_chevron": {
"icon": "icons/file_icons/chevron_right.svg"
},
"collapsed_folder": {
"icon": "icons/file_icons/folder.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
},
"document": {
"icon": "icons/file_icons/book.svg"
},
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
"expanded_chevron": {
"icon": "icons/file_icons/chevron_down.svg"
},
"expanded_folder": {
"icon": "icons/file_icons/folder_open.svg"
},
"image": {
"icon": "icons/file_icons/image.svg"
},
"lock": {
"icon": "icons/file_icons/lock.svg"
},
"log": {
"icon": "icons/file_icons/info.svg"
},
"prettier": {
"icon": "icons/file_icons/prettier.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"settings": {
"icon": "icons/file_icons/settings.svg"
},
"storage": {
"icon": "icons/file_icons/database.svg"
},
"template": {
"icon": "icons/file_icons/html.svg"
},
"terminal": {
"icon": "icons/file_icons/terminal.svg"
},
"toml": {
"icon": "icons/file_icons/toml.svg"
},
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},
"video": {
"icon": "icons/file_icons/video.svg"
}
}
}

View File

@@ -22,7 +22,6 @@
"alt-cmd-right": "pane::ActivateNextItem",
"cmd-w": "pane::CloseActiveItem",
"alt-cmd-t": "pane::CloseInactiveItems",
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k u": "pane::CloseCleanItems",
"cmd-k cmd-w": "pane::CloseAllItems",
"cmd-shift-w": "workspace::CloseWindow",
@@ -227,26 +226,12 @@
"alt-enter": "search::SelectAllMatches"
}
},
{
"context": "BufferSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus"
}
},
{
"context": "ProjectSearchBar > Editor",
"bindings": {
"up": "search::PreviousHistoryQuery",
"down": "search::NextHistoryQuery"
}
},
{
"context": "ProjectSearchView",
"bindings": {
@@ -426,6 +411,7 @@
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-t": "project_symbols::Toggle",
"cmd-ctrl-t": "semantic_search::Toggle",
"cmd-p": "file_finder::Toggle",
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",

View File

@@ -12,7 +12,10 @@ use client::{
use collections::{HashMap, HashSet};
use fs::FakeFs;
use futures::{channel::oneshot, StreamExt as _};
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, WindowHandle};
use gpui::{
elements::*, executor::Deterministic, AnyElement, Entity, ModelHandle, TestAppContext, View,
ViewContext, ViewHandle, WeakViewHandle,
};
use language::LanguageRegistry;
use parking_lot::Mutex;
use project::{Project, WorktreeId};
@@ -463,8 +466,42 @@ impl TestClient {
&self,
project: &ModelHandle<Project>,
cx: &mut TestAppContext,
) -> WindowHandle<Workspace> {
cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
) -> ViewHandle<Workspace> {
struct WorkspaceContainer {
workspace: Option<WeakViewHandle<Workspace>>,
}
impl Entity for WorkspaceContainer {
type Event = ();
}
impl View for WorkspaceContainer {
fn ui_name() -> &'static str {
"WorkspaceContainer"
}
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
if let Some(workspace) = self
.workspace
.as_ref()
.and_then(|workspace| workspace.upgrade(cx))
{
ChildView::new(&workspace, cx).into_any()
} else {
Empty::new().into_any()
}
}
}
// We use a workspace container so that we don't need to remove the window in order to
// drop the workspace and we can use a ViewHandle instead.
let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
container.update(cx, |container, cx| {
container.workspace = Some(workspace.downgrade());
cx.notify();
});
workspace
}
}

View File

@@ -7,7 +7,8 @@ use client::{User, RECEIVE_TIMEOUT};
use collections::HashSet;
use editor::{
test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
Undo,
};
use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
use futures::StreamExt as _;
@@ -1207,7 +1208,7 @@ async fn test_share_project(
cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking();
let window_b = cx_b.add_window(|_| EmptyView);
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
@@ -1315,7 +1316,7 @@ async fn test_share_project(
.await
.unwrap();
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// Client A sees client B's selection
deterministic.run_until_parked();
@@ -1498,8 +1499,8 @@ async fn test_host_disconnect(
deterministic.run_until_parked();
assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let (window_id_b, workspace_b) =
cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1508,9 +1509,11 @@ async fn test_host_disconnect(
.unwrap()
.downcast::<Editor>()
.unwrap();
assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
assert!(cx_b
.read_window(window_id_b, |cx| editor_b.is_focused(cx))
.unwrap());
editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
assert!(window_b.is_edited(cx_b));
assert!(cx_b.is_window_edited(workspace_b.window_id()));
// Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
server.forbid_connections();
@@ -1522,10 +1525,10 @@ async fn test_host_disconnect(
assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
// Ensure client B's edited state is reset and that the whole window is blurred.
window_b.read_with(cx_b, |cx| {
cx_b.read_window(window_id_b, |cx| {
assert_eq!(cx.focused_view_id(), None);
});
assert!(!window_b.is_edited(cx_b));
assert!(!cx_b.is_window_edited(workspace_b.window_id()));
// Ensure client B is not prompted to save edits when closing window after disconnecting.
let can_close = workspace_b
@@ -3442,11 +3445,13 @@ 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_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a), cx)
});
let mut editor_cx_a = EditorTestContext {
cx: cx_a,
window: window_a.into(),
window_id: window_a,
editor: editor_a,
};
@@ -3455,11 +3460,13 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
.await
.unwrap();
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b), cx)
});
let mut editor_cx_b = EditorTestContext {
cx: cx_b,
window: window_b.into(),
window_id: window_b,
editor: editor_b,
};
@@ -4198,8 +4205,8 @@ async fn test_collaborating_with_completion(
.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
.await
.unwrap();
let window_b = cx_b.add_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
});
@@ -5309,8 +5316,7 @@ async fn test_collaborating_with_code_actions(
// Join the project as client B.
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -5534,8 +5540,7 @@ async fn test_collaborating_with_renames(
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let workspace_b = window_b.root(cx_b);
let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@@ -5566,7 +5571,6 @@ async fn test_collaborating_with_renames(
.unwrap();
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!(
@@ -6441,10 +6445,8 @@ async fn test_basic_following(
.await
.unwrap();
let window_a = client_a.build_workspace(&project_a, cx_a);
let workspace_a = window_a.root(cx_a);
let window_b = client_b.build_workspace(&project_b, cx_b);
let workspace_b = window_b.root(cx_b);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
// Client A opens some editors.
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
@@ -6527,8 +6529,7 @@ async fn test_basic_following(
cx_c.foreground().run_until_parked();
let active_call_c = cx_c.read(ActiveCall::global);
let project_c = client_c.build_remote_project(project_id, cx_c).await;
let window_c = client_c.build_workspace(&project_c, cx_c);
let workspace_c = window_c.root(cx_c);
let workspace_c = client_c.build_workspace(&project_c, cx_c);
active_call_c
.update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
.await
@@ -6546,7 +6547,7 @@ async fn test_basic_following(
cx_d.foreground().run_until_parked();
let active_call_d = cx_d.read(ActiveCall::global);
let project_d = client_d.build_remote_project(project_id, cx_d).await;
let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
let workspace_d = client_d.build_workspace(&project_d, cx_d);
active_call_d
.update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
.await
@@ -6644,7 +6645,6 @@ async fn test_basic_following(
}
// Client C closes the project.
window_c.remove(cx_c);
cx_c.drop_last(workspace_c);
// Clients A and B see that client B is following A, and client C is not present in the followers.
@@ -6874,7 +6874,9 @@ async fn test_basic_following(
});
// Client B activates a panel, and the previously-opened screen-sharing item gets activated.
let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
let panel = cx_b.add_view(workspace_b.window_id(), |_| {
TestPanel::new(DockPosition::Left)
});
workspace_b.update(cx_b, |workspace, cx| {
workspace.add_panel(panel, cx);
workspace.toggle_panel_focus::<TestPanel>(cx);
@@ -6902,7 +6904,7 @@ async fn test_basic_following(
// Client B activates an item that doesn't implement following,
// so the previously-opened screen-sharing item gets activated.
let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
let unfollowable_item = cx_b.add_view(workspace_b.window_id(), |_| TestItem::new());
workspace_b.update(cx_b, |workspace, cx| {
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@@ -7064,10 +7066,10 @@ async fn test_following_tab_order(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let client_b_id = project_a.read_with(cx_a, |project, _| {
@@ -7190,7 +7192,7 @@ async fn test_peers_following_each_other(
.unwrap();
// Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
@@ -7202,7 +7204,7 @@ async fn test_peers_following_each_other(
.unwrap();
// Client B opens an editor.
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let _editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
@@ -7361,7 +7363,7 @@ async fn test_auto_unfollowing(
.unwrap();
// Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let _editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -7372,7 +7374,7 @@ async fn test_auto_unfollowing(
.unwrap();
// Client B starts following client A.
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
let leader_id = project_b.read_with(cx_b, |project, _| {
project.collaborators().values().next().unwrap().peer_id
@@ -7500,14 +7502,14 @@ async fn test_peers_simultaneously_following_each_other(
client_a.fs.insert_tree("/a", json!({})).await;
let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let project_b = client_b.build_remote_project(project_id, cx_b).await;
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
deterministic.run_until_parked();
let client_a_id = project_b.read_with(cx_b, |project, _| {
@@ -7599,8 +7601,8 @@ 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_window(|_| EmptyView);
let editor_a = window_a.add_view(cx_a, |cx| {
let (window_a, _) = cx_a.add_window(|_| EmptyView);
let editor_a = cx_a.add_view(window_a, |cx| {
Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
});
@@ -7728,8 +7730,8 @@ 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_window(|_| EmptyView);
let editor_b = window_b.add_view(cx_b, |cx| {
let (window_b, _) = cx_b.add_window(|_| EmptyView);
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
});
@@ -7889,7 +7891,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
cx_a.foreground().start_waiting();
let _buffer_a = project_a
@@ -7957,7 +7959,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
"Host editor update the cache version after every cache/view change",
);
});
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
let editor_b = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -8196,8 +8198,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
.await
.unwrap();
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
let workspace_a = client_a.build_workspace(&project_a, cx_a);
let workspace_b = client_b.build_workspace(&project_b, cx_b);
cx_a.foreground().start_waiting();
cx_b.foreground().start_waiting();

View File

@@ -183,7 +183,7 @@ async fn apply_server_operation(
let username;
{
let mut plan = plan.lock();
let user = plan.user(user_id);
let mut user = plan.user(user_id);
if user.online {
return false;
}

View File

@@ -15,8 +15,8 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
ViewContext, ViewHandle, WeakViewHandle,
};
use picker::PickerEvent;
use project::{Project, RepositoryEntry};
@@ -374,7 +374,7 @@ impl CollabTitlebarItem {
"Share Feedback",
feedback::feedback_editor::GiveFeedback,
),
ContextMenuItem::action("Sign Out", SignOut),
ContextMenuItem::action("Sign out", SignOut),
]
} else {
vec![
@@ -1312,7 +1312,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
_: RectF,
_: &mut Self::LayoutState,
_: &mut CollabTitlebarItem,
_: &mut PaintContext<CollabTitlebarItem>,
_: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let mut path = PathBuilder::new();
path.reset(bounds.lower_left());

View File

@@ -305,18 +305,18 @@ impl ContactList {
github_login
);
let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
let window = cx.window();
let window_id = cx.window_id();
cx.spawn(|_, mut cx| async move {
if answer.next().await == Some(0) {
if let Err(e) = user_store
.update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
.await
{
window.prompt(
cx.prompt(
window_id,
PromptLevel::Info,
&format!("Failed to remove contact: {}", e),
&["Ok"],
&mut cx,
);
}
}

View File

@@ -7,7 +7,7 @@ use gpui::{
},
json::ToJson,
serde_json::{self, json},
AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, ViewContext,
AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
};
use crate::CollabTitlebarItem;
@@ -54,7 +54,7 @@ impl Element<CollabTitlebarItem> for FacePile {
visible_bounds: RectF,
_layout: &mut Self::LayoutState,
view: &mut CollabTitlebarItem,
cx: &mut PaintContext<CollabTitlebarItem>,
cx: &mut ViewContext<CollabTitlebarItem>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View File

@@ -7,7 +7,7 @@ use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
AnyElement, AppContext, Entity, View, ViewContext,
};
use util::ResultExt;
use workspace::AppState;
@@ -16,10 +16,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move {
let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
let mut notification_windows = Vec::new();
while let Some(incoming_call) = incoming_call.next().await {
for window in notification_windows.drain(..) {
window.remove(&mut cx);
for window_id in notification_windows.drain(..) {
cx.remove_window(window_id);
}
if let Some(incoming_call) = incoming_call {
@@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let window = cx.add_window(
let (window_id, _) = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right()
@@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
notification_windows.push(window);
notification_windows.push(window_id);
}
}
}

View File

@@ -26,7 +26,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.platform().screens() {
let screen_bounds = screen.bounds();
let window = cx.add_window(
let (window_id, _) = cx.add_window(
WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(
screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING),
@@ -52,20 +52,20 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
notification_windows
.entry(*project_id)
.or_insert(Vec::new())
.push(window);
.push(window_id);
}
}
room::Event::RemoteProjectUnshared { project_id } => {
if let Some(windows) = notification_windows.remove(&project_id) {
for window in windows {
window.remove(cx);
if let Some(window_ids) = notification_windows.remove(&project_id) {
for window_id in window_ids {
cx.update_window(window_id, |cx| cx.remove_window());
}
}
}
room::Event::Left => {
for (_, windows) in notification_windows.drain() {
for window in windows {
window.remove(cx);
for (_, window_ids) in notification_windows.drain() {
for window_id in window_ids {
cx.update_window(window_id, |cx| cx.remove_window());
}
}
}

View File

@@ -20,11 +20,11 @@ pub fn init(cx: &mut AppContext) {
{
status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
}
} else if let Some(window) = status_indicator.take() {
window.update(cx, |cx| cx.remove_window());
} else if let Some((window_id, _)) = status_indicator.take() {
cx.update_window(window_id, |cx| cx.remove_window());
}
} else if let Some(window) = status_indicator.take() {
window.update(cx, |cx| cx.remove_window());
} else if let Some((window_id, _)) = status_indicator.take() {
cx.update_window(window_id, |cx| cx.remove_window());
}
})
.detach();

View File

@@ -1,8 +1,8 @@
use collections::CommandPaletteFilter;
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
AppContext, Element, MouseState, ViewContext,
actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
ViewContext,
};
use picker::{Picker, PickerDelegate, PickerEvent};
use std::cmp;
@@ -28,7 +28,7 @@ pub struct CommandPaletteDelegate {
pub enum Event {
Dismissed,
Confirmed {
window: AnyWindowHandle,
window_id: usize,
focused_view_id: usize,
action: Box<dyn Action>,
},
@@ -80,13 +80,12 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
let window_id = cx.window_id();
let view_id = self.focused_view_id;
let window = cx.window();
cx.spawn(move |picker, mut cx| async move {
let actions = window
.available_actions(view_id, &cx)
let actions = cx
.available_actions(window_id, view_id)
.into_iter()
.flatten()
.filter_map(|(name, action, bindings)| {
let filtered = cx.read(|cx| {
if cx.has_global::<CommandPaletteFilter>() {
@@ -163,15 +162,13 @@ impl PickerDelegate for CommandPaletteDelegate {
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
if !self.matches.is_empty() {
let window = cx.window();
let window_id = cx.window_id();
let focused_view_id = self.focused_view_id;
let action_ix = self.matches[self.selected_ix].candidate_id;
let action = self.actions.remove(action_ix).action;
cx.app_context()
.spawn(move |mut cx| async move {
window
.dispatch_action(focused_view_id, action.as_ref(), &mut cx)
.ok_or_else(|| anyhow!("window was closed"))
cx.dispatch_action(window_id, focused_view_id, action.as_ref())
})
.detach_and_log_err(cx);
}
@@ -298,9 +295,8 @@ mod tests {
let app_state = init_test(cx);
let project = Project::test(app_state.fs.clone(), [], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let editor = window.add_view(cx, |cx| {
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let editor = cx.add_view(window_id, |cx| {
let mut editor = Editor::single_line(None, cx);
editor.set_text("abc", cx);
editor

View File

@@ -1,5 +1,5 @@
use gpui::{
anyhow::{self, anyhow},
anyhow,
elements::*,
geometry::vector::Vector2F,
keymap_matcher::KeymapContext,
@@ -218,14 +218,12 @@ impl ContextMenu {
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
match action {
ContextMenuItemAction::Action(action) => {
let window = cx.window();
let window_id = cx.window_id();
let view_id = self.parent_view_id;
let action = action.boxed_clone();
cx.app_context()
.spawn(|mut cx| async move {
window
.dispatch_action(view_id, action.as_ref(), &mut cx)
.ok_or_else(|| anyhow!("window was closed"))
cx.dispatch_action(window_id, view_id, action.as_ref())
})
.detach_and_log_err(cx);
}
@@ -482,19 +480,17 @@ impl ContextMenu {
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_click(MouseButton::Left, move |_, menu, cx| {
menu.cancel(&Default::default(), cx);
let window = cx.window();
let window_id = cx.window_id();
match &action {
ContextMenuItemAction::Action(action) => {
let action = action.boxed_clone();
cx.app_context()
.spawn(|mut cx| async move {
window
.dispatch_action(
view_id,
action.as_ref(),
&mut cx,
)
.ok_or_else(|| anyhow!("window was closed"))
cx.dispatch_action(
window_id,
view_id,
action.as_ref(),
)
})
.detach_and_log_err(cx);
}

View File

@@ -338,9 +338,9 @@ impl Copilot {
let (server, fake_server) =
LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
let this = cx.add_model(|_| Self {
let this = cx.add_model(|cx| Self {
http: http.clone(),
node_runtime: NodeRuntime::instance(http),
node_runtime: NodeRuntime::instance(http, cx.background().clone()),
server: CopilotServer::Running(RunningCopilotServer {
lsp: Arc::new(server),
sign_in_status: SignInStatus::Authorized,

View File

@@ -4,7 +4,7 @@ use gpui::{
geometry::rect::RectF,
platform::{WindowBounds, WindowKind, WindowOptions},
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
WindowHandle,
ViewHandle,
};
use theme::ui::modal;
@@ -18,43 +18,43 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
pub fn init(cx: &mut AppContext) {
if let Some(copilot) = Copilot::global(cx) {
let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
cx.observe(&copilot, move |copilot, cx| {
let status = copilot.read(cx).status();
match &status {
crate::Status::SigningIn { prompt } => {
if let Some(window) = verification_window.as_mut() {
let updated = window
.root(cx)
.map(|root| {
root.update(cx, |verification, cx| {
verification.set_status(status.clone(), cx);
cx.activate_window();
})
})
.is_some();
if !updated {
verification_window = Some(create_copilot_auth_window(cx, &status));
if let Some(code_verification_handle) = code_verification.as_mut() {
let window_id = code_verification_handle.window_id();
let updated = cx.update_window(window_id, |cx| {
code_verification_handle.update(cx, |code_verification, cx| {
code_verification.set_status(status.clone(), cx)
});
cx.activate_window();
});
if updated.is_none() {
code_verification = Some(create_copilot_auth_window(cx, &status));
}
} else if let Some(_prompt) = prompt {
verification_window = Some(create_copilot_auth_window(cx, &status));
code_verification = Some(create_copilot_auth_window(cx, &status));
}
}
Status::Authorized | Status::Unauthorized => {
if let Some(window) = verification_window.as_ref() {
if let Some(verification) = window.root(cx) {
verification.update(cx, |verification, cx| {
verification.set_status(status, cx);
cx.platform().activate(true);
cx.activate_window();
if let Some(code_verification) = code_verification.as_ref() {
let window_id = code_verification.window_id();
cx.update_window(window_id, |cx| {
code_verification.update(cx, |code_verification, cx| {
code_verification.set_status(status, cx)
});
}
cx.platform().activate(true);
cx.activate_window();
});
}
}
_ => {
if let Some(code_verification) = verification_window.take() {
code_verification.update(cx, |cx| cx.remove_window());
if let Some(code_verification) = code_verification.take() {
cx.update_window(code_verification.window_id(), |cx| cx.remove_window());
}
}
}
@@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
fn create_copilot_auth_window(
cx: &mut AppContext,
status: &Status,
) -> WindowHandle<CopilotCodeVerification> {
) -> ViewHandle<CopilotCodeVerification> {
let window_size = theme::current(cx).copilot.modal.dimensions();
let window_options = WindowOptions {
bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@@ -78,9 +78,10 @@ fn create_copilot_auth_window(
is_movable: true,
screen: None,
};
cx.add_window(window_options, |_cx| {
let (_, view) = cx.add_window(window_options, |_cx| {
CopilotCodeVerification::new(status.clone())
})
});
view
}
pub struct CopilotCodeVerification {

View File

@@ -855,8 +855,7 @@ mod tests {
let language_server_id = LanguageServerId(0);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Create some diagnostics
project.update(cx, |project, cx| {
@@ -943,7 +942,7 @@ mod tests {
});
// Open the project diagnostics view while there are already diagnostics.
let view = window.add_view(cx, |cx| {
let view = cx.add_view(window_id, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});
@@ -1249,10 +1248,9 @@ mod tests {
let server_id_1 = LanguageServerId(100);
let server_id_2 = LanguageServerId(101);
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let view = window.add_view(cx, |cx| {
let view = cx.add_view(window_id, |cx| {
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
});

View File

@@ -6,7 +6,7 @@ use gpui::{
geometry::{rect::RectF, vector::Vector2F},
platform::{CursorStyle, MouseButton},
scene::{MouseDown, MouseDrag},
AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
AnyElement, Element, View, ViewContext, WeakViewHandle, WindowContext,
};
const DEAD_ZONE: f32 = 4.;
@@ -21,7 +21,7 @@ enum State<V: View> {
region: RectF,
},
Dragging {
window: AnyWindowHandle,
window_id: usize,
position: Vector2F,
region_offset: Vector2F,
region: RectF,
@@ -49,14 +49,14 @@ impl<V: View> Clone for State<V> {
region,
},
State::Dragging {
window,
window_id,
position,
region_offset,
region,
payload,
render,
} => Self::Dragging {
window: window.clone(),
window_id: window_id.clone(),
position: position.clone(),
region_offset: region_offset.clone(),
region: region.clone(),
@@ -87,16 +87,16 @@ impl<V: View> DragAndDrop<V> {
self.containers.insert(handle);
}
pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
self.currently_dragged.as_ref().and_then(|state| {
if let State::Dragging {
position,
payload,
window: window_dragged_from,
window_id: window_dragged_from,
..
} = state
{
if &window != window_dragged_from {
if &window_id != window_dragged_from {
return None;
}
@@ -126,9 +126,9 @@ impl<V: View> DragAndDrop<V> {
cx: &mut WindowContext,
render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> AnyElement<V>>,
) {
let window = cx.window();
let window_id = cx.window_id();
cx.update_global(|this: &mut Self, cx| {
this.notify_containers_for_window(window, cx);
this.notify_containers_for_window(window_id, cx);
match this.currently_dragged.as_ref() {
Some(&State::Down {
@@ -141,7 +141,7 @@ impl<V: View> DragAndDrop<V> {
}) => {
if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
this.currently_dragged = Some(State::Dragging {
window,
window_id,
region_offset,
region,
position: event.position,
@@ -163,7 +163,7 @@ impl<V: View> DragAndDrop<V> {
..
}) => {
this.currently_dragged = Some(State::Dragging {
window,
window_id,
region_offset,
region,
position: event.position,
@@ -188,14 +188,14 @@ impl<V: View> DragAndDrop<V> {
State::Down { .. } => None,
State::DeadZone { .. } => None,
State::Dragging {
window,
window_id,
region_offset,
position,
region,
payload,
render,
} => {
if cx.window() != window {
if cx.window_id() != window_id {
return None;
}
@@ -260,27 +260,27 @@ impl<V: View> DragAndDrop<V> {
pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging {
payload, window, ..
payload, window_id, ..
}) = &self.currently_dragged
{
if payload.is::<P>() {
let window = *window;
let window_id = *window_id;
self.currently_dragged = Some(State::Canceled);
self.notify_containers_for_window(window, cx);
self.notify_containers_for_window(window_id, cx);
}
}
}
fn finish_dragging(&mut self, cx: &mut WindowContext) {
if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window, cx);
if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
self.notify_containers_for_window(window_id, cx);
}
}
fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
self.containers.retain(|container| {
if let Some(container) = container.upgrade(cx) {
if container.window() == window {
if container.window_id() == window_id {
container.update(cx, |_, cx| cx.notify());
}
true

View File

@@ -47,7 +47,6 @@ workspace = { path = "../workspace" }
aho-corasick = "0.7"
anyhow.workspace = true
convert_case = "0.6.0"
futures.workspace = true
indoc = "1.0.4"
itertools = "0.10"
@@ -57,12 +56,12 @@ ordered-float.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
smallvec.workspace = true
smol.workspace = true
rand.workspace = true
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-html = { workspace = true, optional = true }

View File

@@ -397,7 +397,7 @@ impl InlayMap {
buffer_snapshot: MultiBufferSnapshot,
mut buffer_edits: Vec<text::Edit<usize>>,
) -> (InlaySnapshot, Vec<InlayEdit>) {
let snapshot = &mut self.snapshot;
let mut snapshot = &mut self.snapshot;
if buffer_edits.is_empty() {
if snapshot.buffer.trailing_excerpt_update_count()
@@ -572,6 +572,7 @@ impl InlayMap {
})
.collect();
let buffer_snapshot = snapshot.buffer.clone();
drop(snapshot);
let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits);
(snapshot, edits)
}
@@ -634,6 +635,7 @@ impl InlayMap {
}
log::info!("removing inlays: {:?}", to_remove);
drop(snapshot);
let (snapshot, edits) = self.splice(to_remove, to_insert);
(snapshot, edits)
}

View File

@@ -28,7 +28,6 @@ use blink_manager::BlinkManager;
use client::{ClickhouseEvent, TelemetrySettings};
use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
pub use display_map::DisplayPoint;
use display_map::*;
@@ -232,13 +231,6 @@ actions!(
SortLinesCaseInsensitive,
ReverseLines,
ShuffleLines,
ConvertToUpperCase,
ConvertToLowerCase,
ConvertToTitleCase,
ConvertToSnakeCase,
ConvertToKebabCase,
ConvertToUpperCamelCase,
ConvertToLowerCamelCase,
Transpose,
Cut,
Copy,
@@ -361,13 +353,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::sort_lines_case_insensitive);
cx.add_action(Editor::reverse_lines);
cx.add_action(Editor::shuffle_lines);
cx.add_action(Editor::convert_to_upper_case);
cx.add_action(Editor::convert_to_lower_case);
cx.add_action(Editor::convert_to_title_case);
cx.add_action(Editor::convert_to_snake_case);
cx.add_action(Editor::convert_to_kebab_case);
cx.add_action(Editor::convert_to_upper_camel_case);
cx.add_action(Editor::convert_to_lower_camel_case);
cx.add_action(Editor::delete_to_previous_word_start);
cx.add_action(Editor::delete_to_previous_subword_start);
cx.add_action(Editor::delete_to_next_word_end);
@@ -4236,7 +4221,7 @@ impl Editor {
_: &SortLinesCaseSensitive,
cx: &mut ViewContext<Self>,
) {
self.manipulate_lines(cx, |lines| lines.sort())
self.manipulate_lines(cx, |text| text.sort())
}
pub fn sort_lines_case_insensitive(
@@ -4244,7 +4229,7 @@ impl Editor {
_: &SortLinesCaseInsensitive,
cx: &mut ViewContext<Self>,
) {
self.manipulate_lines(cx, |lines| lines.sort_by_key(|line| line.to_lowercase()))
self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase()))
}
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
@@ -4282,19 +4267,19 @@ impl Editor {
let text = buffer
.text_for_range(start_point..end_point)
.collect::<String>();
let mut lines = text.split("\n").collect_vec();
let mut text = text.split("\n").collect_vec();
let lines_len = lines.len();
callback(&mut lines);
let text_len = text.len();
callback(&mut text);
// This is a current limitation with selections.
// If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
debug_assert!(
lines.len() == lines_len,
text.len() == text_len,
"callback should not change the number of lines"
);
edits.push((start_point..end_point, lines.join("\n")));
edits.push((start_point..end_point, text.join("\n")));
let start_anchor = buffer.anchor_after(start_point);
let end_anchor = buffer.anchor_before(end_point);
@@ -4321,97 +4306,6 @@ impl Editor {
});
}
pub fn convert_to_upper_case(&mut self, _: &ConvertToUpperCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_uppercase())
}
pub fn convert_to_lower_case(&mut self, _: &ConvertToLowerCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_lowercase())
}
pub fn convert_to_title_case(&mut self, _: &ConvertToTitleCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Title))
}
pub fn convert_to_snake_case(&mut self, _: &ConvertToSnakeCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Snake))
}
pub fn convert_to_kebab_case(&mut self, _: &ConvertToKebabCase, cx: &mut ViewContext<Self>) {
self.manipulate_text(cx, |text| text.to_case(Case::Kebab))
}
pub fn convert_to_upper_camel_case(
&mut self,
_: &ConvertToUpperCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::UpperCamel))
}
pub fn convert_to_lower_camel_case(
&mut self,
_: &ConvertToLowerCamelCase,
cx: &mut ViewContext<Self>,
) {
self.manipulate_text(cx, |text| text.to_case(Case::Camel))
}
fn manipulate_text<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
where
Fn: FnMut(&str) -> String,
{
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = self.buffer.read(cx).snapshot(cx);
let mut new_selections = Vec::new();
let mut edits = Vec::new();
let mut selection_adjustment = 0i32;
for selection in self.selections.all::<usize>(cx) {
let selection_is_empty = selection.is_empty();
let (start, end) = if selection_is_empty {
let word_range = movement::surrounding_word(
&display_map,
selection.start.to_display_point(&display_map),
);
let start = word_range.start.to_offset(&display_map, Bias::Left);
let end = word_range.end.to_offset(&display_map, Bias::Left);
(start, end)
} else {
(selection.start, selection.end)
};
let text = buffer.text_for_range(start..end).collect::<String>();
let old_length = text.len() as i32;
let text = callback(&text);
new_selections.push(Selection {
start: (start as i32 - selection_adjustment) as usize,
end: ((start + text.len()) as i32 - selection_adjustment) as usize,
goal: SelectionGoal::None,
..selection
});
selection_adjustment += old_length - text.len() as i32;
edits.push((start..end, text));
}
self.transact(cx, |this, cx| {
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, None, cx);
});
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select(new_selections);
});
this.request_autoscroll(Autoscroll::fit(), cx);
});
}
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let buffer = &display_map.buffer_snapshot;

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ use gpui::{
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
text_layout::{self, Line, RunStyle, TextLayoutCache},
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
};
use itertools::Itertools;
use json::json;
@@ -2455,7 +2455,7 @@ impl Element<Editor> for EditorElement {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
editor: &mut Editor,
cx: &mut PaintContext<Editor>,
cx: &mut ViewContext<Editor>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@@ -2978,12 +2978,10 @@ mod tests {
fn test_layout_line_numbers(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let layouts = editor.update(cx, |editor, cx| {
@@ -2999,12 +2997,10 @@ mod tests {
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
})
.root(cx);
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple("", cx);
Editor::new(EditorMode::Full, buffer, None, None, cx)
});
editor.update(cx, |editor, cx| {
editor.set_placeholder_text("hello", cx);
@@ -3055,14 +3051,7 @@ mod tests {
let mut scene = SceneBuilder::new(1.0);
let bounds = RectF::new(Default::default(), size);
editor.update(cx, |editor, cx| {
element.paint(
&mut scene,
bounds,
bounds,
&mut state,
editor,
&mut PaintContext::new(cx),
);
element.paint(&mut scene, bounds, bounds, &mut state, editor, cx);
});
}
@@ -3218,12 +3207,10 @@ mod tests {
info!(
"Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
);
let editor = cx
.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
})
.root(cx);
let (_, editor) = cx.add_window(|cx| {
let buffer = MultiBuffer::build_simple(&input_text, cx);
Editor::new(editor_mode, buffer, None, None, cx)
});
let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
let (_, layout_state) = editor.update(cx, |editor, cx| {

View File

@@ -571,6 +571,7 @@ fn new_update_task(
if let Some(buffer) =
refresh_multi_buffer.buffer(pending_refresh_query.buffer_id)
{
drop(refresh_multi_buffer);
editor.inlay_hint_cache.update_tasks.insert(
pending_refresh_query.excerpt_id,
UpdateTask {
@@ -1135,9 +1136,7 @@ mod tests {
)
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1837,9 +1836,7 @@ mod tests {
.await;
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1992,9 +1989,7 @@ mod tests {
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2080,9 +2075,8 @@ mod tests {
deterministic.run_until_parked();
cx.foreground().run_until_parked();
let editor = cx
.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let (_, editor) =
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2334,9 +2328,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
project.update(cx, |project, _| {
project.languages().add(Arc::clone(&language))
});
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2381,9 +2373,8 @@ all hints should be invalidated and requeried for all of its visible excerpts"
deterministic.run_until_parked();
cx.foreground().run_until_parked();
let editor = cx
.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
.root(cx);
let (_, editor) =
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
let editor_edited = Arc::new(AtomicBool::new(false));
let fake_server = fake_servers.next().await.unwrap();
let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2571,9 +2562,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let project = Project::test(fs, ["/a".as_ref()], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = workspace.update(cx, |workspace, cx| {
workspace.project().read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()

View File

@@ -28,10 +28,7 @@ use std::{
path::{Path, PathBuf},
};
use text::Selection;
use util::{
paths::{PathExt, FILE_ROW_COLUMN_DELIMITER},
ResultExt, TryFutureExt,
};
use util::{paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
@@ -549,7 +546,9 @@ impl Item for Editor {
.and_then(|f| f.as_local())?
.abs_path(cx);
let file_path = file_path.compact().to_string_lossy().to_string();
let file_path = util::paths::compact(&file_path)
.to_string_lossy()
.to_string();
Some(file_path.into())
}

View File

@@ -69,8 +69,7 @@ impl<'a> EditorLspTestContext<'a> {
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
.await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
project
.update(cx, |project, cx| {
project.find_or_create_local_worktree("/root", true, cx)
@@ -99,7 +98,7 @@ impl<'a> EditorLspTestContext<'a> {
Self {
cx: EditorTestContext {
cx,
window: window.into(),
window_id,
editor,
},
lsp,

View File

@@ -3,8 +3,7 @@ use crate::{
};
use futures::Future;
use gpui::{
keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
ViewContext, ViewHandle,
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
};
use indoc::indoc;
use language::{Buffer, BufferSnapshot};
@@ -22,7 +21,7 @@ use super::build_editor;
pub struct EditorTestContext<'a> {
pub cx: &'a mut gpui::TestAppContext,
pub window: AnyWindowHandle,
pub window_id: usize,
pub editor: ViewHandle<Editor>,
}
@@ -33,14 +32,16 @@ impl<'a> EditorTestContext<'a> {
let buffer = project
.update(cx, |project, cx| project.create_buffer("", None, cx))
.unwrap();
let window = cx.add_window(|cx| {
cx.focus_self();
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
let (window_id, editor) = cx.update(|cx| {
cx.add_window(Default::default(), |cx| {
cx.focus_self();
build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
})
});
let editor = window.root(cx);
Self {
cx,
window: window.into(),
window_id,
editor,
}
}
@@ -112,8 +113,7 @@ impl<'a> EditorTestContext<'a> {
let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
let keystroke = Keystroke::parse(keystroke_text).unwrap();
self.cx.dispatch_keystroke(self.window, keystroke, false);
self.cx.dispatch_keystroke(self.window_id, keystroke, false);
keystroke_under_test_handle
}

View File

@@ -617,9 +617,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
@@ -632,8 +631,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -672,9 +671,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@@ -706,8 +704,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -756,9 +754,8 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
cx.dispatch_action(window_id, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
let file_query = &first_file_name[..3];
@@ -790,8 +787,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window.into(), SelectNext);
cx.dispatch_action(window.into(), Confirm);
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())
.await;
@@ -840,23 +837,19 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
)
})
.root(cx);
),
cx,
)
});
let query = test_path_like("hi");
finder
@@ -938,23 +931,19 @@ mod tests {
cx,
)
.await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
)
})
.root(cx);
),
cx,
)
});
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@@ -978,23 +967,19 @@ mod tests {
cx,
)
.await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
)
})
.root(cx);
),
cx,
)
});
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -1030,6 +1015,61 @@ mod tests {
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
}
#[gpui::test]
async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
"dir1": { "a.txt": "" },
"dir2": { "a.txt": "" }
}),
)
.await;
let project = Project::test(
app_state.fs.clone(),
["/root/dir1".as_ref(), "/root/dir2".as_ref()],
cx,
)
.await;
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
cx,
)
});
// Run a search that matches two files with the same relative path.
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
})
.await;
// Can switch between different matches with the same relative path.
finder.update(cx, |finder, cx| {
let delegate = finder.delegate_mut();
assert_eq!(delegate.matches.len(), 2);
assert_eq!(delegate.selected_index(), 0);
delegate.set_selected_index(1, cx);
assert_eq!(delegate.selected_index(), 1);
delegate.set_selected_index(0, cx);
assert_eq!(delegate.selected_index(), 0);
});
}
#[gpui::test]
async fn test_path_distance_ordering(cx: &mut TestAppContext) {
let app_state = init_test(cx);
@@ -1049,9 +1089,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@@ -1065,20 +1103,18 @@ mod tests {
worktree_id,
path: Arc::from(Path::new("/root/dir2/b.txt")),
}));
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
b_path,
Vec::new(),
cx,
),
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
b_path,
Vec::new(),
cx,
)
})
.root(cx);
),
cx,
)
});
finder
.update(cx, |f, cx| {
@@ -1115,23 +1151,19 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let finder = cx
.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
),
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let (_, finder) = cx.add_window(|cx| {
Picker::new(
FileFinderDelegate::new(
workspace.downgrade(),
workspace.read(cx).project().clone(),
None,
Vec::new(),
cx,
)
})
.root(cx);
),
cx,
)
});
finder
.update(cx, |f, cx| {
f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@@ -1166,8 +1198,7 @@ mod tests {
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
@@ -1185,7 +1216,7 @@ mod tests {
"fir",
1,
"first.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1200,7 +1231,7 @@ mod tests {
"sec",
1,
"second.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1222,7 +1253,7 @@ mod tests {
"thi",
1,
"third.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1254,7 +1285,7 @@ mod tests {
"sec",
1,
"second.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1293,7 +1324,7 @@ mod tests {
"thi",
1,
"third.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1373,8 +1404,7 @@ mod tests {
.detach();
deterministic.run_until_parked();
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let worktree_id = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1,);
@@ -1409,7 +1439,7 @@ mod tests {
"sec",
1,
"second.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1431,7 +1461,7 @@ mod tests {
"fir",
1,
"first.rs",
window.into(),
window_id,
&workspace,
&deterministic,
cx,
@@ -1463,12 +1493,12 @@ mod tests {
input: &str,
expected_matches: usize,
expected_editor_title: &str,
window: gpui::AnyWindowHandle,
window_id: usize,
workspace: &ViewHandle<Workspace>,
deterministic: &gpui::executor::Deterministic,
cx: &mut gpui::TestAppContext,
) -> Vec<FoundPath> {
cx.dispatch_action(window, Toggle);
cx.dispatch_action(window_id, Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
.update(cx, |finder, cx| {
@@ -1485,8 +1515,8 @@ mod tests {
});
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
cx.dispatch_action(window, SelectNext);
cx.dispatch_action(window, Confirm);
cx.dispatch_action(window_id, SelectNext);
cx.dispatch_action(window_id, Confirm);
deterministic.run_until_parked();
active_pane
.condition(cx, |pane, _| pane.active_item().is_some())

View File

@@ -135,7 +135,7 @@ impl Entity for GoToLine {
fn release(&mut self, cx: &mut AppContext) {
let scroll_position = self.prev_scroll_position.take();
self.active_editor.window().update(cx, |cx| {
cx.update_window(self.active_editor.window_id(), |cx| {
self.active_editor.update(cx, |editor, cx| {
editor.highlight_rows(None);
if let Some(scroll_position) = scroll_position {

View File

@@ -22,7 +22,6 @@ sqlez = { path = "../sqlez" }
async-task = "4.0.3"
backtrace = { version = "0.3", optional = true }
ctor.workspace = true
derive_more.workspace = true
dhat = { version = "0.3", optional = true }
env_logger = { version = "0.9", optional = true }
etagere = "0.2"

File diff suppressed because it is too large Load Diff

View File

@@ -77,9 +77,9 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
let cx = app.0.clone();
move |action| {
let mut cx = cx.borrow_mut();
if let Some(main_window) = cx.active_window() {
let dispatched = main_window
.update(&mut *cx, |cx| {
if let Some(main_window_id) = cx.platform.main_window_id() {
let dispatched = cx
.update_window(main_window_id, |cx| {
if let Some(view_id) = cx.focused_view_id() {
cx.dispatch_action(Some(view_id), action);
true

View File

@@ -9,7 +9,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
#[cfg(any(test, feature = "test-support"))]
use crate::util::post_inc;
use crate::{AnyWindowHandle, ElementStateId};
use crate::ElementStateId;
lazy_static! {
static ref LEAK_BACKTRACE: bool =
@@ -26,7 +26,7 @@ pub struct RefCounts {
entity_counts: HashMap<usize, usize>,
element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
dropped_models: HashSet<usize>,
dropped_views: HashSet<(AnyWindowHandle, usize)>,
dropped_views: HashSet<(usize, usize)>,
dropped_element_states: HashSet<ElementStateId>,
#[cfg(any(test, feature = "test-support"))]
@@ -55,12 +55,12 @@ impl RefCounts {
}
}
pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
match self.entity_counts.entry(view_id) {
Entry::Occupied(mut entry) => *entry.get_mut() += 1,
Entry::Vacant(entry) => {
entry.insert(1);
self.dropped_views.remove(&(window, view_id));
self.dropped_views.remove(&(window_id, view_id));
}
}
}
@@ -94,12 +94,12 @@ impl RefCounts {
}
}
pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
let count = self.entity_counts.get_mut(&view_id).unwrap();
*count -= 1;
if *count == 0 {
self.entity_counts.remove(&view_id);
self.dropped_views.insert((window, view_id));
self.dropped_views.insert((window_id, view_id));
}
}
@@ -120,7 +120,7 @@ impl RefCounts {
&mut self,
) -> (
HashSet<usize>,
HashSet<(AnyWindowHandle, usize)>,
HashSet<(usize, usize)>,
HashSet<ElementStateId>,
) {
(

View File

@@ -4,9 +4,9 @@ use crate::{
keymap_matcher::{Binding, Keystroke},
platform,
platform::{Event, InputHandler, KeyDownEvent, Platform},
Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
WeakHandle, WindowContext, WindowHandle,
Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
WindowContext,
};
use collections::BTreeMap;
use futures::Future;
@@ -60,7 +60,7 @@ impl TestAppContext {
RefCounts::new(leak_detector),
(),
);
cx.next_id = first_entity_id;
cx.next_entity_id = first_entity_id;
let cx = TestAppContext {
cx: Rc::new(RefCell::new(cx)),
foreground_platform,
@@ -72,8 +72,8 @@ impl TestAppContext {
cx
}
pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
self.update_window(window, |window| {
pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
self.update_window(window_id, |window| {
window.dispatch_action(window.focused_view_id(), &action);
})
.expect("window not found");
@@ -81,10 +81,10 @@ impl TestAppContext {
pub fn available_actions(
&self,
window: AnyWindowHandle,
window_id: usize,
view_id: usize,
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
self.read_window(window, |cx| cx.available_actions(view_id))
self.read_window(window_id, |cx| cx.available_actions(view_id))
.unwrap_or_default()
}
@@ -92,34 +92,33 @@ impl TestAppContext {
self.update(|cx| cx.dispatch_global_action_any(&action));
}
pub fn dispatch_keystroke(
&mut self,
window: AnyWindowHandle,
keystroke: Keystroke,
is_held: bool,
) {
let handled = window.update(self, |cx| {
if cx.dispatch_keystroke(&keystroke) {
return true;
}
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
let handled = self
.cx
.borrow_mut()
.update_window(window_id, |cx| {
if cx.dispatch_keystroke(&keystroke) {
return true;
}
if cx.dispatch_event(
Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
}),
false,
) {
return true;
}
if cx.dispatch_event(
Event::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
is_held,
}),
false,
) {
return true;
}
false
});
false
})
.unwrap_or(false);
if !handled && !keystroke.cmd && !keystroke.ctrl {
WindowInputHandler {
app: self.cx.clone(),
window,
window_id,
}
.replace_text_in_range(None, &keystroke.key)
}
@@ -127,18 +126,18 @@ impl TestAppContext {
pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
&self,
window: AnyWindowHandle,
window_id: usize,
callback: F,
) -> Option<T> {
self.cx.borrow().read_window(window, callback)
self.cx.borrow().read_window(window_id, callback)
}
pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window: AnyWindowHandle,
window_id: usize,
callback: F,
) -> Option<T> {
self.cx.borrow_mut().update_window(window, callback)
self.cx.borrow_mut().update_window(window_id, callback)
}
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
@@ -149,17 +148,26 @@ impl TestAppContext {
self.cx.borrow_mut().add_model(build_model)
}
pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
{
let window = self
let (window_id, view) = self
.cx
.borrow_mut()
.add_window(Default::default(), build_root_view);
window.simulate_activation(self);
window
self.simulate_window_activation(Some(window_id));
(window_id, view)
}
pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
where
T: View,
F: FnOnce(&mut ViewContext<T>) -> T,
{
self.update_window(window_id, |cx| cx.add_view(build_view))
.expect("window not found")
}
pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@@ -182,8 +190,8 @@ impl TestAppContext {
self.cx.borrow_mut().subscribe_global(callback)
}
pub fn windows(&self) -> Vec<AnyWindowHandle> {
self.cx.borrow().windows().collect()
pub fn window_ids(&self) -> Vec<usize> {
self.cx.borrow().windows.keys().copied().collect()
}
pub fn remove_all_windows(&mut self) {
@@ -253,6 +261,76 @@ impl TestAppContext {
self.foreground_platform.as_ref().did_prompt_for_new_path()
}
pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
use postage::prelude::Sink as _;
let mut done_tx = self
.platform_window_mut(window_id)
.pending_prompts
.borrow_mut()
.pop_front()
.expect("prompt was not called");
done_tx.try_send(answer).ok();
}
pub fn has_pending_prompt(&self, window_id: usize) -> bool {
let window = self.platform_window_mut(window_id);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_window_title(&self, window_id: usize) -> Option<String> {
self.platform_window_mut(window_id).title.clone()
}
pub fn simulate_window_close(&self, window_id: usize) -> bool {
let handler = self
.platform_window_mut(window_id)
.should_close_handler
.take();
if let Some(mut handler) = handler {
let should_close = handler();
self.platform_window_mut(window_id).should_close_handler = Some(handler);
should_close
} else {
false
}
}
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
let mut window = self.platform_window_mut(window_id);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.platform_window_mut(window_id).resize_handlers = handlers;
}
pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
self.cx.borrow_mut().update(|cx| {
let other_window_ids = cx
.windows
.keys()
.filter(|window_id| Some(**window_id) != to_activate)
.copied()
.collect::<Vec<_>>();
for window_id in other_window_ids {
cx.window_changed_active_status(window_id, false)
}
if let Some(to_activate) = to_activate {
cx.window_changed_active_status(to_activate, true)
}
});
}
pub fn is_window_edited(&self, window_id: usize) -> bool {
self.platform_window_mut(window_id).edited
}
pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
self.cx.borrow().leak_detector()
}
@@ -273,6 +351,18 @@ impl TestAppContext {
self.assert_dropped(weak);
}
fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
let window = state.windows.get_mut(&window_id).unwrap();
let test_window = window
.platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
test_window
})
}
pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
self.condition_duration = duration;
}
@@ -315,39 +405,19 @@ impl BorrowAppContext for TestAppContext {
}
impl BorrowWindowContext for TestAppContext {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
self.cx
.borrow()
.read_window(window, f)
.read_window(window_id, f)
.expect("window was closed")
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window(self, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
window: AnyWindowHandle,
f: F,
) -> T {
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
self.cx
.borrow_mut()
.update_window(window, f)
.update_window(window_id, f)
.expect("window was closed")
}
fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window(self, window, f)
}
}
impl<T: Entity> ModelHandle<T> {
@@ -462,71 +532,6 @@ impl<T: Entity> ModelHandle<T> {
}
}
impl AnyWindowHandle {
pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
let window = self.platform_window_mut(cx);
let prompts = window.pending_prompts.borrow_mut();
!prompts.is_empty()
}
pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
self.platform_window_mut(cx).title.clone()
}
pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
let handler = self.platform_window_mut(cx).should_close_handler.take();
if let Some(mut handler) = handler {
let should_close = handler();
self.platform_window_mut(cx).should_close_handler = Some(handler);
should_close
} else {
false
}
}
pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
let mut window = self.platform_window_mut(cx);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.platform_window_mut(cx).resize_handlers = handlers;
}
pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
self.platform_window_mut(cx).edited
}
pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
use postage::prelude::Sink as _;
let mut done_tx = self
.platform_window_mut(cx)
.pending_prompts
.borrow_mut()
.pop_front()
.expect("prompt was not called");
done_tx.try_send(answer).ok();
}
fn platform_window_mut<'a>(
&self,
cx: &'a mut TestAppContext,
) -> std::cell::RefMut<'a, platform::test::Window> {
std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
let window = state.windows.get_mut(&self).unwrap();
let test_window = window
.platform_window
.as_any_mut()
.downcast_mut::<platform::test::Window>()
.unwrap();
test_window
})
}
}
impl<T: View> ViewHandle<T> {
pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
use postage::prelude::{Sink as _, Stream as _};

View File

@@ -13,10 +13,9 @@ use crate::{
},
text_layout::TextLayoutCache,
util::post_inc,
Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
WindowInvalidation,
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
View, ViewContext, ViewHandle, WindowInvalidation,
};
use anyhow::{anyhow, bail, Result};
use collections::{HashMap, HashSet};
@@ -52,8 +51,8 @@ pub struct Window {
cursor_regions: Vec<CursorRegion>,
mouse_regions: Vec<(MouseRegion, usize)>,
last_mouse_moved_event: Option<Event>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
mouse_position: Vector2F,
text_layout_cache: TextLayoutCache,
@@ -61,7 +60,7 @@ pub struct Window {
impl Window {
pub fn new<V, F>(
handle: AnyWindowHandle,
window_id: usize,
platform_window: Box<dyn platform::Window>,
cx: &mut AppContext,
build_view: F,
@@ -93,7 +92,7 @@ impl Window {
appearance,
};
let mut window_context = WindowContext::mutable(cx, &mut window, handle);
let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
let root_view = window_context.add_view(|cx| build_view(cx));
if let Some(invalidation) = window_context.window.invalidation.take() {
window_context.invalidate(invalidation, appearance);
@@ -114,7 +113,7 @@ impl Window {
pub struct WindowContext<'a> {
pub(crate) app_context: Reference<'a, AppContext>,
pub(crate) window: Reference<'a, Window>,
pub(crate) window_handle: AnyWindowHandle,
pub(crate) window_id: usize,
pub(crate) removed: bool,
}
@@ -143,66 +142,42 @@ impl BorrowAppContext for WindowContext<'_> {
}
impl BorrowWindowContext for WindowContext<'_> {
type Result<T> = T;
fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
if self.window_handle == handle {
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
if self.window_id == window_id {
f(self)
} else {
panic!("read_with called with id of window that does not belong to this context")
}
}
fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&WindowContext) -> Option<T>,
{
BorrowWindowContext::read_window(self, window, f)
}
fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
&mut self,
handle: AnyWindowHandle,
f: F,
) -> T {
if self.window_handle == handle {
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
if self.window_id == window_id {
f(self)
} else {
panic!("update called with id of window that does not belong to this context")
}
}
fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
where
F: FnOnce(&mut WindowContext) -> Option<T>,
{
BorrowWindowContext::update_window(self, handle, f)
}
}
impl<'a> WindowContext<'a> {
pub fn mutable(
app_context: &'a mut AppContext,
window: &'a mut Window,
handle: AnyWindowHandle,
window_id: usize,
) -> Self {
Self {
app_context: Reference::Mutable(app_context),
window: Reference::Mutable(window),
window_handle: handle,
window_id,
removed: false,
}
}
pub fn immutable(
app_context: &'a AppContext,
window: &'a Window,
handle: AnyWindowHandle,
) -> Self {
pub fn immutable(app_context: &'a AppContext, window: &'a Window, window_id: usize) -> Self {
Self {
app_context: Reference::Immutable(app_context),
window: Reference::Immutable(window),
window_handle: handle,
window_id,
removed: false,
}
}
@@ -211,8 +186,8 @@ impl<'a> WindowContext<'a> {
self.removed = true;
}
pub fn window(&self) -> AnyWindowHandle {
self.window_handle
pub fn window_id(&self) -> usize {
self.window_id
}
pub fn app_context(&mut self) -> &mut AppContext {
@@ -235,10 +210,10 @@ impl<'a> WindowContext<'a> {
where
F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
{
let handle = self.window_handle;
let mut view = self.views.remove(&(handle, view_id))?;
let window_id = self.window_id;
let mut view = self.views.remove(&(window_id, view_id))?;
let result = f(view.as_mut(), self);
self.views.insert((handle, view_id), view);
self.views.insert((window_id, view_id), view);
Some(result)
}
@@ -263,9 +238,9 @@ impl<'a> WindowContext<'a> {
}
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
let handle = self.window_handle;
let window_id = self.window_id;
self.app_context.defer(move |cx| {
cx.update_window(handle, |cx| callback(cx));
cx.update_window(window_id, |cx| callback(cx));
})
}
@@ -305,10 +280,10 @@ impl<'a> WindowContext<'a> {
H: Handle<E>,
F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
{
let window_handle = self.window_handle;
let window_id = self.window_id;
self.app_context
.subscribe_internal(handle, move |emitter, event, cx| {
cx.update_window(window_handle, |cx| callback(emitter, event, cx))
cx.update_window(window_id, |cx| callback(emitter, event, cx))
.unwrap_or(false)
})
}
@@ -317,17 +292,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(bool, &mut WindowContext) -> bool,
{
let handle = self.window_handle;
let window_id = self.window_id;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowActivationObservation {
window: handle,
window_id,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
.subscribe(handle, subscription_id),
.subscribe(window_id, subscription_id),
)
}
@@ -335,17 +310,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(bool, &mut WindowContext) -> bool,
{
let window = self.window_handle;
let window_id = self.window_id;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowFullscreenObservation {
window,
window_id,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowActivationObservation(
self.window_activation_observations
.subscribe(window, subscription_id),
.subscribe(window_id, subscription_id),
)
}
@@ -353,17 +328,17 @@ impl<'a> WindowContext<'a> {
where
F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
{
let window = self.window_handle;
let window_id = self.window_id;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.pending_effects
.push_back(Effect::WindowBoundsObservation {
window,
window_id,
subscription_id,
callback: Box::new(callback),
});
Subscription::WindowBoundsObservation(
self.window_bounds_observations
.subscribe(window, subscription_id),
.subscribe(window_id, subscription_id),
)
}
@@ -372,13 +347,13 @@ impl<'a> WindowContext<'a> {
F: 'static
+ FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
{
let window = self.window_handle;
let window_id = self.window_id;
let subscription_id = post_inc(&mut self.next_subscription_id);
self.keystroke_observations
.add_callback(window, subscription_id, Box::new(callback));
.add_callback(window_id, subscription_id, Box::new(callback));
Subscription::KeystrokeObservation(
self.keystroke_observations
.subscribe(window, subscription_id),
.subscribe(window_id, subscription_id),
)
}
@@ -386,11 +361,11 @@ impl<'a> WindowContext<'a> {
&self,
view_id: usize,
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
let handle = self.window_handle;
let window_id = self.window_id;
let mut contexts = Vec::new();
let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
for (depth, view_id) in self.ancestors(view_id).enumerate() {
if let Some(view_metadata) = self.views_metadata.get(&(handle, view_id)) {
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
contexts.push(view_metadata.keymap_context.clone());
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
handler_depths_by_action_id
@@ -435,13 +410,13 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let handle = self.window_handle;
let window_id = self.window_id;
if let Some(focused_view_id) = self.focused_view_id() {
let dispatch_path = self
.ancestors(focused_view_id)
.filter_map(|view_id| {
self.views_metadata
.get(&(handle, view_id))
.get(&(window_id, view_id))
.map(|view| (view_id, view.keymap_context.clone()))
})
.collect();
@@ -466,10 +441,15 @@ impl<'a> WindowContext<'a> {
}
};
self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
self.keystroke(
window_id,
keystroke.clone(),
handled_by,
match_result.clone(),
);
keystroke_handled
} else {
self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
false
}
}
@@ -477,7 +457,7 @@ impl<'a> WindowContext<'a> {
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
let mut mouse_events = SmallVec::<[_; 2]>::new();
let mut notified_views: HashSet<usize> = Default::default();
let handle = self.window_handle;
let window_id = self.window_id;
// 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
// get mapped into the mouse-specific MouseEvent type.
@@ -678,7 +658,6 @@ impl<'a> WindowContext<'a> {
let mut highest_z_index = None;
let mouse_position = self.window.mouse_position.clone();
let window = &mut *self.window;
let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
for (region, z_index) in window.mouse_regions.iter().rev() {
// Allow mouse regions to appear transparent to hovers
if !region.hoverable {
@@ -697,11 +676,7 @@ impl<'a> WindowContext<'a> {
// highest_z_index is set.
if contains_mouse && z_index == highest_z_index.unwrap() {
//Ensure that hover entrance events aren't sent twice
if let Err(ix) = window.hovered_region_ids.binary_search(&region.id()) {
window.hovered_region_ids.insert(ix, region.id());
}
// window.hovered_region_ids.insert(region.id());
if !prev_hovered_regions.contains(&region.id()) {
if window.hovered_region_ids.insert(region.id()) {
valid_regions.push(region.clone());
if region.notify_on_hover {
notified_views.insert(region.id().view_id());
@@ -709,7 +684,7 @@ impl<'a> WindowContext<'a> {
}
} else {
// Ensure that hover exit events aren't sent twice
if prev_hovered_regions.contains(&region.id()) {
if window.hovered_region_ids.remove(&region.id()) {
valid_regions.push(region.clone());
if region.notify_on_hover {
notified_views.insert(region.id().view_id());
@@ -846,19 +821,19 @@ impl<'a> WindowContext<'a> {
}
for view_id in notified_views {
self.notify_view(handle, view_id);
self.notify_view(window_id, view_id);
}
any_event_handled
}
pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
let handle = self.window_handle;
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
let handled = view.key_down(event, self, view_id);
self.views.insert((handle, view_id), view);
self.views.insert((window_id, view_id), view);
if handled {
return true;
}
@@ -872,12 +847,12 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
let handle = self.window_handle;
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
let handled = view.key_up(event, self, view_id);
self.views.insert((handle, view_id), view);
self.views.insert((window_id, view_id), view);
if handled {
return true;
}
@@ -891,12 +866,12 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
let handle = self.window_handle;
let window_id = self.window_id;
if let Some(focused_view_id) = self.window.focused_view_id {
for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
if let Some(mut view) = self.views.remove(&(handle, view_id)) {
if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
let handled = view.modifiers_changed(event, self, view_id);
self.views.insert((handle, view_id), view);
self.views.insert((window_id, view_id), view);
if handled {
return true;
}
@@ -931,14 +906,14 @@ impl<'a> WindowContext<'a> {
}
pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
let handle = self.window_handle;
let window_id = self.window_id;
let view_id = params.view_id;
let mut view = self
.views
.remove(&(handle, view_id))
.remove(&(window_id, view_id))
.ok_or_else(|| anyhow!("view not found"))?;
let element = view.render(self, view_id);
self.views.insert((handle, view_id), view);
self.views.insert((window_id, view_id), view);
Ok(element)
}
@@ -966,9 +941,9 @@ impl<'a> WindowContext<'a> {
} else if old_parent_id == new_parent_id {
current_view_id = *old_parent_id.unwrap();
} else {
let handle = self.window_handle;
let window_id = self.window_id;
for view_id_to_notify in view_ids_to_notify {
self.notify_view(handle, view_id_to_notify);
self.notify_view(window_id, view_id_to_notify);
}
break;
}
@@ -1136,7 +1111,7 @@ impl<'a> WindowContext<'a> {
}
pub fn focus(&mut self, view_id: Option<usize>) {
self.app_context.focus(self.window_handle, view_id);
self.app_context.focus(self.window_id, view_id);
}
pub fn window_bounds(&self) -> WindowBounds {
@@ -1176,6 +1151,17 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
where
V: View,
F: FnOnce(&mut ViewContext<V>) -> V,
{
let root_view = self.add_view(|cx| build_root_view(cx));
self.window.root_view = Some(root_view.clone().into_any());
self.window.focused_view_id = Some(root_view.id());
root_view
}
pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
where
T: View,
@@ -1189,26 +1175,26 @@ impl<'a> WindowContext<'a> {
T: View,
F: FnOnce(&mut ViewContext<T>) -> Option<T>,
{
let handle = self.window_handle;
let view_id = post_inc(&mut self.next_id);
let window_id = self.window_id;
let view_id = post_inc(&mut self.next_entity_id);
let mut cx = ViewContext::mutable(self, view_id);
let handle = if let Some(view) = build_view(&mut cx) {
let mut keymap_context = KeymapContext::default();
view.update_keymap_context(&mut keymap_context, cx.app_context());
self.views_metadata.insert(
(handle, view_id),
(window_id, view_id),
ViewMetadata {
type_id: TypeId::of::<T>(),
keymap_context,
},
);
self.views.insert((handle, view_id), Box::new(view));
self.views.insert((window_id, view_id), Box::new(view));
self.window
.invalidation
.get_or_insert_with(Default::default)
.updated
.insert(view_id);
Some(ViewHandle::new(handle, view_id, &self.ref_counts))
Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
} else {
None
};
@@ -1385,7 +1371,7 @@ pub struct ChildView {
impl ChildView {
pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
let view_name = cx.view_ui_name(view.window, view.id()).unwrap();
let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
Self {
view_id: view.id(),
view_name,
@@ -1434,7 +1420,7 @@ impl<V: View> Element<V> for ChildView {
visible_bounds: RectF,
_: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
rendered_view

View File

@@ -2,11 +2,11 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
use pathfinder_geometry::rect::RectF;
use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
pub struct WindowInputHandler {
pub app: Rc<RefCell<AppContext>>,
pub window: AnyWindowHandle,
pub window_id: usize,
}
impl WindowInputHandler {
@@ -21,12 +21,13 @@ impl WindowInputHandler {
//
// See https://github.com/zed-industries/community/issues/444
let mut app = self.app.try_borrow_mut().ok()?;
self.window.update_optional(&mut *app, |cx| {
app.update_window(self.window_id, |cx| {
let view_id = cx.window.focused_view_id?;
let view = cx.views.get(&(self.window, view_id))?;
let view = cx.views.get(&(self.window_id, view_id))?;
let result = f(view.as_ref(), &cx);
Some(result)
})
.flatten()
}
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
@@ -34,12 +35,11 @@ impl WindowInputHandler {
F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
{
let mut app = self.app.try_borrow_mut().ok()?;
self.window
.update(&mut *app, |cx| {
let view_id = cx.window.focused_view_id?;
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
.flatten()
app.update_window(self.window_id, |cx| {
let view_id = cx.window.focused_view_id?;
cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
})
.flatten()
}
}
@@ -83,8 +83,9 @@ impl InputHandler for WindowInputHandler {
}
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
self.window.read_optional_with(&*self.app.borrow(), |cx| {
cx.rect_for_text_range(range_utf16)
})
self.app
.borrow()
.read_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
.flatten()
}
}

View File

@@ -33,8 +33,8 @@ use crate::{
rect::RectF,
vector::{vec2f, Vector2F},
},
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
WeakViewHandle, WindowContext,
json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
WindowContext,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -61,7 +61,7 @@ pub trait Element<V: View>: 'static {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState;
fn rect_for_text_range(
@@ -298,14 +298,7 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
mut layout,
} => {
let bounds = RectF::new(origin, size);
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@@ -323,14 +316,7 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
..
} => {
let bounds = RectF::new(origin, bounds.size());
let paint = element.paint(
scene,
bounds,
visible_bounds,
&mut layout,
view,
&mut PaintContext::new(cx),
);
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
ElementState::PostPaint {
element,
constraint,
@@ -527,7 +513,7 @@ impl<V: View> Element<V> for AnyElement<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
}

View File

@@ -1,7 +1,6 @@
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use json::ToJson;
@@ -70,7 +69,7 @@ impl<V: View> Element<V> for Align<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let my_center = bounds.size() / 2.;
let my_target = my_center + my_center * self.alignment;

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Element;
use crate::{
json::{self, json},
PaintContext, SceneBuilder, View, ViewContext,
SceneBuilder, View, ViewContext,
};
use json::ToJson;
use pathfinder_geometry::{
@@ -56,7 +56,7 @@ where
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.0(scene, bounds, visible_bounds, view, cx)
}

View File

@@ -4,8 +4,7 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
use serde_json::json;
use crate::{
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct Clipped<V: View> {
@@ -38,7 +37,7 @@ impl<V: View> Element<V> for Clipped<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(bounds), |scene| {
self.child

View File

@@ -5,8 +5,7 @@ use serde_json::json;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct ConstrainedBox<V: View> {
@@ -157,7 +156,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.paint_layer(Some(visible_bounds), |scene| {
self.child

View File

@@ -10,8 +10,7 @@ use crate::{
json::ToJson,
platform::CursorStyle,
scene::{self, Border, CursorRegion, Quad},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -215,7 +214,7 @@ impl<V: View> Element<V> for Container<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let quad_bounds = RectF::from_points(
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),

View File

@@ -6,7 +6,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
LayoutContext, SceneBuilder, View, ViewContext,
};
use crate::{Element, SizeConstraint};
@@ -57,7 +57,7 @@ impl<V: View> Element<V> for Empty {
_: RectF,
_: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) -> Self::PaintState {
}

View File

@@ -2,8 +2,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use serde_json::json;
@@ -62,7 +61,7 @@ impl<V: View> Element<V> for Expanded<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
use crate::{
json::{self, ToJson, Value},
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
SizeConstraint, Vector2FExt, View, ViewContext,
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
Vector2FExt, View, ViewContext,
};
use pathfinder_geometry::{
rect::RectF,
@@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
visible_bounds: RectF,
remaining_space: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
@@ -449,7 +449,7 @@ impl<V: View> Element<V> for FlexItem<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx)

View File

@@ -3,8 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
pub struct Hook<V: View> {
@@ -53,7 +52,7 @@ impl<V: View> Element<V> for Hook<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@@ -5,8 +5,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::{json, ToJson},
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -97,7 +97,7 @@ impl<V: View> Element<V> for Image {
_: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) -> Self::PaintState {
if let Some(data) = layout {
scene.push_image(scene::Image {

View File

@@ -66,7 +66,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
visible_bounds: RectF,
element: &mut AnyElement<V>,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
}

View File

@@ -8,7 +8,7 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle},
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -163,7 +163,7 @@ impl<V: View> Element<V> for Label {
visible_bounds: RectF,
line: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(

View File

@@ -4,8 +4,8 @@ use crate::{
vector::{vec2f, Vector2F},
},
json::json,
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
View, ViewContext,
AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
@@ -255,7 +255,7 @@ impl<V: View> Element<V> for List<V> {
visible_bounds: RectF,
scroll_top: &mut ListOffset,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
scene.push_layer(Some(visible_bounds));
@@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
#[cfg(test)]
mod tests {
use super::*;
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
use crate::{elements::Empty, geometry::vector::vec2f, Entity};
use rand::prelude::*;
use std::env;
@@ -988,7 +988,7 @@ mod tests {
_: RectF,
_: &mut (),
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) {
unimplemented!()
}

View File

@@ -10,8 +10,8 @@ use crate::{
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
},
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
SceneBuilder, SizeConstraint, View, ViewContext,
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
SizeConstraint, View, ViewContext,
};
use serde_json::json;
use std::{marker::PhantomData, ops::Range};
@@ -256,7 +256,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
if self.above {
self.child

View File

@@ -3,8 +3,8 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::ToJson,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, View, ViewContext,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
};
use serde_json::json;
@@ -143,7 +143,7 @@ impl<V: View> Element<V> for Overlay<V> {
_: RectF,
size: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
let (anchor_position, mut bounds) = match self.position_mode {
OverlayPositionMode::Window => {

View File

@@ -7,8 +7,8 @@ use crate::{
geometry::rect::RectF,
platform::{CursorStyle, MouseButton},
scene::MouseDrag,
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
SizeConstraint, View, ViewContext,
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
ViewContext,
};
#[derive(Copy, Clone, Debug)]
@@ -125,7 +125,7 @@ impl<V: View> Element<V> for Resizable<V> {
visible_bounds: pathfinder_geometry::rect::RectF,
constraint: &mut SizeConstraint,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
scene.push_stacking_context(None, None);

View File

@@ -3,8 +3,7 @@ use std::ops::Range;
use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::{self, json, ToJson},
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
ViewContext,
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
};
/// Element which renders it's children in a stack on top of each other.
@@ -58,7 +57,7 @@ impl<V: View> Element<V> for Stack<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
for child in &mut self.children {
scene.paint_layer(None, |scene| {

View File

@@ -1,6 +1,5 @@
use super::constrain_size_preserving_aspect_ratio;
use crate::json::ToJson;
use crate::PaintContext;
use crate::{
color::Color,
geometry::{
@@ -74,7 +73,7 @@ impl<V: View> Element<V> for Svg {
_visible_bounds: RectF,
svg: &mut Self::LayoutState,
_: &mut V,
_: &mut PaintContext<V>,
_: &mut ViewContext<V>,
) {
if let Some(svg) = svg.clone() {
scene.push_icon(scene::Icon {

View File

@@ -7,8 +7,8 @@ use crate::{
},
json::{ToJson, Value},
text_layout::{Line, RunStyle, ShapedBoundary},
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
TextLayoutCache, View, ViewContext,
AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
View, ViewContext,
};
use log::warn;
use serde_json::json;
@@ -171,7 +171,7 @@ impl<V: View> Element<V> for Text {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
_: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let mut origin = bounds.origin();
let empty = Vec::new();

View File

@@ -6,8 +6,8 @@ use crate::{
fonts::TextStyle,
geometry::{rect::RectF, vector::Vector2F},
json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, View, ViewContext,
Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
ViewContext,
};
use schemars::JsonSchema;
use serde::Deserialize;
@@ -194,7 +194,7 @@ impl<V: View> Element<V> for Tooltip<V> {
visible_bounds: RectF,
_: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) {
self.child
.paint(scene, bounds.origin(), visible_bounds, view, cx);

View File

@@ -6,7 +6,7 @@ use crate::{
},
json::{self, json},
platform::ScrollWheelEvent,
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
};
use json::ToJson;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -278,7 +278,7 @@ impl<V: View> Element<V> for UniformList<V> {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut V,
cx: &mut PaintContext<V>,
cx: &mut ViewContext<V>,
) -> Self::PaintState {
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();

View File

@@ -71,32 +71,6 @@ pub struct TextStyle {
pub underline: Underline,
}
impl TextStyle {
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
TextStyle {
color: refinement.color.unwrap_or(self.color),
font_family_name: refinement
.font_family_name
.unwrap_or_else(|| self.font_family_name.clone()),
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
font_id: refinement.font_id.unwrap_or(self.font_id),
font_size: refinement.font_size.unwrap_or(self.font_size),
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
underline: refinement.underline.unwrap_or(self.underline),
}
}
}
pub struct TextStyleRefinement {
pub color: Option<Color>,
pub font_family_name: Option<Arc<str>>,
pub font_family_id: Option<FamilyId>,
pub font_id: Option<FontId>,
pub font_size: Option<f32>,
pub font_properties: Option<Properties>,
pub underline: Option<Underline>,
}
#[derive(JsonSchema)]
#[serde(remote = "Properties")]
pub struct PropertiesDef {

View File

@@ -19,7 +19,7 @@ use crate::{
},
keymap_matcher::KeymapMatcher,
text_layout::{LineLayout, RunStyle},
Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
Action, ClipboardItem, Menu, Scene,
};
use anyhow::{anyhow, bail, Result};
use async_task::Runnable;
@@ -58,13 +58,13 @@ pub trait Platform: Send + Sync {
fn open_window(
&self,
handle: AnyWindowHandle,
id: usize,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> Box<dyn Window>;
fn main_window(&self) -> Option<AnyWindowHandle>;
fn main_window_id(&self) -> Option<usize>;
fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn Window>;
fn add_status_item(&self, id: usize) -> Box<dyn Window>;
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;

View File

@@ -21,7 +21,7 @@ pub use fonts::FontSystem;
use platform::{MacForegroundPlatform, MacPlatform};
pub use renderer::Surface;
use std::{ops::Range, rc::Rc, sync::Arc};
use window::MacWindow;
use window::Window;
use crate::executor;

View File

@@ -1,12 +1,12 @@
use super::{
event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
FontSystem, MacWindow,
FontSystem, Window,
};
use crate::{
executor,
keymap_matcher::KeymapMatcher,
platform::{self, AppVersion, CursorStyle, Event},
Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
Action, ClipboardItem, Menu, MenuItem,
};
use anyhow::{anyhow, Result};
use block::ConcreteBlock;
@@ -590,18 +590,18 @@ impl platform::Platform for MacPlatform {
fn open_window(
&self,
handle: AnyWindowHandle,
id: usize,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> Box<dyn platform::Window> {
Box::new(MacWindow::open(handle, options, executor, self.fonts()))
Box::new(Window::open(id, options, executor, self.fonts()))
}
fn main_window(&self) -> Option<AnyWindowHandle> {
MacWindow::main_window()
fn main_window_id(&self) -> Option<usize> {
Window::main_window_id()
}
fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
Box::new(StatusItem::add(self.fonts()))
}

View File

@@ -13,7 +13,6 @@ use crate::{
Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
},
AnyWindowHandle,
};
use block::ConcreteBlock;
use cocoa::{
@@ -283,7 +282,7 @@ struct InsertText {
}
struct WindowState {
handle: AnyWindowHandle,
id: usize,
native_window: id,
kind: WindowKind,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
@@ -423,11 +422,11 @@ impl WindowState {
}
}
pub struct MacWindow(Rc<RefCell<WindowState>>);
pub struct Window(Rc<RefCell<WindowState>>);
impl MacWindow {
impl Window {
pub fn open(
handle: AnyWindowHandle,
id: usize,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
fonts: Arc<dyn platform::FontSystem>,
@@ -505,7 +504,7 @@ impl MacWindow {
assert!(!native_view.is_null());
let window = Self(Rc::new(RefCell::new(WindowState {
handle,
id,
native_window,
kind: options.kind,
event_callback: None,
@@ -622,13 +621,13 @@ impl MacWindow {
}
}
pub fn main_window() -> Option<AnyWindowHandle> {
pub fn main_window_id() -> Option<usize> {
unsafe {
let app = NSApplication::sharedApplication(nil);
let main_window: id = msg_send![app, mainWindow];
if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
let handle = get_window_state(&*main_window).borrow().handle;
Some(handle)
let id = get_window_state(&*main_window).borrow().id;
Some(id)
} else {
None
}
@@ -636,7 +635,7 @@ impl MacWindow {
}
}
impl Drop for MacWindow {
impl Drop for Window {
fn drop(&mut self) {
let this = self.0.borrow();
let window = this.native_window;
@@ -650,7 +649,7 @@ impl Drop for MacWindow {
}
}
impl platform::Window for MacWindow {
impl platform::Window for Window {
fn bounds(&self) -> WindowBounds {
self.0.as_ref().borrow().bounds()
}
@@ -882,7 +881,7 @@ impl platform::Window for MacWindow {
fn is_topmost_for_position(&self, position: Vector2F) -> bool {
let self_borrow = self.0.borrow();
let self_handle = self_borrow.handle;
let self_id = self_borrow.id;
unsafe {
let app = NSApplication::sharedApplication(nil);
@@ -899,8 +898,8 @@ impl platform::Window for MacWindow {
let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
if is_panel == YES || is_window == YES {
let topmost_window = get_window_state(&*top_most_window).borrow().handle;
topmost_window == self_handle
let topmost_window_id = get_window_state(&*top_most_window).borrow().id;
topmost_window_id == self_id
} else {
// Someone else's window is on top
false
@@ -1087,10 +1086,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
button: MouseButton::Left,
modifiers: Modifiers { ctrl: true, .. },
..
}) => {
window_state_borrow.synthetic_drag_counter += 1;
return;
}
}) => return,
_ => None,
};

View File

@@ -5,7 +5,7 @@ use crate::{
vector::{vec2f, Vector2F},
},
keymap_matcher::KeymapMatcher,
Action, AnyWindowHandle, ClipboardItem, Menu,
Action, ClipboardItem, Menu,
};
use anyhow::{anyhow, Result};
use collections::VecDeque;
@@ -102,7 +102,7 @@ pub struct Platform {
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: Mutex<Option<ClipboardItem>>,
cursor: Mutex<CursorStyle>,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
active_window_id: Arc<Mutex<Option<usize>>>,
}
impl Platform {
@@ -112,7 +112,7 @@ impl Platform {
fonts: Arc::new(super::current::FontSystem::new()),
current_clipboard_item: Default::default(),
cursor: Mutex::new(CursorStyle::Arrow),
active_window: Default::default(),
active_window_id: Default::default(),
}
}
}
@@ -146,30 +146,30 @@ impl super::Platform for Platform {
fn open_window(
&self,
handle: AnyWindowHandle,
id: usize,
options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>,
) -> Box<dyn super::Window> {
*self.active_window.lock() = Some(handle);
*self.active_window_id.lock() = Some(id);
Box::new(Window::new(
handle,
id,
match options.bounds {
WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
WindowBounds::Fixed(rect) => rect.size(),
},
self.active_window.clone(),
self.active_window_id.clone(),
))
}
fn main_window(&self) -> Option<AnyWindowHandle> {
self.active_window.lock().clone()
fn main_window_id(&self) -> Option<usize> {
self.active_window_id.lock().clone()
}
fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> {
fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
Box::new(Window::new(
handle,
id,
vec2f(24., 24.),
self.active_window.clone(),
self.active_window_id.clone(),
))
}
@@ -256,7 +256,7 @@ impl super::Screen for Screen {
}
pub struct Window {
handle: AnyWindowHandle,
id: usize,
pub(crate) size: Vector2F,
scale_factor: f32,
current_scene: Option<crate::Scene>,
@@ -270,17 +270,13 @@ pub struct Window {
pub(crate) title: Option<String>,
pub(crate) edited: bool,
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
active_window_id: Arc<Mutex<Option<usize>>>,
}
impl Window {
pub fn new(
handle: AnyWindowHandle,
size: Vector2F,
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
) -> Self {
pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
Self {
handle,
id,
size,
event_handlers: Default::default(),
resize_handlers: Default::default(),
@@ -294,7 +290,7 @@ impl Window {
title: None,
edited: false,
pending_prompts: Default::default(),
active_window,
active_window_id,
}
}
@@ -346,7 +342,7 @@ impl super::Window for Window {
}
fn activate(&self) {
*self.active_window.lock() = Some(self.handle);
*self.active_window_id.lock() = Some(self.id);
}
fn set_title(&mut self, title: &str) {

View File

@@ -177,7 +177,7 @@ impl MouseRegion {
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct MouseRegionId {
view_id: usize,
tag: TypeId,

View File

@@ -45,7 +45,7 @@ use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query};
use unicase::UniCase;
use util::{http::HttpClient, paths::PathExt};
use util::http::HttpClient;
use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))]
@@ -777,7 +777,7 @@ impl LanguageRegistry {
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name();
let extension = path.extension().and_then(|name| name.to_str());
let path_suffixes = [extension, filename];
self.get_or_load_language(|config| {
let path_matches = config

View File

@@ -61,9 +61,7 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
.receive_notification::<lsp::notification::DidOpenTextDocument>()
.await;
let log_view = cx
.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
.root(cx);
let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
message: "hello from the server".into(),

View File

@@ -58,14 +58,11 @@ fn build_bridge(swift_target: &SwiftTarget) {
"cargo:rerun-if-changed={}/Package.resolved",
SWIFT_PACKAGE_NAME
);
let swift_package_root = swift_package_root();
let swift_target_folder = swift_target_folder();
if !Command::new("swift")
.arg("build")
.args(["--configuration", &env::var("PROFILE").unwrap()])
.args(["--triple", &swift_target.target.triple])
.args(["--build-path".into(), swift_target_folder])
.current_dir(&swift_package_root)
.status()
.unwrap()
@@ -131,12 +128,6 @@ fn swift_package_root() -> PathBuf {
env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME)
}
fn swift_target_folder() -> PathBuf {
env::current_dir()
.unwrap()
.join(format!("../../target/{SWIFT_PACKAGE_NAME}"))
}
fn copy_dir(source: &Path, destination: &Path) {
assert!(
Command::new("rm")
@@ -164,7 +155,8 @@ fn copy_dir(source: &Path, destination: &Path) {
impl SwiftTarget {
fn out_dir_path(&self) -> PathBuf {
swift_target_folder()
swift_package_root()
.join(".build")
.join(&self.target.unversioned_triple)
.join(env::var("PROFILE").unwrap())
}

View File

@@ -1,6 +1,9 @@
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use futures::lock::Mutex;
use futures::{future::Shared, FutureExt};
use gpui::{executor::Background, Task};
use serde::Deserialize;
use smol::{fs, io::BufReader, process::Command};
use std::process::{Output, Stdio};
@@ -30,12 +33,20 @@ pub struct NpmInfoDistTags {
pub struct NodeRuntime {
http: Arc<dyn HttpClient>,
background: Arc<Background>,
installation_path: Mutex<Option<Shared<Task<Result<PathBuf, Arc<anyhow::Error>>>>>>,
}
impl NodeRuntime {
pub fn instance(http: Arc<dyn HttpClient>) -> Arc<NodeRuntime> {
pub fn instance(http: Arc<dyn HttpClient>, background: Arc<Background>) -> Arc<NodeRuntime> {
RUNTIME_INSTANCE
.get_or_init(|| Arc::new(NodeRuntime { http }))
.get_or_init(|| {
Arc::new(NodeRuntime {
http,
background,
installation_path: Mutex::new(None),
})
})
.clone()
}
@@ -50,9 +61,7 @@ impl NodeRuntime {
subcommand: &str,
args: &[&str],
) -> Result<Output> {
let attempt = || async move {
let installation_path = self.install_if_needed().await?;
let attempt = |installation_path: PathBuf| async move {
let mut env_path = installation_path.join("bin").into_os_string();
if let Some(existing_path) = std::env::var_os("PATH") {
if !existing_path.is_empty() {
@@ -83,9 +92,10 @@ impl NodeRuntime {
command.output().await.map_err(|e| anyhow!("{e}"))
};
let mut output = attempt().await;
let installation_path = self.install_if_needed().await?;
let mut output = attempt(installation_path.clone()).await;
if output.is_err() {
output = attempt().await;
output = attempt(installation_path).await;
if output.is_err() {
return Err(anyhow!(
"failed to launch npm subcommand {subcommand} subcommand"
@@ -157,8 +167,23 @@ impl NodeRuntime {
}
async fn install_if_needed(&self) -> Result<PathBuf> {
log::info!("Node runtime install_if_needed");
let task = self
.installation_path
.lock()
.await
.get_or_insert_with(|| {
let http = self.http.clone();
self.background
.spawn(async move { Self::install(http).await.map_err(Arc::new) })
.shared()
})
.clone();
task.await.map_err(|e| anyhow!("{}", e))
}
async fn install(http: Arc<dyn HttpClient>) -> Result<PathBuf> {
log::info!("installing Node runtime");
let arch = match consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
@@ -189,8 +214,7 @@ impl NodeRuntime {
let file_name = format!("node-{VERSION}-darwin-{arch}.tar.gz");
let url = format!("https://nodejs.org/dist/{VERSION}/{file_name}");
let mut response = self
.http
let mut response = http
.get(&url, Default::default(), true)
.await
.context("error downloading Node binary tarball")?;

View File

@@ -1,6 +1,7 @@
use crate::{search::PathMatcher, worktree::WorktreeHandle, Event, *};
use crate::{worktree::WorktreeHandle, Event, *};
use fs::{FakeFs, LineEnding, RealFs};
use futures::{future, StreamExt};
use globset::Glob;
use gpui::{executor::Deterministic, test::subscribe, AppContext};
use language::{
language_settings::{AllLanguageSettings, LanguageSettingsContent},
@@ -3640,7 +3641,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
search_query,
false,
true,
vec![PathMatcher::new("*.odd").unwrap()],
vec![Glob::new("*.odd").unwrap().compile_matcher()],
Vec::new()
),
cx
@@ -3658,7 +3659,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
search_query,
false,
true,
vec![PathMatcher::new("*.rs").unwrap()],
vec![Glob::new("*.rs").unwrap().compile_matcher()],
Vec::new()
),
cx
@@ -3680,8 +3681,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
false,
true,
vec![
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap(),
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher(),
],
Vec::new()
),
@@ -3704,9 +3705,9 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
false,
true,
vec![
PathMatcher::new("*.rs").unwrap(),
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap(),
Glob::new("*.rs").unwrap().compile_matcher(),
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher(),
],
Vec::new()
),
@@ -3751,7 +3752,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
false,
true,
Vec::new(),
vec![PathMatcher::new("*.odd").unwrap()],
vec![Glob::new("*.odd").unwrap().compile_matcher()],
),
cx
)
@@ -3774,7 +3775,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
false,
true,
Vec::new(),
vec![PathMatcher::new("*.rs").unwrap()],
vec![Glob::new("*.rs").unwrap().compile_matcher()],
),
cx
)
@@ -3796,8 +3797,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
true,
Vec::new(),
vec![
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap(),
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher(),
],
),
cx
@@ -3820,9 +3821,9 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
true,
Vec::new(),
vec![
PathMatcher::new("*.rs").unwrap(),
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap(),
Glob::new("*.rs").unwrap().compile_matcher(),
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher(),
],
),
cx
@@ -3859,8 +3860,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
search_query,
false,
true,
vec![PathMatcher::new("*.odd").unwrap()],
vec![PathMatcher::new("*.odd").unwrap()],
vec![Glob::new("*.odd").unwrap().compile_matcher()],
vec![Glob::new("*.odd").unwrap().compile_matcher()],
),
cx
)
@@ -3877,8 +3878,8 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
search_query,
false,
true,
vec![PathMatcher::new("*.ts").unwrap()],
vec![PathMatcher::new("*.ts").unwrap()],
vec![Glob::new("*.ts").unwrap().compile_matcher()],
vec![Glob::new("*.ts").unwrap().compile_matcher()],
),
cx
)
@@ -3896,12 +3897,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
false,
true,
vec![
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap()
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher()
],
vec![
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap()
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher()
],
),
cx
@@ -3920,12 +3921,12 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
false,
true,
vec![
PathMatcher::new("*.ts").unwrap(),
PathMatcher::new("*.odd").unwrap()
Glob::new("*.ts").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher()
],
vec![
PathMatcher::new("*.rs").unwrap(),
PathMatcher::new("*.odd").unwrap()
Glob::new("*.rs").unwrap().compile_matcher(),
Glob::new("*.odd").unwrap().compile_matcher()
],
),
cx

View File

@@ -1,5 +1,5 @@
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
use anyhow::{Context, Result};
use anyhow::Result;
use client::proto;
use globset::{Glob, GlobMatcher};
use itertools::Itertools;
@@ -9,7 +9,7 @@ use smol::future::yield_now;
use std::{
io::{BufRead, BufReader, Read},
ops::Range,
path::{Path, PathBuf},
path::Path,
sync::Arc,
};
@@ -20,8 +20,8 @@ pub enum SearchQuery {
query: Arc<str>,
whole_word: bool,
case_sensitive: bool,
files_to_include: Vec<PathMatcher>,
files_to_exclude: Vec<PathMatcher>,
files_to_include: Vec<GlobMatcher>,
files_to_exclude: Vec<GlobMatcher>,
},
Regex {
regex: Regex,
@@ -29,43 +29,18 @@ pub enum SearchQuery {
multiline: bool,
whole_word: bool,
case_sensitive: bool,
files_to_include: Vec<PathMatcher>,
files_to_exclude: Vec<PathMatcher>,
files_to_include: Vec<GlobMatcher>,
files_to_exclude: Vec<GlobMatcher>,
},
}
#[derive(Clone, Debug)]
pub struct PathMatcher {
maybe_path: PathBuf,
glob: GlobMatcher,
}
impl std::fmt::Display for PathMatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.maybe_path.to_string_lossy().fmt(f)
}
}
impl PathMatcher {
pub fn new(maybe_glob: &str) -> Result<Self, globset::Error> {
Ok(PathMatcher {
glob: Glob::new(&maybe_glob)?.compile_matcher(),
maybe_path: PathBuf::from(maybe_glob),
})
}
pub fn is_match<P: AsRef<Path>>(&self, other: P) -> bool {
other.as_ref().starts_with(&self.maybe_path) || self.glob.is_match(other)
}
}
impl SearchQuery {
pub fn text(
query: impl ToString,
whole_word: bool,
case_sensitive: bool,
files_to_include: Vec<PathMatcher>,
files_to_exclude: Vec<PathMatcher>,
files_to_include: Vec<GlobMatcher>,
files_to_exclude: Vec<GlobMatcher>,
) -> Self {
let query = query.to_string();
let search = AhoCorasickBuilder::new()
@@ -86,8 +61,8 @@ impl SearchQuery {
query: impl ToString,
whole_word: bool,
case_sensitive: bool,
files_to_include: Vec<PathMatcher>,
files_to_exclude: Vec<PathMatcher>,
files_to_include: Vec<GlobMatcher>,
files_to_exclude: Vec<GlobMatcher>,
) -> Result<Self> {
let mut query = query.to_string();
let initial_query = Arc::from(query.as_str());
@@ -121,16 +96,16 @@ impl SearchQuery {
message.query,
message.whole_word,
message.case_sensitive,
deserialize_path_matches(&message.files_to_include)?,
deserialize_path_matches(&message.files_to_exclude)?,
deserialize_globs(&message.files_to_include)?,
deserialize_globs(&message.files_to_exclude)?,
)
} else {
Ok(Self::text(
message.query,
message.whole_word,
message.case_sensitive,
deserialize_path_matches(&message.files_to_include)?,
deserialize_path_matches(&message.files_to_exclude)?,
deserialize_globs(&message.files_to_include)?,
deserialize_globs(&message.files_to_exclude)?,
))
}
}
@@ -145,12 +120,12 @@ impl SearchQuery {
files_to_include: self
.files_to_include()
.iter()
.map(|matcher| matcher.to_string())
.map(|g| g.glob().to_string())
.join(","),
files_to_exclude: self
.files_to_exclude()
.iter()
.map(|matcher| matcher.to_string())
.map(|g| g.glob().to_string())
.join(","),
}
}
@@ -291,7 +266,7 @@ impl SearchQuery {
matches!(self, Self::Regex { .. })
}
pub fn files_to_include(&self) -> &[PathMatcher] {
pub fn files_to_include(&self) -> &[GlobMatcher] {
match self {
Self::Text {
files_to_include, ..
@@ -302,7 +277,7 @@ impl SearchQuery {
}
}
pub fn files_to_exclude(&self) -> &[PathMatcher] {
pub fn files_to_exclude(&self) -> &[GlobMatcher] {
match self {
Self::Text {
files_to_exclude, ..
@@ -331,63 +306,11 @@ impl SearchQuery {
}
}
fn deserialize_path_matches(glob_set: &str) -> anyhow::Result<Vec<PathMatcher>> {
fn deserialize_globs(glob_set: &str) -> Result<Vec<GlobMatcher>> {
glob_set
.split(',')
.map(str::trim)
.filter(|glob_str| !glob_str.is_empty())
.map(|glob_str| {
PathMatcher::new(glob_str)
.with_context(|| format!("deserializing path match glob {glob_str}"))
})
.map(|glob_str| Ok(Glob::new(glob_str)?.compile_matcher()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_matcher_creation_for_valid_paths() {
for valid_path in [
"file",
"Cargo.toml",
".DS_Store",
"~/dir/another_dir/",
"./dir/file",
"dir/[a-z].txt",
"../dir/filé",
] {
let path_matcher = PathMatcher::new(valid_path).unwrap_or_else(|e| {
panic!("Valid path {valid_path} should be accepted, but got: {e}")
});
assert!(
path_matcher.is_match(valid_path),
"Path matcher for valid path {valid_path} should match itself"
)
}
}
#[test]
fn path_matcher_creation_for_globs() {
for invalid_glob in ["dir/[].txt", "dir/[a-z.txt", "dir/{file"] {
match PathMatcher::new(invalid_glob) {
Ok(_) => panic!("Invalid glob {invalid_glob} should not be accepted"),
Err(_expected) => {}
}
}
for valid_glob in [
"dir/?ile",
"dir/*.txt",
"dir/**/file",
"dir/[a-z].txt",
"{dir,file}",
] {
match PathMatcher::new(valid_glob) {
Ok(_expected) => {}
Err(e) => panic!("Valid glob {valid_glob} should be accepted, but got: {e}"),
}
}
}
}

View File

@@ -1,5 +1,5 @@
use crate::Project;
use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
use gpui::{ModelContext, ModelHandle, WeakModelHandle};
use std::path::PathBuf;
use terminal::{Terminal, TerminalBuilder, TerminalSettings};
@@ -11,7 +11,7 @@ impl Project {
pub fn create_terminal(
&mut self,
working_directory: Option<PathBuf>,
window: AnyWindowHandle,
window_id: usize,
cx: &mut ModelContext<Self>,
) -> anyhow::Result<ModelHandle<Terminal>> {
if self.is_remote() {
@@ -27,7 +27,7 @@ impl Project {
settings.env.clone(),
Some(settings.blinking.clone()),
settings.alternate_scroll,
window,
window_id,
)
.map(|builder| {
let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));

View File

@@ -2369,7 +2369,7 @@ impl BackgroundScannerState {
}
// Remove any git repositories whose .git entry no longer exists.
let snapshot = &mut self.snapshot;
let mut snapshot = &mut self.snapshot;
let mut repositories = mem::take(&mut snapshot.git_repositories);
let mut repository_entries = mem::take(&mut snapshot.repository_entries);
repositories.retain(|work_directory_id, _| {

View File

@@ -4,7 +4,7 @@ use collections::HashMap;
use gpui::{AppContext, AssetSource};
use serde_derive::Deserialize;
use util::{iife, paths::PathExt};
use util::iife;
#[derive(Deserialize, Debug)]
struct TypeConfig {
@@ -48,7 +48,14 @@ impl FileAssociations {
// FIXME: Associate a type with the languages and have the file's langauge
// override these associations
iife!({
let suffix = path.icon_suffix()?;
let suffix = path
.file_name()
.and_then(|os_str| os_str.to_str())
.and_then(|file_name| {
file_name
.find('.')
.and_then(|dot_index| file_name.get(dot_index + 1..))
})?;
this.suffixes
.get(suffix)

View File

@@ -115,7 +115,6 @@ actions!(
[
ExpandSelectedEntry,
CollapseSelectedEntry,
CollapseAllEntries,
NewDirectory,
NewFile,
Copy,
@@ -141,7 +140,6 @@ pub fn init(assets: impl AssetSource, cx: &mut AppContext) {
file_associations::init(assets, cx);
cx.add_action(ProjectPanel::expand_selected_entry);
cx.add_action(ProjectPanel::collapse_selected_entry);
cx.add_action(ProjectPanel::collapse_all_entries);
cx.add_action(ProjectPanel::select_prev);
cx.add_action(ProjectPanel::select_next);
cx.add_action(ProjectPanel::new_file);
@@ -432,7 +430,7 @@ impl ProjectPanel {
menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder));
if entry.is_dir() {
menu_entries.push(ContextMenuItem::action(
"Search Inside",
"Search inside",
NewSearchInDirectory,
));
}
@@ -516,12 +514,6 @@ impl ProjectPanel {
}
}
pub fn collapse_all_entries(&mut self, _: &CollapseAllEntries, cx: &mut ViewContext<Self>) {
self.expanded_dir_ids.clear();
self.update_visible_entries(None, cx);
cx.notify();
}
fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) {
if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
@@ -1415,7 +1407,7 @@ impl ProjectPanel {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window())
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
&& dragged_entry_destination
.as_ref()
@@ -1459,7 +1451,7 @@ impl ProjectPanel {
.on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window())
.currently_dragged::<ProjectEntryId>(cx.window_id())
{
this.move_entry(
*dragged_entry,
@@ -1472,7 +1464,7 @@ impl ProjectPanel {
.on_move(move |_, this, cx| {
if cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window())
.currently_dragged::<ProjectEntryId>(cx.window_id())
.is_some()
{
this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@@ -1726,7 +1718,7 @@ impl ClipboardEntry {
#[cfg(test)]
mod tests {
use super::*;
use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
use gpui::{TestAppContext, ViewHandle};
use pretty_assertions::assert_eq;
use project::FakeFs;
use serde_json::json;
@@ -1780,9 +1772,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
assert_eq!(
visible_entries_as_strings(&panel, 0..50, cx),
@@ -1870,8 +1860,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -1893,7 +1882,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2222,8 +2211,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "root1", cx);
@@ -2245,7 +2233,7 @@ mod tests {
// Add a file with the root folder selected. The filename editor is placed
// before the first file in the root folder.
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2323,9 +2311,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
@@ -2398,8 +2384,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
toggle_expand_dir(&panel, "src/test", cx);
@@ -2416,9 +2401,9 @@ mod tests {
" third.rs"
]
);
ensure_single_file_is_opened(window, "test/first.rs", cx);
ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx);
submit_deletion(window.into(), &panel, cx);
submit_deletion(window_id, &panel, cx);
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
@@ -2429,7 +2414,7 @@ mod tests {
],
"Project panel should have no deleted file, no other file is selected in it"
);
ensure_no_open_items_and_panes(window.into(), &workspace, cx);
ensure_no_open_items_and_panes(window_id, &workspace, cx);
select_path(&panel, "src/test/second.rs", cx);
panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@@ -2443,9 +2428,9 @@ mod tests {
" third.rs"
]
);
ensure_single_file_is_opened(window, "test/second.rs", cx);
ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx);
window.update(cx, |cx| {
cx.update_window(window_id, |cx| {
let active_items = workspace
.read(cx)
.panes()
@@ -2461,13 +2446,13 @@ mod tests {
.expect("Open item should be an editor");
open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx));
});
submit_deletion(window.into(), &panel, cx);
submit_deletion(window_id, &panel, cx);
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v src", " v test", " third.rs"],
"Project panel should have no deleted file, with one last file remaining"
);
ensure_no_open_items_and_panes(window.into(), &workspace, cx);
ensure_no_open_items_and_panes(window_id, &workspace, cx);
}
#[gpui::test]
@@ -2488,8 +2473,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
select_path(&panel, "src/", cx);
@@ -2500,7 +2484,7 @@ mod tests {
&["v src <== selected", " > test"]
);
panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2531,7 +2515,7 @@ mod tests {
&["v src", " > test <== selected"]
);
panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2581,7 +2565,7 @@ mod tests {
],
);
panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let panel = panel.read(cx);
assert!(panel.filename_editor.is_focused(cx));
});
@@ -2635,9 +2619,7 @@ mod tests {
.await;
let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
let new_search_events_count = Arc::new(AtomicUsize::new(0));
@@ -2696,65 +2678,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_collapse_all_entries(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/project_root",
json!({
"dir_1": {
"nested_dir": {
"file_a.py": "# File contents",
"file_b.py": "# File contents",
"file_c.py": "# File contents",
},
"file_1.py": "# File contents",
"file_2.py": "# File contents",
"file_3.py": "# File contents",
},
"dir_2": {
"file_1.py": "# File contents",
"file_2.py": "# File contents",
"file_3.py": "# File contents",
}
}),
)
.await;
let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
panel.update(cx, |panel, cx| {
panel.collapse_all_entries(&CollapseAllEntries, cx)
});
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&["v project_root", " > dir_1", " > dir_2",]
);
// Open dir_1 and make sure nested_dir was collapsed when running collapse_all_entries
toggle_expand_dir(&panel, "project_root/dir_1", cx);
cx.foreground().run_until_parked();
assert_eq!(
visible_entries_as_strings(&panel, 0..10, cx),
&[
"v project_root",
" v dir_1 <== selected",
" > nested_dir",
" file_1.py",
" file_2.py",
" file_3.py",
" > dir_2",
]
);
}
fn toggle_expand_dir(
panel: &ViewHandle<ProjectPanel>,
path: impl AsRef<Path>,
@@ -2878,11 +2801,13 @@ mod tests {
}
fn ensure_single_file_is_opened(
window: WindowHandle<Workspace>,
window_id: usize,
workspace: &ViewHandle<Workspace>,
expected_path: &str,
cx: &mut TestAppContext,
) {
window.update_root(cx, |workspace, cx| {
cx.read_window(window_id, |cx| {
let workspace = workspace.read(cx);
let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@@ -2904,12 +2829,12 @@ mod tests {
}
fn submit_deletion(
window: AnyWindowHandle,
window_id: usize,
panel: &ViewHandle<ProjectPanel>,
cx: &mut TestAppContext,
) {
assert!(
!window.has_pending_prompt(cx),
!cx.has_pending_prompt(window_id),
"Should have no prompts before the deletion"
);
panel.update(cx, |panel, cx| {
@@ -2919,27 +2844,27 @@ mod tests {
.detach_and_log_err(cx);
});
assert!(
window.has_pending_prompt(cx),
cx.has_pending_prompt(window_id),
"Should have a prompt after the deletion"
);
window.simulate_prompt_answer(0, cx);
cx.simulate_prompt_answer(window_id, 0);
assert!(
!window.has_pending_prompt(cx),
!cx.has_pending_prompt(window_id),
"Should have no prompts after prompt was replied to"
);
cx.foreground().run_until_parked();
}
fn ensure_no_open_items_and_panes(
window: AnyWindowHandle,
window_id: usize,
workspace: &ViewHandle<Workspace>,
cx: &mut TestAppContext,
) {
assert!(
!window.has_pending_prompt(cx),
!cx.has_pending_prompt(window_id),
"Should have no prompts after deletion operation closes the file"
);
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
let open_project_paths = workspace
.read(cx)
.panes()
@@ -2953,4 +2878,3 @@ mod tests {
});
}
}
// TODO - a workspace command?

View File

@@ -326,11 +326,10 @@ mod tests {
},
);
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Create the project symbols view.
let symbols = window.add_view(cx, |cx| {
let symbols = cx.add_view(window_id, |cx| {
ProjectSymbols::new(
ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
cx,

View File

@@ -5,7 +5,6 @@ use gpui::{
elements::{Label, LabelStyle},
AnyElement, Element, View,
};
use util::paths::PathExt;
use workspace::WorkspaceLocation;
pub struct HighlightedText {
@@ -62,7 +61,7 @@ impl HighlightedWorkspaceLocation {
.paths()
.iter()
.map(|path| {
let path = path.compact();
let path = util::paths::compact(&path);
let highlighted_text = Self::highlights_for_path(
path.as_ref(),
&string_match.positions,

View File

@@ -11,7 +11,6 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent};
use std::sync::Arc;
use util::paths::PathExt;
use workspace::{
notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
WORKSPACE_DB,
@@ -135,7 +134,7 @@ impl PickerDelegate for RecentProjectsDelegate {
let combined_string = location
.paths()
.iter()
.map(|path| path.compact().to_string_lossy().into_owned())
.map(|path| util::paths::compact(&path).to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("");
StringMatchCandidate::new(id, combined_string)

View File

@@ -1,6 +1,6 @@
use crate::{
NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectAllMatches,
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord,
SearchOptions, SelectAllMatches, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive,
ToggleRegex, ToggleWholeWord,
};
use collections::HashMap;
use editor::Editor;
@@ -46,8 +46,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(BufferSearchBar::select_prev_match_on_pane);
cx.add_action(BufferSearchBar::select_all_matches_on_pane);
cx.add_action(BufferSearchBar::handle_editor_cancel);
cx.add_action(BufferSearchBar::next_history_query);
cx.add_action(BufferSearchBar::previous_history_query);
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
add_toggle_option_action::<ToggleRegex>(SearchOptions::REGEX, cx);
@@ -67,7 +65,7 @@ fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContex
}
pub struct BufferSearchBar {
query_editor: ViewHandle<Editor>,
pub query_editor: ViewHandle<Editor>,
active_searchable_item: Option<Box<dyn SearchableItemHandle>>,
active_match_index: Option<usize>,
active_searchable_item_subscription: Option<Subscription>,
@@ -78,7 +76,6 @@ pub struct BufferSearchBar {
default_options: SearchOptions,
query_contains_error: bool,
dismissed: bool,
search_history: SearchHistory,
}
impl Entity for BufferSearchBar {
@@ -109,48 +106,6 @@ impl View for BufferSearchBar {
.map(|active_searchable_item| active_searchable_item.supported_options())
.unwrap_or_default();
let previous_query_keystrokes =
cx.binding_for_action(&PreviousHistoryQuery {})
.map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let next_query_keystrokes = cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
(Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
format!(
"Search ({}/{} for previous/next query)",
previous_query_keystrokes.join(" "),
next_query_keystrokes.join(" ")
)
}
(None, Some(next_query_keystrokes)) => {
format!(
"Search ({} for next query)",
next_query_keystrokes.join(" ")
)
}
(Some(previous_query_keystrokes), None) => {
format!(
"Search ({} for previous query)",
previous_query_keystrokes.join(" ")
)
}
(None, None) => String::new(),
};
self.query_editor.update(cx, |editor, cx| {
editor.set_placeholder_text(new_placeholder_text, cx);
});
Flex::row()
.with_child(
Flex::row()
@@ -303,7 +258,6 @@ impl BufferSearchBar {
pending_search: None,
query_contains_error: false,
dismissed: true,
search_history: SearchHistory::default(),
}
}
@@ -387,7 +341,7 @@ impl BufferSearchBar {
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<()> {
let options = options.unwrap_or(self.default_options);
if query != self.query(cx) || self.search_options != options {
if query != self.query_editor.read(cx).text(cx) || self.search_options != options {
self.query_editor.update(cx, |query_editor, cx| {
query_editor.buffer().update(cx, |query_buffer, cx| {
let len = query_buffer.len(cx);
@@ -720,7 +674,7 @@ impl BufferSearchBar {
fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
let (done_tx, done_rx) = oneshot::channel();
let query = self.query(cx);
let query = self.query_editor.read(cx).text(cx);
self.pending_search.take();
if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() {
@@ -753,7 +707,6 @@ impl BufferSearchBar {
)
};
let query_text = query.as_str().to_string();
let matches = active_searchable_item.find_matches(query, cx);
let active_searchable_item = active_searchable_item.downgrade();
@@ -767,7 +720,6 @@ impl BufferSearchBar {
.insert(active_searchable_item.downgrade(), matches);
this.update_match_index(cx);
this.search_history.add(query_text);
if !this.dismissed {
let matches = this
.searchable_items_with_matches
@@ -801,28 +753,6 @@ impl BufferSearchBar {
cx.notify();
}
}
fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
if let Some(new_query) = self.search_history.next().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
} else {
self.search_history.reset_selection();
let _ = self.search("", Some(self.search_options), cx);
}
}
fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
if self.query(cx).is_empty() {
if let Some(new_query) = self.search_history.current().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
return;
}
}
if let Some(new_query) = self.search_history.previous().map(str::to_string) {
let _ = self.search(&new_query, Some(self.search_options), cx);
}
}
}
#[cfg(test)]
@@ -849,10 +779,11 @@ mod tests {
cx,
)
});
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let search_bar = window.add_view(cx, |cx| {
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = cx.add_view(window_id, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@@ -1228,10 +1159,11 @@ mod tests {
"Should pick a query with multiple results"
);
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let (window_id, _root_view) = cx.add_window(|_| EmptyView);
let search_bar = window.add_view(cx, |cx| {
let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = cx.add_view(window_id, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
@@ -1247,13 +1179,12 @@ mod tests {
search_bar.activate_current_match(cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
!editor.is_focused(cx),
"Initially, the editor should not be focused"
);
});
let initial_selections = editor.update(cx, |editor, cx| {
let initial_selections = editor.selections.display_ranges(cx);
assert_eq!(
@@ -1270,7 +1201,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@@ -1294,7 +1225,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_next_match(&SelectNextMatch, cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectNextMatch"
@@ -1323,7 +1254,7 @@ mod tests {
cx.focus(search_bar.query_editor.as_any());
search_bar.select_all_matches(&SelectAllMatches, cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
editor.is_focused(cx),
"Should focus editor after successful SelectAllMatches"
@@ -1347,7 +1278,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_prev_match(&SelectPrevMatch, cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
editor.is_focused(cx),
"Should still have editor focused after SelectPrevMatch"
@@ -1383,7 +1314,7 @@ mod tests {
search_bar.update(cx, |search_bar, cx| {
search_bar.select_all_matches(&SelectAllMatches, cx);
});
window.read_with(cx, |cx| {
cx.read_window(window_id, |cx| {
assert!(
!editor.is_focused(cx),
"Should not switch focus to editor if SelectAllMatches does not find any matches"
@@ -1402,154 +1333,4 @@ mod tests {
);
});
}
#[gpui::test]
async fn test_search_query_history(cx: &mut TestAppContext) {
crate::project_search::tests::init_test(cx);
let buffer_text = r#"
A regular expression (shortened as regex or regexp;[1] also referred to as
rational expression[2][3]) is a sequence of characters that specifies a search
pattern in text. Usually such patterns are used by string-searching algorithms
for "find" or "find and replace" operations on strings, or for input validation.
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
let window = cx.add_window(|_| EmptyView);
let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
let search_bar = window.add_view(cx, |cx| {
let mut search_bar = BufferSearchBar::new(cx);
search_bar.set_active_pane_item(Some(&editor), cx);
search_bar.show(cx);
search_bar
});
// Add 3 search items into the history.
search_bar
.update(cx, |search_bar, cx| search_bar.search("a", None, cx))
.await
.unwrap();
search_bar
.update(cx, |search_bar, cx| search_bar.search("b", None, cx))
.await
.unwrap();
search_bar
.update(cx, |search_bar, cx| {
search_bar.search("c", Some(SearchOptions::CASE_SENSITIVE), cx)
})
.await
.unwrap();
// Ensure that the latest search is active.
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next history query after the latest should set the query to the empty string.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// First previous query for empty current query should set the query to the latest.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Further previous items should go over the history in reverse order.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Previous items should never go behind the first history item.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "a");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "a");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next items should go over the history in the original order.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar
.update(cx, |search_bar, cx| search_bar.search("ba", None, cx))
.await
.unwrap();
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "ba");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
// New search input should add another entry to history and move the selection to the end of the history.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "b");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "c");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "ba");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_bar.read_with(cx, |search_bar, cx| {
assert_eq!(search_bar.query(cx), "");
assert_eq!(search_bar.search_options, SearchOptions::NONE);
});
}
}

View File

@@ -1,14 +1,15 @@
use crate::{
NextHistoryQuery, PreviousHistoryQuery, SearchHistory, SearchOptions, SelectNextMatch,
SelectPrevMatch, ToggleCaseSensitive, ToggleRegex, ToggleWholeWord,
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleRegex,
ToggleWholeWord,
};
use anyhow::Context;
use anyhow::Result;
use collections::HashMap;
use editor::{
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
SelectAll, MAX_TAB_TITLE_LEN,
};
use futures::StreamExt;
use globset::{Glob, GlobMatcher};
use gpui::{
actions,
elements::*,
@@ -18,10 +19,7 @@ use gpui::{
};
use menu::Confirm;
use postage::stream::Stream;
use project::{
search::{PathMatcher, SearchQuery},
Entry, Project,
};
use project::{search::SearchQuery, Entry, Project};
use semantic_index::SemanticIndex;
use smallvec::SmallVec;
use std::{
@@ -56,8 +54,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(ProjectSearchBar::search_in_new);
cx.add_action(ProjectSearchBar::select_next_match);
cx.add_action(ProjectSearchBar::select_prev_match);
cx.add_action(ProjectSearchBar::next_history_query);
cx.add_action(ProjectSearchBar::previous_history_query);
cx.capture_action(ProjectSearchBar::tab);
cx.capture_action(ProjectSearchBar::tab_previous);
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
@@ -85,7 +81,6 @@ struct ProjectSearch {
match_ranges: Vec<Range<Anchor>>,
active_query: Option<SearchQuery>,
search_id: usize,
search_history: SearchHistory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -134,7 +129,6 @@ impl ProjectSearch {
match_ranges: Default::default(),
active_query: None,
search_id: 0,
search_history: SearchHistory::default(),
}
}
@@ -148,7 +142,6 @@ impl ProjectSearch {
match_ranges: self.match_ranges.clone(),
active_query: self.active_query.clone(),
search_id: self.search_id,
search_history: self.search_history.clone(),
})
}
@@ -157,7 +150,6 @@ impl ProjectSearch {
.project
.update(cx, |project, cx| project.search(query.clone(), cx));
self.search_id += 1;
self.search_history.add(query.as_str().to_string());
self.active_query = Some(query);
self.match_ranges.clear();
self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
@@ -193,22 +185,27 @@ impl ProjectSearch {
cx.notify();
}
fn semantic_search(&mut self, query: SearchQuery, cx: &mut ModelContext<Self>) {
fn semantic_search(
&mut self,
query: String,
include_files: Vec<GlobMatcher>,
exclude_files: Vec<GlobMatcher>,
cx: &mut ModelContext<Self>,
) {
let search = SemanticIndex::global(cx).map(|index| {
index.update(cx, |semantic_index, cx| {
semantic_index.search_project(
self.project.clone(),
query.as_str().to_owned(),
query.clone(),
10,
query.files_to_include().to_vec(),
query.files_to_exclude().to_vec(),
include_files,
exclude_files,
cx,
)
})
});
self.search_id += 1;
self.match_ranges.clear();
self.search_history.add(query.as_str().to_string());
self.pending_search = Some(cx.spawn(|this, mut cx| async move {
let results = search?.await.log_err()?;
@@ -285,49 +282,6 @@ impl View for ProjectSearchView {
Cow::Borrowed("No results")
};
let previous_query_keystrokes =
cx.binding_for_action(&PreviousHistoryQuery {})
.map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let next_query_keystrokes =
cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
binding
.keystrokes()
.iter()
.map(|k| k.to_string())
.collect::<Vec<_>>()
});
let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
(Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
format!(
"Search ({}/{} for previous/next query)",
previous_query_keystrokes.join(" "),
next_query_keystrokes.join(" ")
)
}
(None, Some(next_query_keystrokes)) => {
format!(
"Search ({} for next query)",
next_query_keystrokes.join(" ")
)
}
(Some(previous_query_keystrokes), None) => {
format!(
"Search ({} for previous query)",
previous_query_keystrokes.join(" ")
)
}
(None, None) => String::new(),
};
self.query_editor.update(cx, |editor, cx| {
editor.set_placeholder_text(new_placeholder_text, cx);
});
MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
Label::new(text, theme.search.results_status.clone())
.aligned()
@@ -636,7 +590,8 @@ impl ProjectSearchView {
if !dir_entry.is_dir() {
return;
}
let Some(filter_str) = dir_entry.path.to_str() else { return; };
let filter_path = dir_entry.path.join("**");
let Some(filter_str) = filter_path.to_str() else { return; };
let model = cx.add_model(|cx| ProjectSearch::new(workspace.project().clone(), cx));
let search = cx.add_view(|cx| ProjectSearchView::new(model, cx));
@@ -707,10 +662,16 @@ impl ProjectSearchView {
if semantic.outstanding_file_count > 0 {
return;
}
if let Some(query) = self.build_search_query(cx) {
self.model
.update(cx, |model, cx| model.semantic_search(query, cx));
let query = self.query_editor.read(cx).text(cx);
if let Some((included_files, exclude_files)) =
self.get_included_and_excluded_globsets(cx)
{
self.model.update(cx, |model, cx| {
model.semantic_search(query, included_files, exclude_files, cx)
});
}
return;
}
if let Some(query) = self.build_search_query(cx) {
@@ -718,10 +679,12 @@ impl ProjectSearchView {
}
}
fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
let text = self.query_editor.read(cx).text(cx);
fn get_included_and_excluded_globsets(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<(Vec<GlobMatcher>, Vec<GlobMatcher>)> {
let included_files =
match Self::parse_path_matches(&self.included_files_editor.read(cx).text(cx)) {
match Self::load_glob_set(&self.included_files_editor.read(cx).text(cx)) {
Ok(included_files) => {
self.panels_with_errors.remove(&InputPanel::Include);
included_files
@@ -733,7 +696,37 @@ impl ProjectSearchView {
}
};
let excluded_files =
match Self::parse_path_matches(&self.excluded_files_editor.read(cx).text(cx)) {
match Self::load_glob_set(&self.excluded_files_editor.read(cx).text(cx)) {
Ok(excluded_files) => {
self.panels_with_errors.remove(&InputPanel::Exclude);
excluded_files
}
Err(_e) => {
self.panels_with_errors.insert(InputPanel::Exclude);
cx.notify();
return None;
}
};
Some((included_files, excluded_files))
}
fn build_search_query(&mut self, cx: &mut ViewContext<Self>) -> Option<SearchQuery> {
let text = self.query_editor.read(cx).text(cx);
let included_files =
match Self::load_glob_set(&self.included_files_editor.read(cx).text(cx)) {
Ok(included_files) => {
self.panels_with_errors.remove(&InputPanel::Include);
included_files
}
Err(_e) => {
self.panels_with_errors.insert(InputPanel::Include);
cx.notify();
return None;
}
};
let excluded_files =
match Self::load_glob_set(&self.excluded_files_editor.read(cx).text(cx)) {
Ok(excluded_files) => {
self.panels_with_errors.remove(&InputPanel::Exclude);
excluded_files
@@ -773,14 +766,11 @@ impl ProjectSearchView {
}
}
fn parse_path_matches(text: &str) -> anyhow::Result<Vec<PathMatcher>> {
fn load_glob_set(text: &str) -> Result<Vec<GlobMatcher>> {
text.split(',')
.map(str::trim)
.filter(|maybe_glob_str| !maybe_glob_str.is_empty())
.map(|maybe_glob_str| {
PathMatcher::new(maybe_glob_str)
.with_context(|| format!("parsing {maybe_glob_str} as path matcher"))
})
.filter(|glob_str| !glob_str.is_empty())
.map(|glob_str| anyhow::Ok(Glob::new(glob_str)?.compile_matcher()))
.collect()
}
@@ -1202,47 +1192,6 @@ impl ProjectSearchBar {
false
}
}
fn next_history_query(&mut self, _: &NextHistoryQuery, cx: &mut ViewContext<Self>) {
if let Some(search_view) = self.active_project_search.as_ref() {
search_view.update(cx, |search_view, cx| {
let new_query = search_view.model.update(cx, |model, _| {
if let Some(new_query) = model.search_history.next().map(str::to_string) {
new_query
} else {
model.search_history.reset_selection();
String::new()
}
});
search_view.set_query(&new_query, cx);
});
}
}
fn previous_history_query(&mut self, _: &PreviousHistoryQuery, cx: &mut ViewContext<Self>) {
if let Some(search_view) = self.active_project_search.as_ref() {
search_view.update(cx, |search_view, cx| {
if search_view.query_editor.read(cx).text(cx).is_empty() {
if let Some(new_query) = search_view
.model
.read(cx)
.search_history
.current()
.map(str::to_string)
{
search_view.set_query(&new_query, cx);
return;
}
}
if let Some(new_query) = search_view.model.update(cx, |model, _| {
model.search_history.previous().map(str::to_string)
}) {
search_view.set_query(&new_query, cx);
}
});
}
}
}
impl Entity for ProjectSearchBar {
@@ -1424,7 +1373,6 @@ pub mod tests {
use editor::DisplayPoint;
use gpui::{color::Color, executor::Deterministic, TestAppContext};
use project::FakeFs;
use semantic_index::semantic_index_settings::SemanticIndexSettings;
use serde_json::json;
use settings::SettingsStore;
use std::sync::Arc;
@@ -1447,9 +1395,7 @@ pub mod tests {
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
let search_view = cx
.add_window(|cx| ProjectSearchView::new(search.clone(), cx))
.root(cx);
let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx));
search_view.update(cx, |search_view, cx| {
search_view
@@ -1566,8 +1512,7 @@ pub mod tests {
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let active_item = cx.read(|cx| {
workspace
@@ -1598,9 +1543,9 @@ pub mod tests {
};
let search_view_id = search_view.id();
cx.spawn(|mut cx| async move {
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
})
cx.spawn(
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
)
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -1651,7 +1596,7 @@ pub mod tests {
);
});
cx.spawn(
|mut cx| async move { window.dispatch_action(search_view_id, &ToggleFocus, &mut cx) },
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
)
.detach();
deterministic.run_until_parked();
@@ -1682,9 +1627,9 @@ pub mod tests {
"Search view with mismatching query should be focused after search results are available",
);
});
cx.spawn(|mut cx| async move {
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
})
cx.spawn(
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
)
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -1712,9 +1657,9 @@ pub mod tests {
);
});
cx.spawn(|mut cx| async move {
window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
})
cx.spawn(
|mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
)
.detach();
deterministic.run_until_parked();
search_view.update(cx, |search_view, cx| {
@@ -1751,9 +1696,7 @@ pub mod tests {
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
let workspace = cx
.add_window(|cx| Workspace::test_new(project, cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let active_item = cx.read(|cx| {
workspace
@@ -1831,7 +1774,7 @@ pub mod tests {
search_view.included_files_editor.update(cx, |editor, cx| {
assert_eq!(
editor.display_text(cx),
a_dir_entry.path.to_str().unwrap(),
a_dir_entry.path.join("**").display().to_string(),
"New search in directory should have included dir entry path"
);
});
@@ -1855,193 +1798,6 @@ pub mod tests {
});
}
#[gpui::test]
async fn test_search_query_history(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background());
fs.insert_tree(
"/dir",
json!({
"one.rs": "const ONE: usize = 1;",
"two.rs": "const TWO: usize = one::ONE + one::ONE;",
"three.rs": "const THREE: usize = one::ONE + two::TWO;",
"four.rs": "const FOUR: usize = one::ONE + three::THREE;",
}),
)
.await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
workspace.update(cx, |workspace, cx| {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
});
let search_view = cx.read(|cx| {
workspace
.read(cx)
.active_pane()
.read(cx)
.active_item()
.and_then(|item| item.downcast::<ProjectSearchView>())
.expect("Search view expected to appear after new search event trigger")
});
let search_bar = window.add_view(cx, |cx| {
let mut search_bar = ProjectSearchBar::new();
search_bar.set_active_pane_item(Some(&search_view), cx);
// search_bar.show(cx);
search_bar
});
// Add 3 search items into the history + another unsubmitted one.
search_view.update(cx, |search_view, cx| {
search_view.search_options = SearchOptions::CASE_SENSITIVE;
search_view
.query_editor
.update(cx, |query_editor, cx| query_editor.set_text("ONE", cx));
search_view.search(cx);
});
cx.foreground().run_until_parked();
search_view.update(cx, |search_view, cx| {
search_view
.query_editor
.update(cx, |query_editor, cx| query_editor.set_text("TWO", cx));
search_view.search(cx);
});
cx.foreground().run_until_parked();
search_view.update(cx, |search_view, cx| {
search_view
.query_editor
.update(cx, |query_editor, cx| query_editor.set_text("THREE", cx));
search_view.search(cx);
});
cx.foreground().run_until_parked();
search_view.update(cx, |search_view, cx| {
search_view.query_editor.update(cx, |query_editor, cx| {
query_editor.set_text("JUST_TEXT_INPUT", cx)
});
});
cx.foreground().run_until_parked();
// Ensure that the latest input with search settings is active.
search_view.update(cx, |search_view, cx| {
assert_eq!(
search_view.query_editor.read(cx).text(cx),
"JUST_TEXT_INPUT"
);
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next history query after the latest should set the query to the empty string.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// First previous query for empty current query should set the query to the latest submitted one.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// Further previous items should go over the history in reverse order.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// Previous items should never go behind the first history item.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "ONE");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// Next items should go over the history in the original order.
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_view.update(cx, |search_view, cx| {
search_view
.query_editor
.update(cx, |query_editor, cx| query_editor.set_text("TWO_NEW", cx));
search_view.search(cx);
});
cx.foreground().run_until_parked();
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
// New search input should add another entry to history and move the selection to the end of the history.
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.previous_history_query(&PreviousHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "THREE");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "TWO_NEW");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
search_bar.update(cx, |search_bar, cx| {
search_bar.next_history_query(&NextHistoryQuery, cx);
});
search_view.update(cx, |search_view, cx| {
assert_eq!(search_view.query_editor.read(cx).text(cx), "");
assert_eq!(search_view.search_options, SearchOptions::CASE_SENSITIVE);
});
}
pub fn init_test(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
let fonts = cx.font_cache();
@@ -2051,7 +1807,6 @@ pub mod tests {
cx.update(|cx| {
cx.set_global(SettingsStore::test(cx));
cx.set_global(ActiveSearches::default());
settings::register::<SemanticIndexSettings>(cx);
theme::init((), cx);
cx.update_global::<SettingsStore, _, _>(|store, _| {

View File

@@ -3,7 +3,6 @@ pub use buffer_search::BufferSearchBar;
use gpui::{actions, Action, AppContext};
use project::search::SearchQuery;
pub use project_search::{ProjectSearchBar, ProjectSearchView};
use smallvec::SmallVec;
pub mod buffer_search;
pub mod project_search;
@@ -22,8 +21,6 @@ actions!(
SelectNextMatch,
SelectPrevMatch,
SelectAllMatches,
NextHistoryQuery,
PreviousHistoryQuery,
]
);
@@ -68,187 +65,3 @@ impl SearchOptions {
options
}
}
const SEARCH_HISTORY_LIMIT: usize = 20;
#[derive(Default, Debug, Clone)]
pub struct SearchHistory {
history: SmallVec<[String; SEARCH_HISTORY_LIMIT]>,
selected: Option<usize>,
}
impl SearchHistory {
pub fn add(&mut self, search_string: String) {
if let Some(i) = self.selected {
if search_string == self.history[i] {
return;
}
}
if let Some(previously_searched) = self.history.last_mut() {
if search_string.find(previously_searched.as_str()).is_some() {
*previously_searched = search_string;
self.selected = Some(self.history.len() - 1);
return;
}
}
self.history.push(search_string);
if self.history.len() > SEARCH_HISTORY_LIMIT {
self.history.remove(0);
}
self.selected = Some(self.history.len() - 1);
}
pub fn next(&mut self) -> Option<&str> {
let history_size = self.history.len();
if history_size == 0 {
return None;
}
let selected = self.selected?;
if selected == history_size - 1 {
return None;
}
let next_index = selected + 1;
self.selected = Some(next_index);
Some(&self.history[next_index])
}
pub fn current(&self) -> Option<&str> {
Some(&self.history[self.selected?])
}
pub fn previous(&mut self) -> Option<&str> {
let history_size = self.history.len();
if history_size == 0 {
return None;
}
let prev_index = match self.selected {
Some(selected_index) => {
if selected_index == 0 {
return None;
} else {
selected_index - 1
}
}
None => history_size - 1,
};
self.selected = Some(prev_index);
Some(&self.history[prev_index])
}
pub fn reset_selection(&mut self) {
self.selected = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
let mut search_history = SearchHistory::default();
assert_eq!(
search_history.current(),
None,
"No current selection should be set fo the default search history"
);
search_history.add("rust".to_string());
assert_eq!(
search_history.current(),
Some("rust"),
"Newly added item should be selected"
);
// check if duplicates are not added
search_history.add("rust".to_string());
assert_eq!(
search_history.history.len(),
1,
"Should not add a duplicate"
);
assert_eq!(search_history.current(), Some("rust"));
// check if new string containing the previous string replaces it
search_history.add("rustlang".to_string());
assert_eq!(
search_history.history.len(),
1,
"Should replace previous item if it's a substring"
);
assert_eq!(search_history.current(), Some("rustlang"));
// push enough items to test SEARCH_HISTORY_LIMIT
for i in 0..SEARCH_HISTORY_LIMIT * 2 {
search_history.add(format!("item{i}"));
}
assert!(search_history.history.len() <= SEARCH_HISTORY_LIMIT);
}
#[test]
fn test_next_and_previous() {
let mut search_history = SearchHistory::default();
assert_eq!(
search_history.next(),
None,
"Default search history should not have a next item"
);
search_history.add("Rust".to_string());
assert_eq!(search_history.next(), None);
search_history.add("JavaScript".to_string());
assert_eq!(search_history.next(), None);
search_history.add("TypeScript".to_string());
assert_eq!(search_history.next(), None);
assert_eq!(search_history.current(), Some("TypeScript"));
assert_eq!(search_history.previous(), Some("JavaScript"));
assert_eq!(search_history.current(), Some("JavaScript"));
assert_eq!(search_history.previous(), Some("Rust"));
assert_eq!(search_history.current(), Some("Rust"));
assert_eq!(search_history.previous(), None);
assert_eq!(search_history.current(), Some("Rust"));
assert_eq!(search_history.next(), Some("JavaScript"));
assert_eq!(search_history.current(), Some("JavaScript"));
assert_eq!(search_history.next(), Some("TypeScript"));
assert_eq!(search_history.current(), Some("TypeScript"));
assert_eq!(search_history.next(), None);
assert_eq!(search_history.current(), Some("TypeScript"));
}
#[test]
fn test_reset_selection() {
let mut search_history = SearchHistory::default();
search_history.add("Rust".to_string());
search_history.add("JavaScript".to_string());
search_history.add("TypeScript".to_string());
assert_eq!(search_history.current(), Some("TypeScript"));
search_history.reset_selection();
assert_eq!(search_history.current(), None);
assert_eq!(
search_history.previous(),
Some("TypeScript"),
"Should start from the end after reset on previous item query"
);
search_history.previous();
assert_eq!(search_history.current(), Some("JavaScript"));
search_history.previous();
assert_eq!(search_history.current(), Some("Rust"));
search_history.reset_selection();
assert_eq!(search_history.current(), None);
}
}

View File

@@ -54,12 +54,9 @@ tempdir.workspace = true
ctor.workspace = true
env_logger.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-json.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-toml.workspace = true
tree-sitter-cpp.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-lua.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-php.workspace = true
tree-sitter-typescript = "*"
tree-sitter-json = "*"
tree-sitter-rust = "*"
tree-sitter-toml = "*"
tree-sitter-cpp = "*"
tree-sitter-elixir = "*"

View File

@@ -1,6 +1,7 @@
use crate::{parsing::Document, SEMANTIC_INDEX_VERSION};
use anyhow::{anyhow, Context, Result};
use project::{search::PathMatcher, Fs};
use globset::GlobMatcher;
use project::Fs;
use rpc::proto::Timestamp;
use rusqlite::{
params,
@@ -266,32 +267,41 @@ impl VectorDatabase {
pub fn top_k_search(
&self,
worktree_ids: &[i64],
query_embedding: &Vec<f32>,
limit: usize,
file_ids: &[i64],
) -> Result<Vec<(i64, f32)>> {
include_globs: Vec<GlobMatcher>,
exclude_globs: Vec<GlobMatcher>,
) -> Result<Vec<(i64, PathBuf, Range<usize>)>> {
let mut results = Vec::<(i64, f32)>::with_capacity(limit + 1);
self.for_each_document(file_ids, |id, embedding| {
let similarity = dot(&embedding, &query_embedding);
let ix = match results
.binary_search_by(|(_, s)| similarity.partial_cmp(&s).unwrap_or(Ordering::Equal))
{
Ok(ix) => ix,
Err(ix) => ix,
};
results.insert(ix, (id, similarity));
results.truncate(limit);
})?;
self.for_each_document(
&worktree_ids,
include_globs,
exclude_globs,
|id, embedding| {
let similarity = dot(&embedding, &query_embedding);
let ix = match results.binary_search_by(|(_, s)| {
similarity.partial_cmp(&s).unwrap_or(Ordering::Equal)
}) {
Ok(ix) => ix,
Err(ix) => ix,
};
results.insert(ix, (id, similarity));
results.truncate(limit);
},
)?;
Ok(results)
let ids = results.into_iter().map(|(id, _)| id).collect::<Vec<_>>();
self.get_documents_by_ids(&ids)
}
pub fn retrieve_included_file_ids(
fn for_each_document(
&self,
worktree_ids: &[i64],
includes: &[PathMatcher],
excludes: &[PathMatcher],
) -> Result<Vec<i64>> {
include_globs: Vec<GlobMatcher>,
exclude_globs: Vec<GlobMatcher>,
mut f: impl FnMut(i64, Vec<f32>),
) -> Result<()> {
let mut file_query = self.db.prepare(
"
SELECT
@@ -305,22 +315,21 @@ impl VectorDatabase {
let mut file_ids = Vec::<i64>::new();
let mut rows = file_query.query([ids_to_sql(worktree_ids)])?;
while let Some(row) = rows.next()? {
let file_id = row.get(0)?;
let relative_path = row.get_ref(1)?.as_str()?;
let included =
includes.is_empty() || includes.iter().any(|glob| glob.is_match(relative_path));
let excluded = excludes.iter().any(|glob| glob.is_match(relative_path));
let included = include_globs.is_empty()
|| include_globs
.iter()
.any(|glob| glob.is_match(relative_path));
let excluded = exclude_globs
.iter()
.any(|glob| glob.is_match(relative_path));
if included && !excluded {
file_ids.push(file_id);
}
}
Ok(file_ids)
}
fn for_each_document(&self, file_ids: &[i64], mut f: impl FnMut(i64, Vec<f32>)) -> Result<()> {
let mut query_statement = self.db.prepare(
"
SELECT
@@ -341,7 +350,7 @@ impl VectorDatabase {
Ok(())
}
pub fn get_documents_by_ids(&self, ids: &[i64]) -> Result<Vec<(i64, PathBuf, Range<usize>)>> {
fn get_documents_by_ids(&self, ids: &[i64]) -> Result<Vec<(i64, PathBuf, Range<usize>)>> {
let mut statement = self.db.prepare(
"
SELECT

View File

@@ -21,9 +21,7 @@ const CODE_CONTEXT_TEMPLATE: &str =
"The below code snippet is from file '<path>'\n\n```<language>\n<item>\n```";
const ENTIRE_FILE_TEMPLATE: &str =
"The below snippet is from file '<path>'\n\n```<language>\n<item>\n```";
const MARKDOWN_CONTEXT_TEMPLATE: &str = "The below file contents is from file '<path>'\n\n<item>";
pub const PARSEABLE_ENTIRE_FILE_TYPES: &[&str] =
&["TOML", "YAML", "CSS", "HEEX", "ERB", "SVELTE", "HTML"];
pub const PARSEABLE_ENTIRE_FILE_TYPES: &[&str] = &["TOML", "YAML", "CSS"];
pub struct CodeContextRetriever {
pub parser: Parser,
@@ -61,7 +59,7 @@ impl CodeContextRetriever {
let document_span = ENTIRE_FILE_TEMPLATE
.replace("<path>", relative_path.to_string_lossy().as_ref())
.replace("<language>", language_name.as_ref())
.replace("<item>", &content);
.replace("item", &content);
Ok(vec![Document {
range: 0..content.len(),
@@ -71,19 +69,6 @@ impl CodeContextRetriever {
}])
}
fn parse_markdown_file(&self, relative_path: &Path, content: &str) -> Result<Vec<Document>> {
let document_span = MARKDOWN_CONTEXT_TEMPLATE
.replace("<path>", relative_path.to_string_lossy().as_ref())
.replace("<item>", &content);
Ok(vec![Document {
range: 0..content.len(),
content: document_span,
embedding: Vec::new(),
name: "Markdown".to_string(),
}])
}
fn get_matches_in_file(
&mut self,
content: &str,
@@ -150,8 +135,6 @@ impl CodeContextRetriever {
if PARSEABLE_ENTIRE_FILE_TYPES.contains(&language_name.as_ref()) {
return self.parse_entire_file(relative_path, language_name, &content);
} else if &language_name.to_string() == &"Markdown".to_string() {
return self.parse_markdown_file(relative_path, &content);
}
let mut documents = self.parse_file(content, language)?;
@@ -217,12 +200,7 @@ impl CodeContextRetriever {
let mut document_content = String::new();
for context_range in &context_match.context_ranges {
add_content_from_range(
&mut document_content,
content,
context_range.clone(),
context_match.start_col,
);
document_content.push_str(&content[context_range.clone()]);
document_content.push_str("\n");
}

View File

@@ -11,15 +11,15 @@ use anyhow::{anyhow, Result};
use db::VectorDatabase;
use embedding::{EmbeddingProvider, OpenAIEmbeddings};
use futures::{channel::oneshot, Future};
use globset::GlobMatcher;
use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle};
use language::{Anchor, Buffer, Language, LanguageRegistry};
use parking_lot::Mutex;
use parsing::{CodeContextRetriever, Document, PARSEABLE_ENTIRE_FILE_TYPES};
use postage::watch;
use project::{search::PathMatcher, Fs, Project, WorktreeId};
use project::{Fs, Project, WorktreeId};
use smol::channel;
use std::{
cmp::Ordering,
collections::HashMap,
mem,
ops::Range,
@@ -612,7 +612,6 @@ impl SemanticIndex {
.await
{
if !PARSEABLE_ENTIRE_FILE_TYPES.contains(&language.name().as_ref())
&& &language.name().as_ref() != &"Markdown"
&& language
.grammar()
.and_then(|grammar| grammar.embedding_config.as_ref())
@@ -682,8 +681,8 @@ impl SemanticIndex {
project: ModelHandle<Project>,
phrase: String,
limit: usize,
includes: Vec<PathMatcher>,
excludes: Vec<PathMatcher>,
include_globs: Vec<GlobMatcher>,
exclude_globs: Vec<GlobMatcher>,
cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<SearchResult>>> {
let project_state = if let Some(state) = self.projects.get(&project.downgrade()) {
@@ -705,66 +704,27 @@ impl SemanticIndex {
let database_url = self.database_url.clone();
let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move {
let database = VectorDatabase::new(fs.clone(), database_url.clone()).await?;
let documents = cx
.background()
.spawn(async move {
let database = VectorDatabase::new(fs, database_url).await?;
let phrase_embedding = embedding_provider
.embed_batch(vec![&phrase])
.await?
.into_iter()
.next()
.unwrap();
let phrase_embedding = embedding_provider
.embed_batch(vec![&phrase])
.await?
.into_iter()
.next()
.unwrap();
let file_ids =
database.retrieve_included_file_ids(&worktree_db_ids, &includes, &excludes)?;
let batch_n = cx.background().num_cpus();
let ids_len = file_ids.clone().len();
let batch_size = if ids_len <= batch_n {
ids_len
} else {
ids_len / batch_n
};
let mut result_tasks = Vec::new();
for batch in file_ids.chunks(batch_size) {
let batch = batch.into_iter().map(|v| *v).collect::<Vec<i64>>();
let limit = limit.clone();
let fs = fs.clone();
let database_url = database_url.clone();
let phrase_embedding = phrase_embedding.clone();
let task = cx.background().spawn(async move {
let database = VectorDatabase::new(fs, database_url).await.log_err();
if database.is_none() {
return Err(anyhow!("failed to acquire database connection"));
} else {
database
.unwrap()
.top_k_search(&phrase_embedding, limit, batch.as_slice())
}
});
result_tasks.push(task);
}
let batch_results = futures::future::join_all(result_tasks).await;
let mut results = Vec::new();
for batch_result in batch_results {
if batch_result.is_ok() {
for (id, similarity) in batch_result.unwrap() {
let ix = match results.binary_search_by(|(_, s)| {
similarity.partial_cmp(&s).unwrap_or(Ordering::Equal)
}) {
Ok(ix) => ix,
Err(ix) => ix,
};
results.insert(ix, (id, similarity));
results.truncate(limit);
}
}
}
let ids = results.into_iter().map(|(id, _)| id).collect::<Vec<i64>>();
let documents = database.get_documents_by_ids(ids.as_slice())?;
database.top_k_search(
&worktree_db_ids,
&phrase_embedding,
limit,
include_globs,
exclude_globs,
)
})
.await?;
let mut tasks = Vec::new();
let mut ranges = Vec::new();

View File

@@ -7,10 +7,11 @@ use crate::{
};
use anyhow::Result;
use async_trait::async_trait;
use globset::Glob;
use gpui::{Task, TestAppContext};
use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
use pretty_assertions::assert_eq;
use project::{project_settings::ProjectSettings, search::PathMatcher, FakeFs, Fs, Project};
use project::{project_settings::ProjectSettings, FakeFs, Fs, Project};
use rand::{rngs::StdRng, Rng};
use serde_json::json;
use settings::SettingsStore;
@@ -120,8 +121,8 @@ async fn test_semantic_index(cx: &mut TestAppContext) {
);
// Test Include Files Functonality
let include_files = vec![PathMatcher::new("*.rs").unwrap()];
let exclude_files = vec![PathMatcher::new("*.rs").unwrap()];
let include_files = vec![Glob::new("*.rs").unwrap().compile_matcher()];
let exclude_files = vec![Glob::new("*.rs").unwrap().compile_matcher()];
let rust_only_search_results = store
.update(cx, |store, cx| {
store.search_project(
@@ -485,79 +486,6 @@ async fn test_code_context_retrieval_javascript() {
)
}
#[gpui::test]
async fn test_code_context_retrieval_lua() {
let language = lua_lang();
let mut retriever = CodeContextRetriever::new();
let text = r#"
-- Creates a new class
-- @param baseclass The Baseclass of this class, or nil.
-- @return A new class reference.
function classes.class(baseclass)
-- Create the class definition and metatable.
local classdef = {}
-- Find the super class, either Object or user-defined.
baseclass = baseclass or classes.Object
-- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable.
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.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc(mastertable)
-- All class instances have a reference to a superclass object.
local instance = { super = baseclass.alloc(mastertable) }
-- Any functions this instance does not know of will 'look up' to the superclass definition.
setmetatable(instance, { __index = classdef, __newindex = mastertable })
return instance
end
end
"#.unindent();
let documents = retriever.parse_file(&text, language.clone()).unwrap();
assert_documents_eq(
&documents,
&[
(r#"
-- Creates a new class
-- @param baseclass The Baseclass of this class, or nil.
-- @return A new class reference.
function classes.class(baseclass)
-- Create the class definition and metatable.
local classdef = {}
-- Find the super class, either Object or user-defined.
baseclass = baseclass or classes.Object
-- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable.
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.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc(mastertable)
--[ ... ]--
--[ ... ]--
end
end"#.unindent(),
114),
(r#"
--- Recursivly 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)
-- All class instances have a reference to a superclass object.
local instance = { super = baseclass.alloc(mastertable) }
-- 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),
]
);
}
#[gpui::test]
async fn test_code_context_retrieval_elixir() {
let language = elixir_lang();
@@ -826,346 +754,6 @@ async fn test_code_context_retrieval_cpp() {
);
}
#[gpui::test]
async fn test_code_context_retrieval_ruby() {
let language = ruby_lang();
let mut retriever = CodeContextRetriever::new();
let text = r#"
# This concern is inspired by "sudo mode" on GitHub. It
# is a way to re-authenticate a user before allowing them
# to see or perform an action.
#
# Add `before_action :require_challenge!` to actions you
# want to protect.
#
# The user will be shown a page to enter the challenge (which
# is either the password, or just the username when no
# password exists). Upon passing, there is a grace period
# during which no challenge will be asked from the user.
#
# Accessing challenge-protected resources during the grace
# period will refresh the grace period.
module ChallengableConcern
extend ActiveSupport::Concern
CHALLENGE_TIMEOUT = 1.hour.freeze
def require_challenge!
return if skip_challenge?
if challenge_passed_recently?
session[:challenge_passed_at] = Time.now.utc
return
end
@challenge = Form::Challenge.new(return_to: request.url)
if params.key?(:form_challenge)
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
else
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge
end
else
render_challenge
end
end
def challenge_passed?
current_user.valid_password?(challenge_params[:current_password])
end
end
class Animal
include Comparable
attr_reader :legs
def initialize(name, legs)
@name, @legs = name, legs
end
def <=>(other)
legs <=> other.legs
end
end
# Singleton method for car object
def car.wheels
puts "There are four wheels"
end"#
.unindent();
let documents = retriever.parse_file(&text, language.clone()).unwrap();
assert_documents_eq(
&documents,
&[
(
r#"
# This concern is inspired by "sudo mode" on GitHub. It
# is a way to re-authenticate a user before allowing them
# to see or perform an action.
#
# Add `before_action :require_challenge!` to actions you
# want to protect.
#
# The user will be shown a page to enter the challenge (which
# is either the password, or just the username when no
# password exists). Upon passing, there is a grace period
# during which no challenge will be asked from the user.
#
# Accessing challenge-protected resources during the grace
# period will refresh the grace period.
module ChallengableConcern
extend ActiveSupport::Concern
CHALLENGE_TIMEOUT = 1.hour.freeze
def require_challenge!
# ...
end
def challenge_passed?
# ...
end
end"#
.unindent(),
558,
),
(
r#"
def require_challenge!
return if skip_challenge?
if challenge_passed_recently?
session[:challenge_passed_at] = Time.now.utc
return
end
@challenge = Form::Challenge.new(return_to: request.url)
if params.key?(:form_challenge)
if challenge_passed?
session[:challenge_passed_at] = Time.now.utc
else
flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge
end
else
render_challenge
end
end"#
.unindent(),
663,
),
(
r#"
def challenge_passed?
current_user.valid_password?(challenge_params[:current_password])
end"#
.unindent(),
1254,
),
(
r#"
class Animal
include Comparable
attr_reader :legs
def initialize(name, legs)
# ...
end
def <=>(other)
# ...
end
end"#
.unindent(),
1363,
),
(
r#"
def initialize(name, legs)
@name, @legs = name, legs
end"#
.unindent(),
1427,
),
(
r#"
def <=>(other)
legs <=> other.legs
end"#
.unindent(),
1501,
),
(
r#"
# Singleton method for car object
def car.wheels
puts "There are four wheels"
end"#
.unindent(),
1591,
),
],
);
}
#[gpui::test]
async fn test_code_context_retrieval_php() {
let language = php_lang();
let mut retriever = CodeContextRetriever::new();
let text = r#"
<?php
namespace LevelUp\Experience\Concerns;
/*
This is a multiple-lines comment block
that spans over multiple
lines
*/
function functionName() {
echo "Hello world!";
}
trait HasAchievements
{
/**
* @throws \Exception
*/
public function grantAchievement(Achievement $achievement, $progress = null): void
{
if ($progress > 100) {
throw new Exception(message: 'Progress cannot be greater than 100');
}
if ($this->achievements()->find($achievement->id)) {
throw new Exception(message: 'User already has this Achievement');
}
$this->achievements()->attach($achievement, [
'progress' => $progress ?? null,
]);
$this->when(value: ($progress === null) || ($progress === 100), callback: fn (): ?array => event(new AchievementAwarded(achievement: $achievement, user: $this)));
}
public function achievements(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
->withPivot(columns: 'progress')
->where('is_secret', false)
->using(AchievementUser::class);
}
}
interface Multiplier
{
public function qualifies(array $data): bool;
public function setMultiplier(): int;
}
enum AuditType: string
{
case Add = 'add';
case Remove = 'remove';
case Reset = 'reset';
case LevelUp = 'level_up';
}
?>"#
.unindent();
let documents = retriever.parse_file(&text, language.clone()).unwrap();
assert_documents_eq(
&documents,
&[
(
r#"
/*
This is a multiple-lines comment block
that spans over multiple
lines
*/
function functionName() {
echo "Hello world!";
}"#
.unindent(),
123,
),
(
r#"
trait HasAchievements
{
/**
* @throws \Exception
*/
public function grantAchievement(Achievement $achievement, $progress = null): void
{/* ... */}
public function achievements(): BelongsToMany
{/* ... */}
}"#
.unindent(),
177,
),
(r#"
/**
* @throws \Exception
*/
public function grantAchievement(Achievement $achievement, $progress = null): void
{
if ($progress > 100) {
throw new Exception(message: 'Progress cannot be greater than 100');
}
if ($this->achievements()->find($achievement->id)) {
throw new Exception(message: 'User already has this Achievement');
}
$this->achievements()->attach($achievement, [
'progress' => $progress ?? null,
]);
$this->when(value: ($progress === null) || ($progress === 100), callback: fn (): ?array => event(new AchievementAwarded(achievement: $achievement, user: $this)));
}"#.unindent(), 245),
(r#"
public function achievements(): BelongsToMany
{
return $this->belongsToMany(related: Achievement::class)
->withPivot(columns: 'progress')
->where('is_secret', false)
->using(AchievementUser::class);
}"#.unindent(), 902),
(r#"
interface Multiplier
{
public function qualifies(array $data): bool;
public function setMultiplier(): int;
}"#.unindent(),
1146),
(r#"
enum AuditType: string
{
case Add = 'add';
case Remove = 'remove';
case Reset = 'reset';
case LevelUp = 'level_up';
}"#.unindent(), 1265)
],
);
}
#[gpui::test]
fn test_dot_product(mut rng: StdRng) {
assert_eq!(dot(&[1., 0., 0., 0., 0.], &[0., 1., 0., 0., 0.]), 0.);
@@ -1496,131 +1084,6 @@ fn cpp_lang() -> Arc<Language> {
)
}
fn lua_lang() -> Arc<Language> {
Arc::new(
Language::new(
LanguageConfig {
name: "Lua".into(),
path_suffixes: vec!["lua".into()],
collapsed_placeholder: "--[ ... ]--".to_string(),
..Default::default()
},
Some(tree_sitter_lua::language()),
)
.with_embedding_query(
r#"
(
(comment)* @context
.
(function_declaration
"function" @name
name: (_) @name
(comment)* @collapse
body: (block) @collapse
) @item
)
"#,
)
.unwrap(),
)
}
fn php_lang() -> Arc<Language> {
Arc::new(
Language::new(
LanguageConfig {
name: "PHP".into(),
path_suffixes: vec!["php".into()],
collapsed_placeholder: "/* ... */".into(),
..Default::default()
},
Some(tree_sitter_php::language()),
)
.with_embedding_query(
r#"
(
(comment)* @context
.
[
(function_definition
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)
(trait_declaration
"trait" @name
name: (_) @name)
(method_declaration
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)
(interface_declaration
"interface" @name
name: (_) @name
)
(enum_declaration
"enum" @name
name: (_) @name
)
] @item
)
"#,
)
.unwrap(),
)
}
fn ruby_lang() -> Arc<Language> {
Arc::new(
Language::new(
LanguageConfig {
name: "Ruby".into(),
path_suffixes: vec!["rb".into()],
collapsed_placeholder: "# ...".to_string(),
..Default::default()
},
Some(tree_sitter_ruby::language()),
)
.with_embedding_query(
r#"
(
(comment)* @context
.
[
(module
"module" @name
name: (_) @name)
(method
"def" @name
name: (_) @name
body: (body_statement) @collapse)
(class
"class" @name
name: (_) @name)
(singleton_method
"def" @name
object: (_) @name
"." @name
name: (_) @name
body: (body_statement) @collapse)
] @item
)
"#,
)
.unwrap(),
)
}
fn elixir_lang() -> Arc<Language> {
Arc::new(
Language::new(

View File

@@ -202,7 +202,7 @@ where
self.position = D::default();
}
let entry = self.stack.last_mut().unwrap();
let mut entry = self.stack.last_mut().unwrap();
if !descending {
if entry.index == 0 {
self.stack.pop();

View File

@@ -16,7 +16,7 @@ db = { path = "../db" }
theme = { path = "../theme" }
util = { path = "../util" }
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "7b9f32300ee0a249c0872302c97635b460e45ba5" }
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
smallvec.workspace = true
smol.workspace = true

View File

@@ -114,7 +114,11 @@ fn rgb_for_index(i: &u8) -> (u8, u8, u8) {
//Convenience method to convert from a GPUI color to an alacritty Rgb
pub fn to_alac_rgb(color: Color) -> AlacRgb {
AlacRgb::new(color.r, color.g, color.g)
AlacRgb {
r: color.r,
g: color.g,
b: color.g,
}
}
#[cfg(test)]

View File

@@ -53,7 +53,7 @@ use gpui::{
keymap_matcher::Keystroke,
platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
AnyWindowHandle, AppContext, ClipboardItem, Entity, ModelContext, Task,
AppContext, ClipboardItem, Entity, ModelContext, Task,
};
use crate::mappings::{
@@ -404,7 +404,7 @@ impl TerminalBuilder {
mut env: HashMap<String, String>,
blink_settings: Option<TerminalBlink>,
alternate_scroll: AlternateScroll,
window: AnyWindowHandle,
window_id: usize,
) -> Result<TerminalBuilder> {
let pty_config = {
let alac_shell = match shell.clone() {
@@ -462,7 +462,7 @@ impl TerminalBuilder {
let pty = match tty::new(
&pty_config,
TerminalSize::default().into(),
window.id() as u64,
window_id as u64,
) {
Ok(pty) => pty,
Err(error) => {

View File

@@ -10,9 +10,8 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json::json,
text_layout::{Line, RunStyle},
AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
WeakModelHandle,
AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad,
SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
};
use itertools::Itertools;
use language::CursorShape;
@@ -735,7 +734,7 @@ impl Element<TerminalView> for TerminalElement {
visible_bounds: RectF,
layout: &mut Self::LayoutState,
view: &mut TerminalView,
cx: &mut PaintContext<TerminalView>,
cx: &mut ViewContext<TerminalView>,
) -> Self::PaintState {
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

View File

@@ -48,7 +48,7 @@ impl TerminalPanel {
fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
let weak_self = cx.weak_handle();
let pane = cx.add_view(|cx| {
let window = cx.window();
let window_id = cx.window_id();
let mut pane = Pane::new(
workspace.weak_handle(),
workspace.project().clone(),
@@ -60,7 +60,7 @@ impl TerminalPanel {
pane.set_can_navigate(false, cx);
pane.on_can_drop(move |drag_and_drop, cx| {
drag_and_drop
.currently_dragged::<DraggedItem>(window)
.currently_dragged::<DraggedItem>(window_id)
.map_or(false, |(_, item)| {
item.handle.act_as::<TerminalView>(cx).is_some()
})
@@ -255,10 +255,10 @@ impl TerminalPanel {
.clone();
let working_directory =
crate::get_working_directory(workspace, cx, working_directory_strategy);
let window = cx.window();
let window_id = cx.window_id();
if let Some(terminal) = workspace.project().update(cx, |project, cx| {
project
.create_terminal(working_directory, window, cx)
.create_terminal(working_directory, window_id, cx)
.log_err()
}) {
let terminal = Box::new(cx.add_view(|cx| {

View File

@@ -112,11 +112,11 @@ impl TerminalView {
let working_directory =
get_working_directory(workspace, cx, strategy.working_directory.clone());
let window = cx.window();
let window_id = cx.window_id();
let terminal = workspace
.project()
.update(cx, |project, cx| {
project.create_terminal(working_directory, window, cx)
project.create_terminal(working_directory, window_id, cx)
})
.notify_err(workspace, cx);
@@ -741,7 +741,7 @@ impl Item for TerminalView {
item_id: workspace::ItemId,
cx: &mut ViewContext<Pane>,
) -> Task<anyhow::Result<ViewHandle<Self>>> {
let window = cx.window();
let window_id = cx.window_id();
cx.spawn(|pane, mut cx| async move {
let cwd = TERMINAL_DB
.get_working_directory(item_id, workspace_id)
@@ -762,7 +762,7 @@ impl Item for TerminalView {
});
let terminal = project.update(&mut cx, |project, cx| {
project.create_terminal(cwd, window, cx)
project.create_terminal(cwd, window_id, cx)
})?;
Ok(pane.update(&mut cx, |_, cx| {
cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))
@@ -1070,9 +1070,7 @@ mod tests {
});
let project = Project::test(params.fs.clone(), [], cx).await;
let workspace = cx
.add_window(|cx| Workspace::test_new(project.clone(), cx))
.root(cx);
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
(project, workspace)
}

Some files were not shown because too many files have changed in this diff Show More