Compare commits
23 Commits
v0.99.0-pr
...
v0.97.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1c67a5025 | ||
|
|
cbaf7cc972 | ||
|
|
c24c4063e3 | ||
|
|
707da9bbbf | ||
|
|
c826010008 | ||
|
|
e928e1db4e | ||
|
|
f5b1962b0e | ||
|
|
0b13c6bc84 | ||
|
|
75365249cc | ||
|
|
1c2f5162bc | ||
|
|
209c68c85e | ||
|
|
023a617b30 | ||
|
|
286d302273 | ||
|
|
4091a2004e | ||
|
|
9d4a2bfb58 | ||
|
|
33e58d47ac | ||
|
|
056282f59b | ||
|
|
553b9601d1 | ||
|
|
a166f0b56a | ||
|
|
badc2ec0e9 | ||
|
|
9c3c719ce0 | ||
|
|
3594b5e2a8 | ||
|
|
99d0ed4a76 |
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"JSON": {
|
||||
"tab_size": 4
|
||||
}
|
||||
}
|
||||
625
Cargo.lock
generated
625
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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" }
|
||||
|
||||
@@ -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 . .
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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| {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
) {
|
||||
(
|
||||
|
||||
@@ -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 _};
|
||||
|
||||
@@ -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(®ion.id()) {
|
||||
window.hovered_region_ids.insert(ix, region.id());
|
||||
}
|
||||
// window.hovered_region_ids.insert(region.id());
|
||||
if !prev_hovered_regions.contains(®ion.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(®ion.id()) {
|
||||
if window.hovered_region_ids.remove(®ion.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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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")?;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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, _| {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, _| {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = "*"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user