Compare commits
49 Commits
v0.133.0-p
...
testing-in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6486bb7cc4 | ||
|
|
de0c8bc368 | ||
|
|
af555a6189 | ||
|
|
e4781ce5fa | ||
|
|
c128c3ac79 | ||
|
|
893b0eb371 | ||
|
|
8d05ee91b3 | ||
|
|
4abd55d340 | ||
|
|
8aac108d5a | ||
|
|
3d41adfc53 | ||
|
|
dee6b5629f | ||
|
|
266949d56b | ||
|
|
fade2f4843 | ||
|
|
e5f9780c05 | ||
|
|
a60785c423 | ||
|
|
cf2272a949 | ||
|
|
366d7e7728 | ||
|
|
4c780568bc | ||
|
|
7af96a15fe | ||
|
|
3eac581a62 | ||
|
|
c833a7e662 | ||
|
|
f176e8f0e4 | ||
|
|
7005f0b424 | ||
|
|
d3f6ca7a1e | ||
|
|
544bd490ac | ||
|
|
7065da2b98 | ||
|
|
0d6fb08b67 | ||
|
|
3ce4ff94ae | ||
|
|
21022f1644 | ||
|
|
11bcfea6d2 | ||
|
|
1cd34fdd9c | ||
|
|
530224527d | ||
|
|
0de2636324 | ||
|
|
7ec963664e | ||
|
|
019821d62c | ||
|
|
bb213b6e37 | ||
|
|
6a7761e620 | ||
|
|
031580f4dc | ||
|
|
1a27016123 | ||
|
|
d1425603f6 | ||
|
|
583a662ddc | ||
|
|
64617a0ede | ||
|
|
b673494f4d | ||
|
|
53f67a8241 | ||
|
|
06d2d9da5f | ||
|
|
9e88155a48 | ||
|
|
048fc7ad09 | ||
|
|
bd77232f65 | ||
|
|
facd04c902 |
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -173,6 +173,11 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We need to fetch more than one commit so that `script/draft-release-notes`
|
||||
# is able to diff between the current and previous tag.
|
||||
#
|
||||
# 25 was chosen arbitrarily.
|
||||
fetch-depth: 25
|
||||
clean: false
|
||||
submodules: "recursive"
|
||||
|
||||
@@ -206,7 +211,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p target/
|
||||
script/draft-release-notes "$version" "$channel" > target/release-notes.md
|
||||
# Ignore any errors that occur while drafting release notes to not fail the build.
|
||||
script/draft-release-notes "$version" "$channel" > target/release-notes.md || true
|
||||
|
||||
- name: Generate license file
|
||||
run: script/generate-licenses
|
||||
|
||||
61
Cargo.lock
generated
61
Cargo.lock
generated
@@ -284,21 +284,21 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.37.3+1.3.251"
|
||||
version = "0.38.0+1.3.281"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
|
||||
checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
|
||||
dependencies = [
|
||||
"libloading 0.7.4",
|
||||
"libloading 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ash-window"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b912285a7c29f3a8f87ca6f55afc48768624e5e33ec17dbd2f2075903f5e35ab"
|
||||
checksum = "52bca67b61cb81e5553babde81b8211f713cb6db79766f80168f3e5f40ea6c82"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.0",
|
||||
"raw-window-metal",
|
||||
]
|
||||
|
||||
@@ -1479,7 +1479,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
|
||||
source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1500,7 +1500,7 @@ dependencies = [
|
||||
"mint",
|
||||
"naga",
|
||||
"objc",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.0",
|
||||
"slab",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@@ -1509,7 +1509,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/kvark/blade?rev=810ec594358aafea29a4a3d8ab601d25292b2ce4#810ec594358aafea29a4a3d8ab601d25292b2ce4"
|
||||
source = "git+https://github.com/kvark/blade?rev=e82eec97691c3acdb43494484be60d661edfebf3#e82eec97691c3acdb43494484be60d661edfebf3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3390,6 +3390,7 @@ dependencies = [
|
||||
"smol",
|
||||
"snippet",
|
||||
"sum_tree",
|
||||
"task",
|
||||
"text",
|
||||
"theme",
|
||||
"time",
|
||||
@@ -3433,12 +3434,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embed-manifest"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"
|
||||
|
||||
[[package]]
|
||||
name = "emojis"
|
||||
version = "0.6.1"
|
||||
@@ -4492,9 +4487,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gpu-alloc-ash"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2424bc9be88170e1a56e57c25d3d0e2dfdd22e8f328e892786aeb4da1415732"
|
||||
checksum = "cbda7a18a29bc98c2e0de0435c347df935bf59489935d0cbd0b73f1679b6f79a"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"gpu-alloc-types",
|
||||
@@ -4561,7 +4556,6 @@ dependencies = [
|
||||
"postage",
|
||||
"profiling",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.0",
|
||||
"refineable",
|
||||
"resvg",
|
||||
@@ -4724,6 +4718,7 @@ dependencies = [
|
||||
"project",
|
||||
"rpc",
|
||||
"settings",
|
||||
"shellexpand",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -5552,12 +5547,9 @@ dependencies = [
|
||||
"regex",
|
||||
"rope",
|
||||
"rust-embed",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"shellexpand",
|
||||
"smol",
|
||||
"task",
|
||||
"text",
|
||||
@@ -5568,12 +5560,10 @@ dependencies = [
|
||||
"tree-sitter-c",
|
||||
"tree-sitter-cpp",
|
||||
"tree-sitter-css",
|
||||
"tree-sitter-elixir",
|
||||
"tree-sitter-embedded-template",
|
||||
"tree-sitter-go",
|
||||
"tree-sitter-gomod",
|
||||
"tree-sitter-gowork",
|
||||
"tree-sitter-heex",
|
||||
"tree-sitter-jsdoc",
|
||||
"tree-sitter-json 0.20.0",
|
||||
"tree-sitter-markdown",
|
||||
@@ -7731,14 +7721,14 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-metal"
|
||||
version = "0.3.2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac4ea493258d54c24cb46aa9345d099e58e2ea3f30dd63667fc54fc892f18e76"
|
||||
checksum = "76e8caa82e31bb98fee12fa8f051c94a6aa36b07cddb03f0d4fc558988360ff1"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"objc",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9488,7 +9478,6 @@ dependencies = [
|
||||
"ctrlc",
|
||||
"dialoguer",
|
||||
"editor",
|
||||
"embed-manifest",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indoc",
|
||||
@@ -9504,6 +9493,7 @@ dependencies = [
|
||||
"strum",
|
||||
"theme",
|
||||
"ui",
|
||||
"winresource",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9805,6 +9795,7 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"hex",
|
||||
"parking_lot",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json_lenient",
|
||||
@@ -9817,7 +9808,6 @@ dependencies = [
|
||||
name = "tasks_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"editor",
|
||||
"file_icons",
|
||||
"fuzzy",
|
||||
@@ -10519,7 +10509,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.20.100"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter?rev=7f21c3b98c0749ac192da67a0d65dfe3eabc4a63#7f21c3b98c0749ac192da67a0d65dfe3eabc4a63"
|
||||
source = "git+https://github.com/tree-sitter/tree-sitter?rev=528bcd2274814ca53711a57d71d1e3cf7abd73fe#528bcd2274814ca53711a57d71d1e3cf7abd73fe"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
@@ -11101,6 +11091,7 @@ dependencies = [
|
||||
"futures 0.3.28",
|
||||
"gpui",
|
||||
"indoc",
|
||||
"itertools 0.11.0",
|
||||
"language",
|
||||
"log",
|
||||
"lsp",
|
||||
@@ -12607,7 +12598,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.133.0"
|
||||
version = "0.134.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -12633,7 +12624,6 @@ dependencies = [
|
||||
"db",
|
||||
"diagnostics",
|
||||
"editor",
|
||||
"embed-manifest",
|
||||
"env_logger",
|
||||
"extension",
|
||||
"extensions_ui",
|
||||
@@ -12724,7 +12714,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_dart"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -12736,6 +12726,13 @@ dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_elixir"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zed_elm"
|
||||
version = "0.0.1"
|
||||
|
||||
@@ -110,6 +110,7 @@ members = [
|
||||
"extensions/csharp",
|
||||
"extensions/dart",
|
||||
"extensions/deno",
|
||||
"extensions/elixir",
|
||||
"extensions/elm",
|
||||
"extensions/emmet",
|
||||
"extensions/erlang",
|
||||
@@ -249,9 +250,8 @@ async-recursion = "1.0.0"
|
||||
async-tar = "0.4.2"
|
||||
async-trait = "0.1"
|
||||
bitflags = "2.4.2"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "810ec594358aafea29a4a3d8ab601d25292b2ce4" }
|
||||
blade-rwh = { package = "raw-window-handle", version = "0.5" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e82eec97691c3acdb43494484be60d661edfebf3" }
|
||||
cap-std = "3.0"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
@@ -407,7 +407,7 @@ features = [
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "7f21c3b98c0749ac192da67a0d65dfe3eabc4a63" }
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "528bcd2274814ca53711a57d71d1e3cf7abd73fe" }
|
||||
# Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released.
|
||||
pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "30419d07660dc11a21e42ef4a7fa329600cff152" }
|
||||
|
||||
|
||||
@@ -297,13 +297,8 @@
|
||||
"ctrl-shift-k": "editor::DeleteLine",
|
||||
"alt-up": "editor::MoveLineUp",
|
||||
"alt-down": "editor::MoveLineDown",
|
||||
"ctrl-alt-shift-up": [
|
||||
"editor::DuplicateLine",
|
||||
{
|
||||
"move_upwards": true
|
||||
}
|
||||
],
|
||||
"ctrl-alt-shift-down": "editor::DuplicateLine",
|
||||
"ctrl-alt-shift-up": "editor::DuplicateLineUp",
|
||||
"ctrl-alt-shift-down": "editor::DuplicateLineDown",
|
||||
"ctrl-shift-left": "editor::SelectToPreviousWordStart",
|
||||
"ctrl-shift-right": "editor::SelectToNextWordEnd",
|
||||
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
|
||||
@@ -593,12 +588,6 @@
|
||||
"tab": "channel_modal::ToggleMode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "ChatPanel > MessageEditor",
|
||||
"bindings": {
|
||||
"escape": "chat_panel::CloseReplyPreview"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "FileFinder",
|
||||
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
|
||||
|
||||
@@ -212,7 +212,8 @@
|
||||
"context": "AssistantChat > Editor", // Used in the assistant2 crate
|
||||
"bindings": {
|
||||
"enter": ["assistant2::Submit", "Simple"],
|
||||
"cmd-enter": ["assistant2::Submit", "Codebase"]
|
||||
"cmd-enter": ["assistant2::Submit", "Codebase"],
|
||||
"escape": "assistant2::Cancel"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"cmd-q": "storybook::Quit"
|
||||
"cmd-q": "storybook::Quit",
|
||||
"backspace": "editor::Backspace",
|
||||
"delete": "editor::Delete",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -555,27 +555,6 @@
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
// Settings specific to our elixir integration
|
||||
"elixir": {
|
||||
// Change the LSP zed uses for elixir.
|
||||
// Note that changing this setting requires a restart of Zed
|
||||
// to take effect.
|
||||
//
|
||||
// May take 3 values:
|
||||
// 1. Use the standard ElixirLS, this is the default
|
||||
// "lsp": "elixir_ls"
|
||||
// 2. Use the experimental NextLs
|
||||
// "lsp": "next_ls",
|
||||
// 3. Use a language server installed locally on your machine:
|
||||
// "lsp": {
|
||||
// "local": {
|
||||
// "path": "~/next-ls/bin/start",
|
||||
// "arguments": ["--stdio"]
|
||||
// }
|
||||
// },
|
||||
//
|
||||
"lsp": "elixir_ls"
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// An object whose keys are language names, and whose values
|
||||
// are arrays of filenames or extensions of files that should
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Context as _;
|
||||
/// This example creates a basic Chat UI with a function for rolling a die.
|
||||
use anyhow::{Context as _, Result};
|
||||
use assets::Assets;
|
||||
use assistant2::AssistantPanel;
|
||||
use assistant_tooling::{LanguageModelTool, ToolRegistry};
|
||||
@@ -83,9 +84,32 @@ struct DiceRoll {
|
||||
rolls: Vec<DieRoll>,
|
||||
}
|
||||
|
||||
pub struct DiceView {
|
||||
result: Result<DiceRoll>,
|
||||
}
|
||||
|
||||
impl Render for DiceView {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let output = match &self.result {
|
||||
Ok(output) => output,
|
||||
Err(_) => return "Somehow dice failed 🎲".into_any_element(),
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.children(
|
||||
output
|
||||
.rolls
|
||||
.iter()
|
||||
.map(|roll| div().p_2().child(roll.render())),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageModelTool for RollDiceTool {
|
||||
type Input = DiceParams;
|
||||
type Output = DiceRoll;
|
||||
type View = DiceView;
|
||||
|
||||
fn name(&self) -> String {
|
||||
"roll_dice".to_string()
|
||||
@@ -110,23 +134,21 @@ impl LanguageModelTool for RollDiceTool {
|
||||
return Task::ready(Ok(DiceRoll { rolls }));
|
||||
}
|
||||
|
||||
fn render(
|
||||
_tool_call_id: &str,
|
||||
_input: &Self::Input,
|
||||
output: &Self::Output,
|
||||
_cx: &mut WindowContext,
|
||||
) -> gpui::AnyElement {
|
||||
h_flex()
|
||||
.children(
|
||||
output
|
||||
.rolls
|
||||
.iter()
|
||||
.map(|roll| div().p_2().child(roll.render())),
|
||||
)
|
||||
.into_any_element()
|
||||
fn new_view(
|
||||
_tool_call_id: String,
|
||||
_input: Self::Input,
|
||||
result: Result<Self::Output>,
|
||||
cx: &mut WindowContext,
|
||||
) -> gpui::View<Self::View> {
|
||||
cx.new_view(|_cx| DiceView { result })
|
||||
}
|
||||
|
||||
fn format(_input: &Self::Input, output: &Self::Output) -> String {
|
||||
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
|
||||
let output = match output {
|
||||
Ok(output) => output,
|
||||
Err(_) => return "Somehow dice failed 🎲".to_string(),
|
||||
};
|
||||
|
||||
let mut result = String::new();
|
||||
for roll in &output.rolls {
|
||||
let die = &roll.die;
|
||||
|
||||
@@ -34,8 +34,6 @@ pub use assistant_settings::AssistantSettings;
|
||||
|
||||
const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
|
||||
|
||||
// gpui::actions!(assistant, [Submit]);
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
|
||||
pub struct Submit(SubmitMode);
|
||||
|
||||
@@ -50,7 +48,7 @@ pub enum SubmitMode {
|
||||
Codebase,
|
||||
}
|
||||
|
||||
gpui::actions!(assistant2, [ToggleFocus]);
|
||||
gpui::actions!(assistant2, [Cancel, ToggleFocus]);
|
||||
gpui::impl_actions!(assistant2, [Submit]);
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
@@ -256,6 +254,21 @@ impl AssistantChat {
|
||||
})
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||
if self.pending_completion.take().is_none() {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
|
||||
if message.body.text.is_empty() {
|
||||
self.pop_message(cx);
|
||||
} else {
|
||||
self.push_new_user_message(false, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
|
||||
let Some(focused_message_id) = self.focused_message_id(cx) else {
|
||||
log::error!("unexpected state: no user message editor is focused.");
|
||||
@@ -282,6 +295,7 @@ impl AssistantChat {
|
||||
.focus_handle(cx)
|
||||
.contains_focused(cx);
|
||||
this.push_new_user_message(focus, cx);
|
||||
this.pending_completion = None;
|
||||
})
|
||||
.context("Failed to push new user message")
|
||||
.log_err();
|
||||
@@ -300,7 +314,8 @@ impl AssistantChat {
|
||||
let completion = this.update(cx, |this, cx| {
|
||||
this.push_new_assistant_message(cx);
|
||||
|
||||
let definitions = if call_count < limit && matches!(mode, SubmitMode::Codebase)
|
||||
let definitions = if call_count < limit
|
||||
&& matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
|
||||
{
|
||||
this.tool_registry.definitions()
|
||||
} else {
|
||||
@@ -308,9 +323,11 @@ impl AssistantChat {
|
||||
};
|
||||
call_count += 1;
|
||||
|
||||
let messages = this.completion_messages(cx);
|
||||
|
||||
CompletionProvider::get(cx).complete(
|
||||
this.model.clone(),
|
||||
this.completion_messages(cx),
|
||||
messages,
|
||||
Vec::new(),
|
||||
1.0,
|
||||
definitions,
|
||||
@@ -393,6 +410,10 @@ impl AssistantChat {
|
||||
}
|
||||
|
||||
let tools = join_all(tool_tasks.into_iter()).await;
|
||||
// If the WindowContext went away for any tool's view we don't include it
|
||||
// especially since the below call would fail for the same reason.
|
||||
let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
if let Some(ChatMessage::Assistant(AssistantMessage { tool_calls, .. })) =
|
||||
this.messages.last_mut()
|
||||
@@ -453,6 +474,17 @@ impl AssistantChat {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn pop_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if self.messages.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.messages.pop();
|
||||
self.list_state
|
||||
.splice(self.messages.len()..self.messages.len() + 1, 0);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(index) = self.messages.iter().position(|message| match message {
|
||||
ChatMessage::User(message) => message.id == last_message_id,
|
||||
@@ -536,10 +568,9 @@ impl AssistantChat {
|
||||
let result = &tool_call.result;
|
||||
let name = tool_call.name.clone();
|
||||
match result {
|
||||
Some(result) => div()
|
||||
.p_2()
|
||||
.child(result.render(&name, &tool_call.id, cx))
|
||||
.into_any(),
|
||||
Some(result) => {
|
||||
div().p_2().child(result.into_any_element(&name)).into_any()
|
||||
}
|
||||
None => div()
|
||||
.p_2()
|
||||
.child(Label::new(name).color(Color::Modified))
|
||||
@@ -552,7 +583,7 @@ impl AssistantChat {
|
||||
}
|
||||
}
|
||||
|
||||
fn completion_messages(&self, cx: &WindowContext) -> Vec<CompletionMessage> {
|
||||
fn completion_messages(&self, cx: &mut WindowContext) -> Vec<CompletionMessage> {
|
||||
let mut completion_messages = Vec::new();
|
||||
|
||||
for message in &self.messages {
|
||||
@@ -677,6 +708,7 @@ impl Render for AssistantChat {
|
||||
.flex_1()
|
||||
.v_flex()
|
||||
.key_context("AssistantChat")
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.text_color(Color::Default.color(cx))
|
||||
.child(self.render_model_dropdown(cx))
|
||||
.child(list(self.list_state.clone()).flex_1())
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use assistant_tooling::LanguageModelTool;
|
||||
use gpui::{prelude::*, AnyElement, AppContext, Model, Task};
|
||||
use gpui::{prelude::*, AppContext, Model, Task};
|
||||
use project::Fs;
|
||||
use schemars::JsonSchema;
|
||||
use semantic_index::ProjectIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
div, prelude::*, CollapsibleContainer, Color, Icon, IconName, Label, SharedString,
|
||||
@@ -14,11 +14,13 @@ use util::ResultExt as _;
|
||||
|
||||
const DEFAULT_SEARCH_LIMIT: usize = 20;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct CodebaseExcerpt {
|
||||
path: SharedString,
|
||||
text: SharedString,
|
||||
score: f32,
|
||||
element_id: ElementId,
|
||||
expanded: bool,
|
||||
}
|
||||
|
||||
// Note: Comments on a `LanguageModelTool::Input` become descriptions on the generated JSON schema as shown to the language model.
|
||||
@@ -32,6 +34,79 @@ pub struct CodebaseQuery {
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
pub struct ProjectIndexView {
|
||||
input: CodebaseQuery,
|
||||
output: Result<Vec<CodebaseExcerpt>>,
|
||||
}
|
||||
|
||||
impl ProjectIndexView {
|
||||
fn toggle_expanded(&mut self, element_id: ElementId, cx: &mut ViewContext<Self>) {
|
||||
if let Ok(excerpts) = &mut self.output {
|
||||
if let Some(excerpt) = excerpts
|
||||
.iter_mut()
|
||||
.find(|excerpt| excerpt.element_id == element_id)
|
||||
{
|
||||
excerpt.expanded = !excerpt.expanded;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ProjectIndexView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let query = self.input.query.clone();
|
||||
|
||||
let result = &self.output;
|
||||
|
||||
let excerpts = match result {
|
||||
Err(err) => {
|
||||
return div().child(Label::new(format!("Error: {}", err)).color(Color::Error));
|
||||
}
|
||||
Ok(excerpts) => excerpts,
|
||||
};
|
||||
|
||||
div()
|
||||
.v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.child(Label::new("Query: ").color(Color::Modified))
|
||||
.child(Label::new(query).color(Color::Muted)),
|
||||
),
|
||||
)
|
||||
.children(excerpts.iter().map(|excerpt| {
|
||||
let element_id = excerpt.element_id.clone();
|
||||
let expanded = excerpt.expanded;
|
||||
|
||||
CollapsibleContainer::new(element_id.clone(), expanded)
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::File).color(Color::Muted))
|
||||
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.toggle_expanded(element_id.clone(), cx);
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
excerpt.text.clone(), // todo!(): Show as an editor block
|
||||
),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProjectIndexTool {
|
||||
project_index: Model<ProjectIndex>,
|
||||
fs: Arc<dyn Fs>,
|
||||
@@ -47,6 +122,7 @@ impl ProjectIndexTool {
|
||||
impl LanguageModelTool for ProjectIndexTool {
|
||||
type Input = CodebaseQuery;
|
||||
type Output = Vec<CodebaseExcerpt>;
|
||||
type View = ProjectIndexView;
|
||||
|
||||
fn name(&self) -> String {
|
||||
"query_codebase".to_string()
|
||||
@@ -90,6 +166,8 @@ impl LanguageModelTool for ProjectIndexTool {
|
||||
}
|
||||
|
||||
anyhow::Ok(CodebaseExcerpt {
|
||||
element_id: ElementId::Name(nanoid::nanoid!().into()),
|
||||
expanded: false,
|
||||
path: path.to_string_lossy().to_string().into(),
|
||||
text: SharedString::from(text[start..end].to_string()),
|
||||
score: result.score,
|
||||
@@ -106,71 +184,37 @@ impl LanguageModelTool for ProjectIndexTool {
|
||||
})
|
||||
}
|
||||
|
||||
fn render(
|
||||
_tool_call_id: &str,
|
||||
input: &Self::Input,
|
||||
excerpts: &Self::Output,
|
||||
fn new_view(
|
||||
_tool_call_id: String,
|
||||
input: Self::Input,
|
||||
output: Result<Self::Output>,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
let query = input.query.clone();
|
||||
|
||||
div()
|
||||
.v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.child(Label::new("Query: ").color(Color::Modified))
|
||||
.child(Label::new(query).color(Color::Muted)),
|
||||
),
|
||||
)
|
||||
.children(excerpts.iter().map(|excerpt| {
|
||||
// This render doesn't have state/model, so we can't use the listener
|
||||
// let expanded = excerpt.expanded;
|
||||
// let element_id = excerpt.element_id.clone();
|
||||
let element_id = ElementId::Name(nanoid::nanoid!().into());
|
||||
let expanded = false;
|
||||
|
||||
CollapsibleContainer::new(element_id.clone(), expanded)
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Icon::new(IconName::File).color(Color::Muted))
|
||||
.child(Label::new(excerpt.path.clone()).color(Color::Muted)),
|
||||
)
|
||||
// .on_click(cx.listener(move |this, _, cx| {
|
||||
// this.toggle_expanded(element_id.clone(), cx);
|
||||
// }))
|
||||
.child(
|
||||
div()
|
||||
.p_2()
|
||||
.rounded_md()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
excerpt.text.clone(), // todo!(): Show as an editor block
|
||||
),
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
) -> gpui::View<Self::View> {
|
||||
cx.new_view(|_cx| ProjectIndexView { input, output })
|
||||
}
|
||||
|
||||
fn format(_input: &Self::Input, excerpts: &Self::Output) -> String {
|
||||
let mut body = "Semantic search results:\n".to_string();
|
||||
fn format(_input: &Self::Input, output: &Result<Self::Output>) -> String {
|
||||
match &output {
|
||||
Ok(excerpts) => {
|
||||
if excerpts.len() == 0 {
|
||||
return "No results found".to_string();
|
||||
}
|
||||
|
||||
for excerpt in excerpts {
|
||||
body.push_str("Excerpt from ");
|
||||
body.push_str(excerpt.path.as_ref());
|
||||
body.push_str(", score ");
|
||||
body.push_str(&excerpt.score.to_string());
|
||||
body.push_str(":\n");
|
||||
body.push_str("~~~\n");
|
||||
body.push_str(excerpt.text.as_ref());
|
||||
body.push_str("~~~\n");
|
||||
let mut body = "Semantic search results:\n".to_string();
|
||||
|
||||
for excerpt in excerpts {
|
||||
body.push_str("Excerpt from ");
|
||||
body.push_str(excerpt.path.as_ref());
|
||||
body.push_str(", score ");
|
||||
body.push_str(&excerpt.score.to_string());
|
||||
body.push_str(":\n");
|
||||
body.push_str("~~~\n");
|
||||
body.push_str(excerpt.text.as_ref());
|
||||
body.push_str("~~~\n");
|
||||
}
|
||||
body
|
||||
}
|
||||
Err(err) => format!("Error: {}", err),
|
||||
}
|
||||
body
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui::{AnyElement, AppContext, Task, WindowContext};
|
||||
use std::{any::Any, collections::HashMap};
|
||||
use gpui::{Task, WindowContext};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::tool::{
|
||||
LanguageModelTool, ToolFunctionCall, ToolFunctionCallResult, ToolFunctionDefinition,
|
||||
};
|
||||
|
||||
pub struct ToolRegistry {
|
||||
tools: HashMap<String, Box<dyn Fn(&ToolFunctionCall, &AppContext) -> Task<ToolFunctionCall>>>,
|
||||
tools: HashMap<
|
||||
String,
|
||||
Box<dyn Fn(&ToolFunctionCall, &mut WindowContext) -> Task<Result<ToolFunctionCall>>>,
|
||||
>,
|
||||
definitions: Vec<ToolFunctionDefinition>,
|
||||
}
|
||||
|
||||
@@ -24,77 +27,45 @@ impl ToolRegistry {
|
||||
}
|
||||
|
||||
pub fn register<T: 'static + LanguageModelTool>(&mut self, tool: T) -> Result<()> {
|
||||
fn render<T: 'static + LanguageModelTool>(
|
||||
tool_call_id: &str,
|
||||
input: &Box<dyn Any>,
|
||||
output: &Box<dyn Any>,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
T::render(
|
||||
tool_call_id,
|
||||
input.as_ref().downcast_ref::<T::Input>().unwrap(),
|
||||
output.as_ref().downcast_ref::<T::Output>().unwrap(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
fn format<T: 'static + LanguageModelTool>(
|
||||
input: &Box<dyn Any>,
|
||||
output: &Box<dyn Any>,
|
||||
) -> String {
|
||||
T::format(
|
||||
input.as_ref().downcast_ref::<T::Input>().unwrap(),
|
||||
output.as_ref().downcast_ref::<T::Output>().unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
self.definitions.push(tool.definition());
|
||||
let name = tool.name();
|
||||
let previous = self.tools.insert(
|
||||
name.clone(),
|
||||
Box::new(move |tool_call: &ToolFunctionCall, cx: &AppContext| {
|
||||
let name = tool_call.name.clone();
|
||||
let arguments = tool_call.arguments.clone();
|
||||
let id = tool_call.id.clone();
|
||||
// registry.call(tool_call, cx)
|
||||
Box::new(
|
||||
move |tool_call: &ToolFunctionCall, cx: &mut WindowContext| {
|
||||
let name = tool_call.name.clone();
|
||||
let arguments = tool_call.arguments.clone();
|
||||
let id = tool_call.id.clone();
|
||||
|
||||
let Ok(input) = serde_json::from_str::<T::Input>(arguments.as_str()) else {
|
||||
return Task::ready(ToolFunctionCall {
|
||||
id,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
result: Some(ToolFunctionCallResult::ParsingFailed),
|
||||
});
|
||||
};
|
||||
|
||||
let result = tool.execute(&input, cx);
|
||||
|
||||
cx.spawn(move |_cx| async move {
|
||||
match result.await {
|
||||
Ok(result) => {
|
||||
let result: T::Output = result;
|
||||
ToolFunctionCall {
|
||||
id,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
result: Some(ToolFunctionCallResult::Finished {
|
||||
input: Box::new(input),
|
||||
output: Box::new(result),
|
||||
render_fn: render::<T>,
|
||||
format_fn: format::<T>,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Err(_error) => ToolFunctionCall {
|
||||
let Ok(input) = serde_json::from_str::<T::Input>(arguments.as_str()) else {
|
||||
return Task::ready(Ok(ToolFunctionCall {
|
||||
id,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
result: Some(ToolFunctionCallResult::ExecutionFailed {
|
||||
input: Box::new(input),
|
||||
result: Some(ToolFunctionCallResult::ParsingFailed),
|
||||
}));
|
||||
};
|
||||
|
||||
let result = tool.execute(&input, cx);
|
||||
|
||||
cx.spawn(move |mut cx| async move {
|
||||
let result: Result<T::Output> = result.await;
|
||||
let for_model = T::format(&input, &result);
|
||||
let view = cx.update(|cx| T::new_view(id.clone(), input, result, cx))?;
|
||||
|
||||
Ok(ToolFunctionCall {
|
||||
id,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
result: Some(ToolFunctionCallResult::Finished {
|
||||
view: view.into(),
|
||||
for_model,
|
||||
}),
|
||||
},
|
||||
}
|
||||
})
|
||||
}),
|
||||
})
|
||||
})
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if previous.is_some() {
|
||||
@@ -104,7 +75,12 @@ impl ToolRegistry {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn call(&self, tool_call: &ToolFunctionCall, cx: &AppContext) -> Task<ToolFunctionCall> {
|
||||
/// Task yields an error if the window for the given WindowContext is closed before the task completes.
|
||||
pub fn call(
|
||||
&self,
|
||||
tool_call: &ToolFunctionCall,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<ToolFunctionCall>> {
|
||||
let name = tool_call.name.clone();
|
||||
let arguments = tool_call.arguments.clone();
|
||||
let id = tool_call.id.clone();
|
||||
@@ -113,12 +89,12 @@ impl ToolRegistry {
|
||||
Some(tool) => tool,
|
||||
None => {
|
||||
let name = name.clone();
|
||||
return Task::ready(ToolFunctionCall {
|
||||
return Task::ready(Ok(ToolFunctionCall {
|
||||
id,
|
||||
name: name.clone(),
|
||||
arguments,
|
||||
result: Some(ToolFunctionCallResult::NoSuchTool),
|
||||
});
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,12 +104,10 @@ impl ToolRegistry {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
use gpui::View;
|
||||
use gpui::{div, prelude::*, Render, TestAppContext};
|
||||
use schemars::schema_for;
|
||||
|
||||
use gpui::{div, AnyElement, Element, ParentElement, TestAppContext, WindowContext};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
@@ -155,9 +129,20 @@ mod test {
|
||||
unit: String,
|
||||
}
|
||||
|
||||
struct WeatherView {
|
||||
result: WeatherResult,
|
||||
}
|
||||
|
||||
impl Render for WeatherView {
|
||||
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
div().child(format!("temperature: {}", self.result.temperature))
|
||||
}
|
||||
}
|
||||
|
||||
impl LanguageModelTool for WeatherTool {
|
||||
type Input = WeatherQuery;
|
||||
type Output = WeatherResult;
|
||||
type View = WeatherView;
|
||||
|
||||
fn name(&self) -> String {
|
||||
"get_current_weather".to_string()
|
||||
@@ -167,7 +152,11 @@ mod test {
|
||||
"Fetches the current weather for a given location.".to_string()
|
||||
}
|
||||
|
||||
fn execute(&self, input: &WeatherQuery, _cx: &AppContext) -> Task<Result<Self::Output>> {
|
||||
fn execute(
|
||||
&self,
|
||||
input: &Self::Input,
|
||||
_cx: &gpui::AppContext,
|
||||
) -> Task<Result<Self::Output>> {
|
||||
let _location = input.location.clone();
|
||||
let _unit = input.unit.clone();
|
||||
|
||||
@@ -176,25 +165,20 @@ mod test {
|
||||
Task::ready(Ok(weather))
|
||||
}
|
||||
|
||||
fn render(
|
||||
_tool_call_id: &str,
|
||||
_input: &Self::Input,
|
||||
output: &Self::Output,
|
||||
_cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.child(format!(
|
||||
"The current temperature in {} is {} {}",
|
||||
output.location, output.temperature, output.unit
|
||||
))
|
||||
.into_any()
|
||||
fn new_view(
|
||||
_tool_call_id: String,
|
||||
_input: Self::Input,
|
||||
result: Result<Self::Output>,
|
||||
cx: &mut WindowContext,
|
||||
) -> View<Self::View> {
|
||||
cx.new_view(|_cx| {
|
||||
let result = result.unwrap();
|
||||
WeatherView { result }
|
||||
})
|
||||
}
|
||||
|
||||
fn format(_input: &Self::Input, output: &Self::Output) -> String {
|
||||
format!(
|
||||
"The current temperature in {} is {} {}",
|
||||
output.location, output.temperature, output.unit
|
||||
)
|
||||
fn format(_: &Self::Input, output: &Result<Self::Output>) -> String {
|
||||
serde_json::to_string(&output.as_ref().unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,20 +198,20 @@ mod test {
|
||||
|
||||
registry.register(tool).unwrap();
|
||||
|
||||
let _result = cx
|
||||
.update(|cx| {
|
||||
registry.call(
|
||||
&ToolFunctionCall {
|
||||
name: "get_current_weather".to_string(),
|
||||
arguments: r#"{ "location": "San Francisco", "unit": "Celsius" }"#
|
||||
.to_string(),
|
||||
id: "test-123".to_string(),
|
||||
result: None,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
// let _result = cx
|
||||
// .update(|cx| {
|
||||
// registry.call(
|
||||
// &ToolFunctionCall {
|
||||
// name: "get_current_weather".to_string(),
|
||||
// arguments: r#"{ "location": "San Francisco", "unit": "Celsius" }"#
|
||||
// .to_string(),
|
||||
// id: "test-123".to_string(),
|
||||
// result: None,
|
||||
// },
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// .await;
|
||||
|
||||
// assert!(result.is_ok());
|
||||
// let result = result.unwrap();
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use gpui::{div, AnyElement, AppContext, Element, ParentElement as _, Task, WindowContext};
|
||||
use gpui::{AnyElement, AnyView, AppContext, IntoElement as _, Render, Task, View, WindowContext};
|
||||
use schemars::{schema::RootSchema, schema_for, JsonSchema};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt::{Debug, Display},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
pub struct ToolFunctionCall {
|
||||
@@ -19,71 +16,29 @@ pub struct ToolFunctionCall {
|
||||
pub enum ToolFunctionCallResult {
|
||||
NoSuchTool,
|
||||
ParsingFailed,
|
||||
ExecutionFailed {
|
||||
input: Box<dyn Any>,
|
||||
},
|
||||
Finished {
|
||||
input: Box<dyn Any>,
|
||||
output: Box<dyn Any>,
|
||||
render_fn: fn(
|
||||
// tool_call_id
|
||||
&str,
|
||||
// LanguageModelTool::Input
|
||||
&Box<dyn Any>,
|
||||
// LanguageModelTool::Output
|
||||
&Box<dyn Any>,
|
||||
&mut WindowContext,
|
||||
) -> AnyElement,
|
||||
format_fn: fn(
|
||||
// LanguageModelTool::Input
|
||||
&Box<dyn Any>,
|
||||
// LanguageModelTool::Output
|
||||
&Box<dyn Any>,
|
||||
) -> String,
|
||||
},
|
||||
Finished { for_model: String, view: AnyView },
|
||||
}
|
||||
|
||||
impl ToolFunctionCallResult {
|
||||
pub fn render(
|
||||
&self,
|
||||
tool_name: &str,
|
||||
tool_call_id: &str,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
pub fn format(&self, name: &String) -> String {
|
||||
match self {
|
||||
ToolFunctionCallResult::NoSuchTool => {
|
||||
div().child(format!("no such tool {tool_name}")).into_any()
|
||||
ToolFunctionCallResult::NoSuchTool => format!("No tool for {name}"),
|
||||
ToolFunctionCallResult::ParsingFailed => {
|
||||
format!("Unable to parse arguments for {name}")
|
||||
}
|
||||
ToolFunctionCallResult::ParsingFailed => div()
|
||||
.child(format!("failed to parse input for tool {tool_name}"))
|
||||
.into_any(),
|
||||
ToolFunctionCallResult::ExecutionFailed { .. } => div()
|
||||
.child(format!("failed to execute tool {tool_name}"))
|
||||
.into_any(),
|
||||
ToolFunctionCallResult::Finished {
|
||||
input,
|
||||
output,
|
||||
render_fn,
|
||||
..
|
||||
} => render_fn(tool_call_id, input, output, cx),
|
||||
ToolFunctionCallResult::Finished { for_model, .. } => for_model.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self, tool: &str) -> String {
|
||||
pub fn into_any_element(&self, name: &String) -> AnyElement {
|
||||
match self {
|
||||
ToolFunctionCallResult::NoSuchTool => format!("no such tool {tool}"),
|
||||
ToolFunctionCallResult::NoSuchTool => {
|
||||
format!("Language Model attempted to call {name}").into_any_element()
|
||||
}
|
||||
ToolFunctionCallResult::ParsingFailed => {
|
||||
format!("failed to parse input for tool {tool}")
|
||||
format!("Language Model called {name} with bad arguments").into_any_element()
|
||||
}
|
||||
ToolFunctionCallResult::ExecutionFailed { input: _input } => {
|
||||
format!("failed to execute tool {tool}")
|
||||
}
|
||||
ToolFunctionCallResult::Finished {
|
||||
input,
|
||||
output,
|
||||
format_fn,
|
||||
..
|
||||
} => format_fn(input, output),
|
||||
ToolFunctionCallResult::Finished { view, .. } => view.clone().into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,19 +60,6 @@ impl Display for ToolFunctionDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ToolFunctionDefinition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let schema = serde_json::to_string(&self.parameters).ok();
|
||||
let schema = schema.unwrap_or("None".to_string());
|
||||
|
||||
f.debug_struct("ToolFunctionDefinition")
|
||||
.field("name", &self.name)
|
||||
.field("description", &self.description)
|
||||
.field("parameters", &schema)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LanguageModelTool {
|
||||
/// The input type that will be passed in to `execute` when the tool is called
|
||||
/// by the language model.
|
||||
@@ -126,6 +68,8 @@ pub trait LanguageModelTool {
|
||||
/// The output returned by executing the tool.
|
||||
type Output: 'static;
|
||||
|
||||
type View: Render;
|
||||
|
||||
/// The name of the tool is exposed to the language model to allow
|
||||
/// the model to pick which tools to use. As this name is used to
|
||||
/// identify the tool within a tool registry, it should be unique.
|
||||
@@ -149,12 +93,12 @@ pub trait LanguageModelTool {
|
||||
/// Execute the tool
|
||||
fn execute(&self, input: &Self::Input, cx: &AppContext) -> Task<Result<Self::Output>>;
|
||||
|
||||
fn render(
|
||||
tool_call_id: &str,
|
||||
input: &Self::Input,
|
||||
output: &Self::Output,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement;
|
||||
fn format(input: &Self::Input, output: &Result<Self::Output>) -> String;
|
||||
|
||||
fn format(input: &Self::Input, output: &Self::Output) -> String;
|
||||
fn new_view(
|
||||
tool_call_id: String,
|
||||
input: Self::Input,
|
||||
output: Result<Self::Output>,
|
||||
cx: &mut WindowContext,
|
||||
) -> View<Self::View>;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
||||
impl Render for Breadcrumbs {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
const MAX_SEGMENTS: usize = 12;
|
||||
let element = h_flex().text_ui();
|
||||
let element = h_flex().text_ui(cx);
|
||||
let Some(active_item) = self.active_item.as_ref() else {
|
||||
return element;
|
||||
};
|
||||
|
||||
@@ -36,6 +36,9 @@ struct Args {
|
||||
/// Custom Zed.app path
|
||||
#[arg(short, long)]
|
||||
bundle_path: Option<PathBuf>,
|
||||
/// Run zed in dev-server mode
|
||||
#[arg(long)]
|
||||
dev_server_token: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_path_with_position(
|
||||
@@ -67,6 +70,10 @@ fn main() -> Result<()> {
|
||||
|
||||
let bundle = Bundle::detect(args.bundle_path.as_deref()).context("Bundle detection")?;
|
||||
|
||||
if let Some(dev_server_token) = args.dev_server_token {
|
||||
return bundle.spawn(vec!["--dev-server-token".into(), dev_server_token]);
|
||||
}
|
||||
|
||||
if args.version {
|
||||
println!("{}", bundle.zed_version_string());
|
||||
return Ok(());
|
||||
@@ -169,6 +176,10 @@ mod linux {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn zed_version_string(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -202,6 +213,10 @@ mod windows {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn spawn(&self, _args: Vec<String>) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn zed_version_string(&self) -> String {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -217,7 +232,7 @@ mod mac_os {
|
||||
url::{CFURLCreateWithBytes, CFURL},
|
||||
};
|
||||
use core_services::{kLSLaunchDefaults, LSLaunchURLSpec, LSOpenFromURLSpec, TCFType};
|
||||
use std::{fs, path::Path, ptr};
|
||||
use std::{fs, path::Path, process::Command, ptr};
|
||||
|
||||
use cli::{CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME};
|
||||
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
|
||||
@@ -278,6 +293,15 @@ mod mac_os {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&self, args: Vec<String>) -> Result<()> {
|
||||
let path = match self {
|
||||
Self::App { app_bundle, .. } => app_bundle.join("Contents/MacOS/zed"),
|
||||
Self::LocalPath { executable, .. } => executable.clone(),
|
||||
};
|
||||
Command::new(path).args(args).status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn launch(&self) -> anyhow::Result<(IpcSender<CliRequest>, IpcReceiver<CliResponse>)> {
|
||||
let (server, server_name) =
|
||||
IpcOneShotServer::<IpcHandshake>::new().context("Handshake before Zed spawn")?;
|
||||
@@ -358,12 +382,12 @@ mod mac_os {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn spawn_channel_cli(
|
||||
channel: release_channel::ReleaseChannel,
|
||||
leftover_args: Vec<String>,
|
||||
) -> Result<()> {
|
||||
use anyhow::bail;
|
||||
use std::process::Command;
|
||||
|
||||
let app_id_prompt = format!("id of app \"{}\"", channel.display_name());
|
||||
let app_id_output = Command::new("osascript")
|
||||
|
||||
@@ -315,7 +315,7 @@ impl ChatPanel {
|
||||
None => {
|
||||
return div().child(
|
||||
h_flex()
|
||||
.text_ui_xs()
|
||||
.text_ui_xs(cx)
|
||||
.my_0p5()
|
||||
.px_0p5()
|
||||
.gap_x_1()
|
||||
@@ -350,7 +350,7 @@ impl ChatPanel {
|
||||
div().child(
|
||||
h_flex()
|
||||
.id(message_element_id)
|
||||
.text_ui_xs()
|
||||
.text_ui_xs(cx)
|
||||
.my_0p5()
|
||||
.px_0p5()
|
||||
.gap_x_1()
|
||||
@@ -495,7 +495,7 @@ impl ChatPanel {
|
||||
|this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.text_ui_sm()
|
||||
.text_ui_sm(cx)
|
||||
.child(
|
||||
div().absolute().child(
|
||||
Avatar::new(message.sender.avatar_uri.clone())
|
||||
@@ -539,7 +539,7 @@ impl ChatPanel {
|
||||
el.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.text_ui_sm()
|
||||
.text_ui_sm(cx)
|
||||
.id(element_id)
|
||||
.child(text.element("body".into(), cx)),
|
||||
)
|
||||
@@ -562,7 +562,7 @@ impl ChatPanel {
|
||||
div()
|
||||
.px_1()
|
||||
.rounded_md()
|
||||
.text_ui_xs()
|
||||
.text_ui_xs(cx)
|
||||
.bg(cx.theme().colors().background)
|
||||
.child("New messages"),
|
||||
)
|
||||
@@ -1003,7 +1003,7 @@ impl Render for ChatPanel {
|
||||
el.child(
|
||||
h_flex()
|
||||
.px_2()
|
||||
.text_ui_xs()
|
||||
.text_ui_xs(cx)
|
||||
.justify_between()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
|
||||
@@ -18,7 +18,7 @@ use project::{search::SearchQuery, Completion};
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, UiTextSize};
|
||||
use ui::{prelude::*, TextSize};
|
||||
|
||||
use crate::panel_settings::MessageEditorSettings;
|
||||
|
||||
@@ -523,7 +523,7 @@ impl Render for MessageEditor {
|
||||
},
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: UiTextSize::Small.rems().into(),
|
||||
font_size: TextSize::Small.rems(cx).into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.3),
|
||||
|
||||
@@ -34,7 +34,7 @@ impl ParentElement for CollabNotification {
|
||||
impl RenderOnce for CollabNotification {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.text_ui()
|
||||
.text_ui(cx)
|
||||
.justify_between()
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
|
||||
@@ -912,7 +912,7 @@ mod tests {
|
||||
display_map::{BlockContext, TransformBlock},
|
||||
DisplayPoint, GutterDimensions,
|
||||
};
|
||||
use gpui::{px, Stateful, TestAppContext, VisualTestContext, WindowContext};
|
||||
use gpui::{px, AvailableSpace, Stateful, TestAppContext, VisualTestContext};
|
||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
@@ -1049,67 +1049,66 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let editor = view.update(cx, |view, _| view.editor.clone());
|
||||
|
||||
view.next_notification(cx).await;
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(15, "collapsed context".into()),
|
||||
(16, "diagnostic header".into()),
|
||||
(25, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(15, "collapsed context".into()),
|
||||
(16, "diagnostic header".into()),
|
||||
(25, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
|
||||
// Cursor is at the first diagnostic
|
||||
view.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
|
||||
);
|
||||
});
|
||||
// Cursor is at the first diagnostic
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(12, 6)..DisplayPoint::new(12, 6)]
|
||||
);
|
||||
});
|
||||
|
||||
// Diagnostics are added for another earlier path.
|
||||
@@ -1138,78 +1137,77 @@ mod tests {
|
||||
});
|
||||
|
||||
view.next_notification(cx).await;
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "path header block".into()),
|
||||
(9, "diagnostic header".into()),
|
||||
(22, "collapsed context".into()),
|
||||
(23, "diagnostic header".into()),
|
||||
(32, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// consts.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "path header block".into()),
|
||||
(9, "diagnostic header".into()),
|
||||
(22, "collapsed context".into()),
|
||||
(23, "diagnostic header".into()),
|
||||
(32, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
|
||||
// Cursor keeps its position.
|
||||
view.editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// consts.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
|
||||
// Cursor keeps its position.
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.selections.display_ranges(cx),
|
||||
[DisplayPoint::new(19, 6)..DisplayPoint::new(19, 6)]
|
||||
);
|
||||
});
|
||||
|
||||
// Diagnostics are added to the first path
|
||||
@@ -1254,80 +1252,79 @@ mod tests {
|
||||
});
|
||||
|
||||
view.next_notification(cx).await;
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
(13, "path header block".into()),
|
||||
(15, "diagnostic header".into()),
|
||||
(28, "collapsed context".into()),
|
||||
(29, "diagnostic header".into()),
|
||||
(38, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// consts.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // supporting diagnostic
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
(13, "path header block".into()),
|
||||
(15, "diagnostic header".into()),
|
||||
(28, "collapsed context".into()),
|
||||
(29, "diagnostic header".into()),
|
||||
(38, "collapsed context".into()),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
//
|
||||
// consts.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"\n", // supporting diagnostic
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"const a: i32 = 'a';\n",
|
||||
"const b: i32 = c;\n",
|
||||
"\n", // supporting diagnostic
|
||||
//
|
||||
// main.rs
|
||||
//
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
" let x = vec![];\n",
|
||||
" let y = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" a(x);\n",
|
||||
" b(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" // comment 1\n",
|
||||
" // comment 2\n",
|
||||
" c(y);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" d(x);\n",
|
||||
"\n", // context ellipsis
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // filename
|
||||
"fn main() {\n",
|
||||
" let x = vec![];\n",
|
||||
"\n", // supporting diagnostic
|
||||
" let y = vec![];\n",
|
||||
" a(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
" b(y);\n",
|
||||
"\n", // context ellipsis
|
||||
" c(y);\n",
|
||||
" d(x);\n",
|
||||
"\n", // supporting diagnostic
|
||||
"}"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1364,6 +1361,7 @@ mod tests {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let editor = view.update(cx, |view, _| view.editor.clone());
|
||||
|
||||
// Two language servers start updating diagnostics
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -1397,27 +1395,25 @@ mod tests {
|
||||
|
||||
// Only the first language server's diagnostics are shown.
|
||||
cx.executor().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", //
|
||||
"b();",
|
||||
)
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", //
|
||||
"b();",
|
||||
)
|
||||
);
|
||||
|
||||
// The second language server finishes
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -1445,36 +1441,34 @@ mod tests {
|
||||
|
||||
// Both language server's diagnostics are shown.
|
||||
cx.executor().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(6, "collapsed context".into()),
|
||||
(7, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // context
|
||||
"b();\n", //
|
||||
"c();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(6, "collapsed context".into()),
|
||||
(7, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // context
|
||||
"b();\n", //
|
||||
"c();", // context
|
||||
)
|
||||
);
|
||||
|
||||
// Both language servers start updating diagnostics, and the first server finishes.
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -1513,37 +1507,35 @@ mod tests {
|
||||
|
||||
// Only the first language server's diagnostics are updated.
|
||||
cx.executor().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"c();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // context
|
||||
"c();\n", //
|
||||
"d();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"a();\n", // location
|
||||
"b();\n", //
|
||||
"c();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // context
|
||||
"c();\n", //
|
||||
"d();", // context
|
||||
)
|
||||
);
|
||||
|
||||
// The second language server finishes.
|
||||
project.update(cx, |project, cx| {
|
||||
@@ -1571,37 +1563,35 @@ mod tests {
|
||||
|
||||
// Both language servers' diagnostics are updated.
|
||||
cx.executor().run_until_parked();
|
||||
view.update(cx, |view, cx| {
|
||||
assert_eq!(
|
||||
editor_blocks(&view.editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // location
|
||||
"c();\n", //
|
||||
"d();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"c();\n", // context
|
||||
"d();\n", //
|
||||
"e();", // context
|
||||
)
|
||||
);
|
||||
});
|
||||
assert_eq!(
|
||||
editor_blocks(&editor, cx),
|
||||
[
|
||||
(0, "path header block".into()),
|
||||
(2, "diagnostic header".into()),
|
||||
(7, "collapsed context".into()),
|
||||
(8, "diagnostic header".into()),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
concat!(
|
||||
"\n", // filename
|
||||
"\n", // padding
|
||||
// diagnostic group 1
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"b();\n", // location
|
||||
"c();\n", //
|
||||
"d();\n", // context
|
||||
"\n", // collapsed context
|
||||
// diagnostic group 2
|
||||
"\n", // primary message
|
||||
"\n", // padding
|
||||
"c();\n", // context
|
||||
"d();\n", //
|
||||
"e();", // context
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
@@ -1618,45 +1608,58 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
fn editor_blocks(editor: &View<Editor>, cx: &mut WindowContext) -> Vec<(u32, SharedString)> {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
snapshot
|
||||
.blocks_in_range(0..snapshot.max_point().row())
|
||||
.enumerate()
|
||||
.filter_map(|(ix, (row, block))| {
|
||||
let name: SharedString = match block {
|
||||
TransformBlock::Custom(block) => cx.with_element_context({
|
||||
|cx| -> Option<SharedString> {
|
||||
let mut element = block.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_dimensions: &GutterDimensions::default(),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id: ix,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
});
|
||||
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
|
||||
element.interactivity().element_id.clone()?.try_into().ok()
|
||||
}
|
||||
})?,
|
||||
fn editor_blocks(
|
||||
editor: &View<Editor>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Vec<(u32, SharedString)> {
|
||||
let mut blocks = Vec::new();
|
||||
cx.draw(gpui::Point::default(), AvailableSpace::min_size(), |cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
blocks.extend(
|
||||
snapshot
|
||||
.blocks_in_range(0..snapshot.max_point().row())
|
||||
.enumerate()
|
||||
.filter_map(|(ix, (row, block))| {
|
||||
let name: SharedString = match block {
|
||||
TransformBlock::Custom(block) => {
|
||||
let mut element = block.render(&mut BlockContext {
|
||||
context: cx,
|
||||
anchor_x: px(0.),
|
||||
gutter_dimensions: &GutterDimensions::default(),
|
||||
line_height: px(0.),
|
||||
em_width: px(0.),
|
||||
max_width: px(0.),
|
||||
block_id: ix,
|
||||
editor_style: &editor::EditorStyle::default(),
|
||||
});
|
||||
let element = element.downcast_mut::<Stateful<Div>>().unwrap();
|
||||
element
|
||||
.interactivity()
|
||||
.element_id
|
||||
.clone()?
|
||||
.try_into()
|
||||
.ok()?
|
||||
}
|
||||
|
||||
TransformBlock::ExcerptHeader {
|
||||
starts_new_buffer, ..
|
||||
} => {
|
||||
if *starts_new_buffer {
|
||||
"path header block".into()
|
||||
} else {
|
||||
"collapsed context".into()
|
||||
}
|
||||
}
|
||||
};
|
||||
TransformBlock::ExcerptHeader {
|
||||
starts_new_buffer, ..
|
||||
} => {
|
||||
if *starts_new_buffer {
|
||||
"path header block".into()
|
||||
} else {
|
||||
"collapsed context".into()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some((row, name))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
Some((row, name))
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
div().into_any()
|
||||
});
|
||||
blocks
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
snippet.workspace = true
|
||||
sum_tree.workspace = true
|
||||
task.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
time_format.workspace = true
|
||||
|
||||
@@ -43,6 +43,12 @@ pub struct ToggleCodeActions {
|
||||
pub deployed_from_indicator: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ToggleTestRunner {
|
||||
#[serde(default)]
|
||||
pub deployed_from_indicator: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
pub struct ConfirmCompletion {
|
||||
#[serde(default)]
|
||||
|
||||
@@ -39,17 +39,15 @@ impl<'a> CommitAvatar<'a> {
|
||||
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
|
||||
|
||||
let element = cx.with_element_context(|cx| {
|
||||
match cx.use_cached_asset::<CommitAvatarAsset>(&avatar_url) {
|
||||
// Loading or no avatar found
|
||||
None | Some(None) => Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
.into_element()
|
||||
.into_any(),
|
||||
// Found
|
||||
Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
|
||||
}
|
||||
});
|
||||
let element = match cx.use_cached_asset::<CommitAvatarAsset>(&avatar_url) {
|
||||
// Loading or no avatar found
|
||||
None | Some(None) => Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
.into_element()
|
||||
.into_any(),
|
||||
// Found
|
||||
Some(Some(url)) => Avatar::new(url.to_string()).into_element().into_any(),
|
||||
};
|
||||
Some(element)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{EditorStyle, GutterDimensions};
|
||||
use collections::{Bound, HashMap, HashSet};
|
||||
use gpui::{AnyElement, ElementContext, Pixels};
|
||||
use gpui::{AnyElement, Pixels, WindowContext};
|
||||
use language::{BufferSnapshot, Chunk, Patch, Point};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
|
||||
use parking_lot::Mutex;
|
||||
@@ -82,7 +82,7 @@ pub enum BlockStyle {
|
||||
}
|
||||
|
||||
pub struct BlockContext<'a, 'b> {
|
||||
pub context: &'b mut ElementContext<'a>,
|
||||
pub context: &'b mut WindowContext<'a>,
|
||||
pub anchor_x: Pixels,
|
||||
pub max_width: Pixels,
|
||||
pub gutter_dimensions: &'b GutterDimensions,
|
||||
@@ -934,7 +934,7 @@ impl BlockDisposition {
|
||||
}
|
||||
|
||||
impl<'a> Deref for BlockContext<'a, '_> {
|
||||
type Target = ElementContext<'a>;
|
||||
type Target = WindowContext<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.context
|
||||
|
||||
@@ -33,6 +33,7 @@ mod persistence;
|
||||
mod rust_analyzer_ext;
|
||||
pub mod scroll;
|
||||
mod selections_collection;
|
||||
pub mod tasks;
|
||||
|
||||
#[cfg(test)]
|
||||
mod editor_tests;
|
||||
@@ -75,6 +76,7 @@ use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion_provider::*;
|
||||
pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
use language::Runnable;
|
||||
use language::{
|
||||
char_kind,
|
||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
@@ -82,6 +84,7 @@ use language::{
|
||||
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||
Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
use task::TaskTemplate;
|
||||
|
||||
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
|
||||
use lsp::{DiagnosticSeverity, LanguageServerId};
|
||||
@@ -96,7 +99,8 @@ use ordered_float::OrderedFloat;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||
use project::{
|
||||
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction,
|
||||
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath,
|
||||
ProjectTransaction, WorktreeId,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::*;
|
||||
@@ -388,6 +392,13 @@ impl Default for ScrollbarMarkerState {
|
||||
}
|
||||
}
|
||||
|
||||
struct RunnableTasks {
|
||||
templates: SmallVec<[TaskTemplate; 1]>,
|
||||
match_range: Range<usize>, // The equivalent of the newest selection,
|
||||
language: Arc<Language>, // For getting a context provider
|
||||
worktree: Option<WorktreeId>,
|
||||
}
|
||||
|
||||
/// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`]
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -3677,6 +3688,47 @@ impl Editor {
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn toggle_test_runner(&mut self, action: &ToggleTestRunner, cx: &mut ViewContext<Self>) {
|
||||
unimplemented!()
|
||||
// let mut context_menu = self.context_menu.write();
|
||||
// if matches!(context_menu.as_ref(), Some(ContextMenu::CodeActions(_))) {
|
||||
// *context_menu = None;
|
||||
// cx.notify();
|
||||
// return;
|
||||
// }
|
||||
// drop(context_menu);
|
||||
|
||||
// let deployed_from_indicator = action.deployed_from_indicator;
|
||||
// let mut task = self.code_actions_task.take();
|
||||
// cx.spawn(|this, mut cx| async move {
|
||||
// while let Some(prev_task) = task {
|
||||
// prev_task.await;
|
||||
// task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
|
||||
// }
|
||||
|
||||
// this.update(&mut cx, |this, cx| {
|
||||
// if this.focus_handle.is_focused(cx) {
|
||||
// if let Some((buffer, actions)) = this.available_code_actions.clone() {
|
||||
// this.completion_tasks.clear();
|
||||
// this.discard_inline_completion(cx);
|
||||
// *this.context_menu.write() =
|
||||
// Some(ContextMenu::CodeActions(CodeActionsMenu {
|
||||
// buffer,
|
||||
// actions,
|
||||
// selected_item: Default::default(),
|
||||
// scroll_handle: UniformListScrollHandle::default(),
|
||||
// deployed_from_indicator,
|
||||
// }));
|
||||
// cx.notify();
|
||||
// }
|
||||
// }
|
||||
// })?;
|
||||
|
||||
// Ok::<_, anyhow::Error>(())
|
||||
// })
|
||||
// .detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub fn confirm_code_action(
|
||||
&mut self,
|
||||
action: &ConfirmCodeAction,
|
||||
@@ -4199,6 +4251,28 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_test_run_indicator(
|
||||
&self,
|
||||
_style: &EditorStyle,
|
||||
is_active: bool,
|
||||
indicator: u32,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> IconButton {
|
||||
IconButton::new("code_actions_indicator", ui::IconName::Play)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.size(ui::ButtonSize::None)
|
||||
.icon_color(Color::Muted)
|
||||
.selected(is_active)
|
||||
.on_click(cx.listener(move |editor, _e, cx| {
|
||||
editor.toggle_test_runner(
|
||||
&ToggleTestRunner {
|
||||
deployed_from_indicator: Some(indicator),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn render_fold_indicators(
|
||||
&mut self,
|
||||
fold_data: Vec<Option<(FoldStatus, u32, bool)>>,
|
||||
@@ -7375,6 +7449,72 @@ impl Editor {
|
||||
self.select_larger_syntax_node_stack = stack;
|
||||
}
|
||||
|
||||
fn runnable_display_rows(
|
||||
&self,
|
||||
range: Range<Anchor>,
|
||||
snapshot: &DisplaySnapshot,
|
||||
cx: &WindowContext,
|
||||
) -> Vec<(u32, RunnableTasks)> {
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.runnable_ranges(range)
|
||||
.filter_map(|(multi_buffer_range, mut runnable)| {
|
||||
let (tasks, worktree_id) = self.resolve_runnable(&mut runnable, cx);
|
||||
if tasks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
multi_buffer_range.start.to_display_point(&snapshot).row(),
|
||||
RunnableTasks {
|
||||
templates: tasks,
|
||||
match_range: multi_buffer_range,
|
||||
language: runnable.language,
|
||||
worktree: worktree_id,
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn resolve_runnable(
|
||||
&self,
|
||||
runnable: &mut Runnable,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> (SmallVec<[TaskTemplate; 1]>, Option<WorktreeId>) {
|
||||
let Some(project) = self.project.as_ref() else {
|
||||
return Default::default();
|
||||
};
|
||||
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
|
||||
let worktree_id = project
|
||||
.buffer_for_id(runnable.buffer)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()));
|
||||
|
||||
(project.task_inventory().clone(), worktree_id)
|
||||
});
|
||||
|
||||
let inventory = inventory.read(cx);
|
||||
let tags = mem::take(&mut runnable.tags);
|
||||
(
|
||||
SmallVec::from_iter(
|
||||
tags.into_iter()
|
||||
.flat_map(|tag| {
|
||||
let tag = tag.0.clone();
|
||||
inventory
|
||||
.list_tasks(Some(runnable.language.clone()), worktree_id)
|
||||
.into_iter()
|
||||
.filter(move |(_, template)| {
|
||||
template.tags.iter().any(|source_tag| source_tag == &tag)
|
||||
})
|
||||
})
|
||||
.sorted_by_key(|(kind, _)| kind.to_owned())
|
||||
.map(|(_, template)| template),
|
||||
),
|
||||
worktree_id,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn move_to_enclosing_bracket(
|
||||
&mut self,
|
||||
_: &MoveToEnclosingBracket,
|
||||
@@ -7745,7 +7885,13 @@ impl Editor {
|
||||
.update(&mut cx, |editor, cx| {
|
||||
editor.navigate_to_hover_links(
|
||||
Some(kind),
|
||||
definitions.into_iter().map(HoverLink::Text).collect(),
|
||||
definitions
|
||||
.into_iter()
|
||||
.filter(|location| {
|
||||
hover_links::exclude_link_to_position(&buffer, &head, location, cx)
|
||||
})
|
||||
.map(HoverLink::Text)
|
||||
.collect::<Vec<_>>(),
|
||||
split,
|
||||
cx,
|
||||
)
|
||||
@@ -8982,6 +9128,10 @@ impl Editor {
|
||||
return;
|
||||
};
|
||||
|
||||
if buffer.read(cx).file().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let project = project.clone();
|
||||
let blame = cx.new_model(|cx| GitBlame::new(buffer, project, user_triggered, cx));
|
||||
self.blame_subscription = Some(cx.observe(&blame, |_, _, cx| cx.notify()));
|
||||
@@ -10323,7 +10473,19 @@ impl Render for Editor {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let text_style = match self.mode {
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => cx.text_style(),
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features,
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: FontWeight::NORMAL,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(settings.buffer_line_height.value()),
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
white_space: WhiteSpace::Normal,
|
||||
},
|
||||
EditorMode::Full => TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, GutterDimensions, HalfPageDown,
|
||||
HalfPageUp, HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
|
||||
SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
RunnableTasks, SelectPhase, Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
@@ -23,12 +23,11 @@ use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementContext,
|
||||
ElementInputHandler, Entity, Hitbox, Hsla, InteractiveElement, IntoElement,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
|
||||
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful,
|
||||
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
|
||||
ViewContext, WeakView, WindowContext,
|
||||
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
|
||||
Hitbox, Hsla, InteractiveElement, IntoElement, ModifiersChangedEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta,
|
||||
ScrollWheelEvent, ShapedLine, SharedString, Size, Stateful, StatefulInteractiveElement, Style,
|
||||
Styled, TextRun, TextStyle, TextStyleRefinement, View, ViewContext, WeakView, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::ShowWhitespaceSetting;
|
||||
@@ -50,6 +49,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::Bias;
|
||||
use task::{RunnableTag, TaskTemplate};
|
||||
use theme::{ActiveTheme, PlayerColor};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
|
||||
@@ -367,7 +367,7 @@ impl EditorElement {
|
||||
register_action(view, cx, Editor::open_active_item_in_terminal)
|
||||
}
|
||||
|
||||
fn register_key_listeners(&self, cx: &mut ElementContext, layout: &EditorLayout) {
|
||||
fn register_key_listeners(&self, cx: &mut WindowContext, layout: &EditorLayout) {
|
||||
let position_map = layout.position_map.clone();
|
||||
cx.on_key_event({
|
||||
let editor = self.editor.clone();
|
||||
@@ -691,7 +691,7 @@ impl EditorElement {
|
||||
snapshot: &EditorSnapshot,
|
||||
start_row: u32,
|
||||
end_row: u32,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (
|
||||
Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||
BTreeMap<u32, bool>,
|
||||
@@ -819,7 +819,7 @@ impl EditorElement {
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<FoldLayout> {
|
||||
snapshot
|
||||
.folds_in_range(visible_anchor_range.clone())
|
||||
@@ -887,7 +887,7 @@ impl EditorElement {
|
||||
line_height: Pixels,
|
||||
em_width: Pixels,
|
||||
autoscroll_containing_element: bool,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<CursorLayout> {
|
||||
let mut autoscroll_bounds = None;
|
||||
let cursor_layouts = self.editor.update(cx, |editor, cx| {
|
||||
@@ -993,7 +993,7 @@ impl EditorElement {
|
||||
color: self.style.background,
|
||||
is_top_row: cursor_position.row() == 0,
|
||||
});
|
||||
cx.with_element_context(|cx| cursor.layout(content_origin, cursor_name, cx));
|
||||
cursor.layout(content_origin, cursor_name, cx);
|
||||
cursors.push(cursor);
|
||||
}
|
||||
}
|
||||
@@ -1013,7 +1013,7 @@ impl EditorElement {
|
||||
bounds: Bounds<Pixels>,
|
||||
scroll_position: gpui::Point<f32>,
|
||||
rows_per_page: f32,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<ScrollbarLayout> {
|
||||
let scrollbar_settings = EditorSettings::get_global(cx).scrollbar;
|
||||
let show_scrollbars = match scrollbar_settings.show {
|
||||
@@ -1082,7 +1082,7 @@ impl EditorElement {
|
||||
gutter_settings: crate::editor_settings::Gutter,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
gutter_hitbox: &Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<Option<AnyElement>> {
|
||||
let mut indicators = self.editor.update(cx, |editor, cx| {
|
||||
editor.render_fold_indicators(
|
||||
@@ -1155,7 +1155,7 @@ impl EditorElement {
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
if !self
|
||||
.editor
|
||||
@@ -1200,7 +1200,7 @@ impl EditorElement {
|
||||
.map(|col| self.column_pixels(col as usize, cx))
|
||||
.unwrap_or(px(0.));
|
||||
|
||||
content_origin.x + max(padded_line_width, min_column)
|
||||
(content_origin.x - scroll_pixel_position.x) + max(padded_line_width, min_column)
|
||||
};
|
||||
|
||||
let absolute_offset = point(start_x, start_y);
|
||||
@@ -1220,7 +1220,7 @@ impl EditorElement {
|
||||
line_height: Pixels,
|
||||
gutter_hitbox: &Hitbox,
|
||||
max_width: Option<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Vec<AnyElement>> {
|
||||
if !self
|
||||
.editor
|
||||
@@ -1278,6 +1278,37 @@ impl EditorElement {
|
||||
Some(shaped_lines)
|
||||
}
|
||||
|
||||
fn layout_test_run_indicators(
|
||||
&self,
|
||||
test_lines: Vec<(u32, RunnableTasks)>,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
gutter_dimensions: &GutterDimensions,
|
||||
gutter_hitbox: &Hitbox,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<AnyElement> {
|
||||
test_lines
|
||||
.into_iter()
|
||||
.filter_map(|(line, tags)| {
|
||||
let button = self.editor.update(cx, |editor, cx| {
|
||||
// active = todo check if the run menu is open or something
|
||||
editor.render_test_run_indicator(&self.style, false, line, cx)
|
||||
});
|
||||
|
||||
let button = prepaint_gutter_button(
|
||||
button,
|
||||
line,
|
||||
line_height,
|
||||
gutter_dimensions,
|
||||
scroll_pixel_position,
|
||||
gutter_hitbox,
|
||||
cx,
|
||||
);
|
||||
Some(button)
|
||||
})
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
fn layout_code_actions_indicator(
|
||||
&self,
|
||||
line_height: Pixels,
|
||||
@@ -1285,7 +1316,7 @@ impl EditorElement {
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
gutter_dimensions: &GutterDimensions,
|
||||
gutter_hitbox: &Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
let mut active = false;
|
||||
let mut button = None;
|
||||
@@ -1297,27 +1328,16 @@ impl EditorElement {
|
||||
button = editor.render_code_actions_indicator(&self.style, active, cx);
|
||||
});
|
||||
|
||||
let mut button = button?.into_any_element();
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
AvailableSpace::Definite(line_height),
|
||||
let button = prepaint_gutter_button(
|
||||
button?,
|
||||
newest_selection_head.row(),
|
||||
line_height,
|
||||
gutter_dimensions,
|
||||
scroll_pixel_position,
|
||||
gutter_hitbox,
|
||||
cx,
|
||||
);
|
||||
let indicator_size = button.layout_as_root(available_space, cx);
|
||||
|
||||
let blame_width = gutter_dimensions
|
||||
.git_blame_entries_width
|
||||
.unwrap_or(Pixels::ZERO);
|
||||
|
||||
let mut x = blame_width;
|
||||
let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
|
||||
- indicator_size.width
|
||||
- blame_width;
|
||||
x += available_width / 2.;
|
||||
|
||||
let mut y = newest_selection_head.row() as f32 * line_height - scroll_pixel_position.y;
|
||||
y += (line_height - indicator_size.height) / 2.;
|
||||
|
||||
button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
|
||||
Some(button)
|
||||
}
|
||||
|
||||
@@ -1372,7 +1392,7 @@ impl EditorElement {
|
||||
active_rows: &BTreeMap<u32, bool>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
snapshot: &EditorSnapshot,
|
||||
cx: &ElementContext,
|
||||
cx: &WindowContext,
|
||||
) -> (
|
||||
Vec<Option<ShapedLine>>,
|
||||
Vec<Option<(FoldStatus, BufferRow, bool)>>,
|
||||
@@ -1465,7 +1485,7 @@ impl EditorElement {
|
||||
rows: Range<u32>,
|
||||
line_number_layouts: &[Option<ShapedLine>],
|
||||
snapshot: &EditorSnapshot,
|
||||
cx: &ElementContext,
|
||||
cx: &WindowContext,
|
||||
) -> Vec<LineWithInvisibles> {
|
||||
if rows.start >= rows.end {
|
||||
return Vec::new();
|
||||
@@ -1530,7 +1550,7 @@ impl EditorElement {
|
||||
text_x: Pixels,
|
||||
line_height: Pixels,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<BlockLayout> {
|
||||
let mut block_id = 0;
|
||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||
@@ -1544,7 +1564,7 @@ impl EditorElement {
|
||||
available_space: Size<AvailableSpace>,
|
||||
block_id: usize,
|
||||
block_row_start: u32,
|
||||
cx: &mut ElementContext| {
|
||||
cx: &mut WindowContext| {
|
||||
let mut element = match block {
|
||||
TransformBlock::Custom(block) => {
|
||||
let align_to = block
|
||||
@@ -1865,7 +1885,7 @@ impl EditorElement {
|
||||
hitbox: &Hitbox,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
for block in blocks {
|
||||
let mut origin = hitbox.origin
|
||||
@@ -1893,7 +1913,7 @@ impl EditorElement {
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
newest_selection_head: DisplayPoint,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> bool {
|
||||
let max_height = cmp::min(
|
||||
12. * line_height,
|
||||
@@ -1933,7 +1953,7 @@ impl EditorElement {
|
||||
true
|
||||
}
|
||||
|
||||
fn layout_mouse_context_menu(&self, cx: &mut ElementContext) -> Option<AnyElement> {
|
||||
fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option<AnyElement> {
|
||||
let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
|
||||
let mut element = deferred(
|
||||
anchored()
|
||||
@@ -1961,7 +1981,7 @@ impl EditorElement {
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
em_width: Pixels,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
struct MeasuredHoverPopover {
|
||||
element: AnyElement,
|
||||
@@ -2021,7 +2041,7 @@ impl EditorElement {
|
||||
}
|
||||
overall_height += HOVER_POPOVER_GAP;
|
||||
|
||||
fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
|
||||
fn draw_occluder(width: Pixels, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
|
||||
let mut occlusion = div()
|
||||
.size_full()
|
||||
.occlude()
|
||||
@@ -2067,7 +2087,7 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_background(&self, layout: &EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_background(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
cx.paint_layer(layout.hitbox.bounds, |cx| {
|
||||
let scroll_top = layout.position_map.snapshot.scroll_position().y;
|
||||
let gutter_bg = cx.theme().colors().editor_gutter_background;
|
||||
@@ -2188,7 +2208,7 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_gutter(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
|
||||
let scroll_position = layout.position_map.snapshot.scroll_position();
|
||||
@@ -2230,13 +2250,19 @@ impl EditorElement {
|
||||
}
|
||||
});
|
||||
|
||||
cx.with_element_id(Some("gutter_test_indicators"), |cx| {
|
||||
for test_indicators in layout.test_indicators.iter_mut() {
|
||||
test_indicators.paint(cx);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
||||
indicator.paint(cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
if layout.display_hunks.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2342,7 +2368,7 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
|
||||
return;
|
||||
};
|
||||
@@ -2354,7 +2380,7 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_text(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
cx.with_content_mask(
|
||||
Some(ContentMask {
|
||||
bounds: layout.text_hitbox.bounds,
|
||||
@@ -2386,7 +2412,7 @@ impl EditorElement {
|
||||
fn paint_highlights(
|
||||
&mut self,
|
||||
layout: &mut EditorLayout,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> SmallVec<[Range<DisplayPoint>; 32]> {
|
||||
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
||||
let mut invisible_display_ranges = SmallVec::<[Range<DisplayPoint>; 32]>::new();
|
||||
@@ -2428,7 +2454,7 @@ impl EditorElement {
|
||||
&mut self,
|
||||
invisible_display_ranges: &[Range<DisplayPoint>],
|
||||
layout: &EditorLayout,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let whitespace_setting = self
|
||||
.editor
|
||||
@@ -2451,7 +2477,7 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_redactions(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
if layout.redacted_ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2475,13 +2501,13 @@ impl EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_cursors(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
for cursor in &mut layout.cursors {
|
||||
cursor.paint(layout.content_origin, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_scrollbar(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
let Some(scrollbar_layout) = layout.scrollbar_layout.as_ref() else {
|
||||
return;
|
||||
};
|
||||
@@ -2617,7 +2643,7 @@ impl EditorElement {
|
||||
&self,
|
||||
layout: &EditorLayout,
|
||||
scrollbar_layout: &ScrollbarLayout,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if !editor.is_singleton(cx)
|
||||
@@ -2775,7 +2801,7 @@ impl EditorElement {
|
||||
corner_radius: Pixels,
|
||||
line_end_overshoot: Pixels,
|
||||
layout: &EditorLayout,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let start_row = layout.visible_display_row_range.start;
|
||||
let end_row = layout.visible_display_row_range.end;
|
||||
@@ -2824,7 +2850,7 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_folds(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if layout.folds.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2855,7 +2881,7 @@ impl EditorElement {
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if let Some(mut inline_blame) = layout.inline_blame.take() {
|
||||
cx.paint_layer(layout.text_hitbox.bounds, |cx| {
|
||||
inline_blame.paint(cx);
|
||||
@@ -2863,19 +2889,19 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_blocks(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
for mut block in layout.blocks.drain(..) {
|
||||
block.element.paint(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_mouse_context_menu(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||
if let Some(mouse_context_menu) = layout.mouse_context_menu.as_mut() {
|
||||
mouse_context_menu.paint(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_scroll_wheel_listener(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
cx.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
let editor = self.editor.clone();
|
||||
@@ -2925,7 +2951,7 @@ impl EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut ElementContext) {
|
||||
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, cx: &mut WindowContext) {
|
||||
self.paint_scroll_wheel_listener(layout, cx);
|
||||
|
||||
cx.on_mouse_event({
|
||||
@@ -3037,12 +3063,45 @@ impl EditorElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn prepaint_gutter_button(
|
||||
button: IconButton,
|
||||
row: u32,
|
||||
line_height: Pixels,
|
||||
gutter_dimensions: &GutterDimensions,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
gutter_hitbox: &Hitbox,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> AnyElement {
|
||||
let mut button = button.into_any_element();
|
||||
let available_space = size(
|
||||
AvailableSpace::MinContent,
|
||||
AvailableSpace::Definite(line_height),
|
||||
);
|
||||
let indicator_size = button.layout_as_root(available_space, cx);
|
||||
|
||||
let blame_width = gutter_dimensions
|
||||
.git_blame_entries_width
|
||||
.unwrap_or(Pixels::ZERO);
|
||||
|
||||
let mut x = blame_width;
|
||||
let available_width = gutter_dimensions.margin + gutter_dimensions.left_padding
|
||||
- indicator_size.width
|
||||
- blame_width;
|
||||
x += available_width / 2.;
|
||||
|
||||
let mut y = row as f32 * line_height - scroll_pixel_position.y;
|
||||
y += (line_height - indicator_size.height) / 2.;
|
||||
|
||||
button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
|
||||
button
|
||||
}
|
||||
|
||||
fn render_inline_blame_entry(
|
||||
blame: &gpui::Model<GitBlame>,
|
||||
blame_entry: BlameEntry,
|
||||
style: &EditorStyle,
|
||||
workspace: Option<WeakView<Workspace>>,
|
||||
cx: &mut ElementContext<'_>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> AnyElement {
|
||||
let relative_timestamp = blame_entry_relative_timestamp(&blame_entry, cx);
|
||||
|
||||
@@ -3073,7 +3132,7 @@ fn render_blame_entry(
|
||||
style: &EditorStyle,
|
||||
last_used_color: &mut Option<(PlayerColor, Oid)>,
|
||||
editor: View<Editor>,
|
||||
cx: &mut ElementContext<'_>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> AnyElement {
|
||||
let mut sha_color = cx
|
||||
.theme()
|
||||
@@ -3286,7 +3345,7 @@ impl LineWithInvisibles {
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
selection_ranges: &[Range<DisplayPoint>],
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let line_height = layout.position_map.line_height;
|
||||
let line_y =
|
||||
@@ -3318,7 +3377,7 @@ impl LineWithInvisibles {
|
||||
row: u32,
|
||||
line_height: Pixels,
|
||||
whitespace_setting: ShowWhitespaceSetting,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let allowed_invisibles_regions = match whitespace_setting {
|
||||
ShowWhitespaceSetting::None => return,
|
||||
@@ -3365,7 +3424,7 @@ impl Element for EditorElement {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = EditorLayout;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (gpui::LayoutId, ()) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_style(self.style.clone(), cx);
|
||||
|
||||
@@ -3375,36 +3434,31 @@ impl Element for EditorElement {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
|
||||
cx.with_element_context(|cx| cx.request_layout(&style, None))
|
||||
cx.request_layout(&style, None)
|
||||
}
|
||||
EditorMode::AutoHeight { max_lines } => {
|
||||
let editor_handle = cx.view().clone();
|
||||
let max_line_number_width =
|
||||
self.max_line_number_width(&editor.snapshot(cx), cx);
|
||||
cx.with_element_context(|cx| {
|
||||
cx.request_measured_layout(
|
||||
Style::default(),
|
||||
move |known_dimensions, _, cx| {
|
||||
editor_handle
|
||||
.update(cx, |editor, cx| {
|
||||
compute_auto_height_layout(
|
||||
editor,
|
||||
max_lines,
|
||||
max_line_number_width,
|
||||
known_dimensions,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
},
|
||||
)
|
||||
cx.request_measured_layout(Style::default(), move |known_dimensions, _, cx| {
|
||||
editor_handle
|
||||
.update(cx, |editor, cx| {
|
||||
compute_auto_height_layout(
|
||||
editor,
|
||||
max_lines,
|
||||
max_line_number_width,
|
||||
known_dimensions,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
EditorMode::Full => {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
cx.with_element_context(|cx| cx.request_layout(&style, None))
|
||||
cx.request_layout(&style, None)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3416,13 +3470,14 @@ impl Element for EditorElement {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
let text_style = TextStyleRefinement {
|
||||
font_size: Some(self.style.text.font_size),
|
||||
line_height: Some(self.style.text.line_height),
|
||||
..Default::default()
|
||||
};
|
||||
cx.set_view_id(self.editor.entity_id());
|
||||
cx.with_text_style(Some(text_style), |cx| {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
let mut snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
@@ -3555,6 +3610,12 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
|
||||
let test_lines = self.editor.read(cx).runnable_display_rows(
|
||||
start_anchor..end_anchor,
|
||||
&snapshot.display_snapshot,
|
||||
cx,
|
||||
);
|
||||
|
||||
let (selections, active_rows, newest_selection_head) = self.layout_selections(
|
||||
start_anchor,
|
||||
end_anchor,
|
||||
@@ -3722,18 +3783,34 @@ impl Element for EditorElement {
|
||||
cx,
|
||||
);
|
||||
if gutter_settings.code_actions {
|
||||
code_actions_indicator = self.layout_code_actions_indicator(
|
||||
line_height,
|
||||
newest_selection_head,
|
||||
scroll_pixel_position,
|
||||
&gutter_dimensions,
|
||||
&gutter_hitbox,
|
||||
cx,
|
||||
);
|
||||
let has_test_indicator = test_lines
|
||||
.iter()
|
||||
.any(|(line, _)| *line == newest_selection_head.row());
|
||||
if !has_test_indicator {
|
||||
code_actions_indicator = self.layout_code_actions_indicator(
|
||||
line_height,
|
||||
newest_selection_head,
|
||||
scroll_pixel_position,
|
||||
&gutter_dimensions,
|
||||
&gutter_hitbox,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let test_indicators = cx.with_element_id(Some("test-run"), |cx| {
|
||||
self.layout_test_run_indicators(
|
||||
test_lines,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
&gutter_dimensions,
|
||||
&gutter_hitbox,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if !context_menu_visible && !cx.has_active_drag() {
|
||||
self.layout_hover_popovers(
|
||||
&snapshot,
|
||||
@@ -3833,6 +3910,7 @@ impl Element for EditorElement {
|
||||
cursors,
|
||||
selections,
|
||||
mouse_context_menu,
|
||||
test_indicators,
|
||||
code_actions_indicator,
|
||||
fold_indicators,
|
||||
tab_invisible,
|
||||
@@ -3847,13 +3925,12 @@ impl Element for EditorElement {
|
||||
bounds: Bounds<gpui::Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
layout: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
let key_context = self.editor.read(cx).key_context(cx);
|
||||
cx.set_focus_handle(&focus_handle);
|
||||
cx.set_key_context(key_context);
|
||||
cx.set_view_id(self.editor.entity_id());
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
ElementInputHandler::new(bounds, self.editor.clone()),
|
||||
@@ -3924,6 +4001,7 @@ pub struct EditorLayout {
|
||||
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||
max_row: u32,
|
||||
code_actions_indicator: Option<AnyElement>,
|
||||
test_indicators: Vec<AnyElement>,
|
||||
fold_indicators: Vec<Option<AnyElement>>,
|
||||
mouse_context_menu: Option<AnyElement>,
|
||||
tab_invisible: ShapedLine,
|
||||
@@ -4206,7 +4284,7 @@ impl CursorLayout {
|
||||
&mut self,
|
||||
origin: gpui::Point<Pixels>,
|
||||
cursor_name: Option<CursorName>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if let Some(cursor_name) = cursor_name {
|
||||
let bounds = self.bounds(origin);
|
||||
@@ -4236,7 +4314,7 @@ impl CursorLayout {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut ElementContext) {
|
||||
pub fn paint(&mut self, origin: gpui::Point<Pixels>, cx: &mut WindowContext) {
|
||||
let bounds = self.bounds(origin);
|
||||
|
||||
//Draw background or border quad
|
||||
@@ -4280,7 +4358,7 @@ pub struct HighlightedRangeLine {
|
||||
}
|
||||
|
||||
impl HighlightedRange {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ElementContext) {
|
||||
pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut WindowContext) {
|
||||
if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
|
||||
self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
|
||||
self.paint_lines(
|
||||
@@ -4299,7 +4377,7 @@ impl HighlightedRange {
|
||||
start_y: Pixels,
|
||||
lines: &[HighlightedRangeLine],
|
||||
_bounds: Bounds<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
@@ -4416,7 +4494,7 @@ mod tests {
|
||||
editor_tests::{init_test, update_test_language_settings},
|
||||
Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::TestAppContext;
|
||||
use gpui::{TestAppContext, VisualTestContext};
|
||||
use language::language_settings;
|
||||
use log::info;
|
||||
use std::num::NonZeroU32;
|
||||
@@ -4437,18 +4515,16 @@ mod tests {
|
||||
|
||||
let layouts = cx
|
||||
.update_window(*window, |_, cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
element
|
||||
.layout_line_numbers(
|
||||
0..6,
|
||||
(0..6).map(Some),
|
||||
&Default::default(),
|
||||
Some(DisplayPoint::new(0, 0)),
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
.0
|
||||
})
|
||||
element
|
||||
.layout_line_numbers(
|
||||
0..6,
|
||||
(0..6).map(Some),
|
||||
&Default::default(),
|
||||
Some(DisplayPoint::new(0, 0)),
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(layouts.len(), 6);
|
||||
@@ -4487,9 +4563,9 @@ mod tests {
|
||||
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
@@ -4503,20 +4579,10 @@ mod tests {
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
let state = cx
|
||||
.update_window(window.into(), |_view, cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
element.prepaint(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
&mut (),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
|
||||
EditorElement::new(&editor, style)
|
||||
});
|
||||
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
@@ -4587,7 +4653,6 @@ mod tests {
|
||||
});
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
let _state = window.update(cx, |editor, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
@@ -4598,20 +4663,9 @@ mod tests {
|
||||
});
|
||||
});
|
||||
|
||||
let state = cx
|
||||
.update_window(window.into(), |_view, cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
element.prepaint(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
&mut (),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
|
||||
EditorElement::new(&editor, style)
|
||||
});
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
assert_eq!(local_selections.len(), 2);
|
||||
@@ -4640,6 +4694,7 @@ mod tests {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
window
|
||||
@@ -4662,22 +4717,9 @@ mod tests {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
let state = cx
|
||||
.update_window(window.into(), |_view, cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
element.prepaint(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
&mut (),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
|
||||
EditorElement::new(&editor, style)
|
||||
});
|
||||
assert_eq!(state.position_map.line_layouts.len(), 4);
|
||||
assert_eq!(
|
||||
state
|
||||
@@ -4850,31 +4892,19 @@ mod tests {
|
||||
let buffer = MultiBuffer::build_simple(&input_text, cx);
|
||||
Editor::new(editor_mode, buffer, None, cx)
|
||||
});
|
||||
let cx = &mut VisualTestContext::from_window(*window, cx);
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_wrap_width(Some(editor_width), cx);
|
||||
})
|
||||
.unwrap();
|
||||
let layout_state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
element.prepaint(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
&mut (),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
layout_state
|
||||
let (_, state) = cx.draw(point(px(500.), px(500.)), size(px(500.), px(500.)), |_| {
|
||||
EditorElement::new(&editor, style)
|
||||
});
|
||||
state
|
||||
.position_map
|
||||
.line_layouts
|
||||
.iter()
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
Anchor, Editor, EditorSnapshot, FindAllReferences, GoToDefinition, GoToTypeDefinition, InlayId,
|
||||
PointForPosition, SelectPhase,
|
||||
};
|
||||
use gpui::{px, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
||||
use gpui::{px, AppContext, AsyncWindowContext, Model, Modifiers, Task, ViewContext};
|
||||
use language::{Bias, ToOffset};
|
||||
use linkify::{LinkFinder, LinkKind};
|
||||
use lsp::LanguageServerId;
|
||||
@@ -11,8 +11,7 @@ use project::{
|
||||
HoverBlock, HoverBlockKind, InlayHintLabelPartTooltip, InlayHintTooltip, LocationLink,
|
||||
ResolveState,
|
||||
};
|
||||
use std::{cmp, ops::Range};
|
||||
use text::Point;
|
||||
use std::ops::Range;
|
||||
use theme::ActiveTheme as _;
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
|
||||
@@ -85,6 +84,25 @@ impl TriggerPoint {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exclude_link_to_position(
|
||||
buffer: &Model<language::Buffer>,
|
||||
current_position: &text::Anchor,
|
||||
location: &LocationLink,
|
||||
cx: &AppContext,
|
||||
) -> bool {
|
||||
// Exclude definition links that points back to cursor position.
|
||||
// (i.e., currently cursor upon definition).
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
!(buffer == &location.target.buffer
|
||||
&& current_position
|
||||
.bias_right(&snapshot)
|
||||
.cmp(&location.target.range.start, &snapshot)
|
||||
.is_ge()
|
||||
&& current_position
|
||||
.cmp(&location.target.range.end, &snapshot)
|
||||
.is_le())
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub(crate) fn update_hovered_link(
|
||||
&mut self,
|
||||
@@ -132,28 +150,12 @@ impl Editor {
|
||||
modifiers: Modifiers,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let selection_before_revealing = self.selections.newest::<Point>(cx);
|
||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let before_revealing_head = selection_before_revealing.head();
|
||||
let before_revealing_tail = selection_before_revealing.tail();
|
||||
let before_revealing = match before_revealing_tail.cmp(&before_revealing_head) {
|
||||
cmp::Ordering::Equal | cmp::Ordering::Less => {
|
||||
multi_buffer_snapshot.anchor_after(before_revealing_head)
|
||||
..multi_buffer_snapshot.anchor_before(before_revealing_tail)
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
multi_buffer_snapshot.anchor_before(before_revealing_tail)
|
||||
..multi_buffer_snapshot.anchor_after(before_revealing_head)
|
||||
}
|
||||
};
|
||||
drop(multi_buffer_snapshot);
|
||||
|
||||
let reveal_task = self.cmd_click_reveal_task(point, modifiers, cx);
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
let definition_revealed = reveal_task.await.log_err().unwrap_or(false);
|
||||
let find_references = editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
if definition_revealed && revealed_elsewhere(editor, before_revealing, cx) {
|
||||
if definition_revealed {
|
||||
return None;
|
||||
}
|
||||
editor.find_all_references(&FindAllReferences, cx)
|
||||
@@ -180,12 +182,30 @@ impl Editor {
|
||||
cx.focus(&self.focus_handle);
|
||||
}
|
||||
|
||||
return self.navigate_to_hover_links(
|
||||
None,
|
||||
hovered_link_state.links,
|
||||
modifiers.alt,
|
||||
cx,
|
||||
);
|
||||
// exclude links pointing back to the current anchor
|
||||
let current_position = point
|
||||
.next_valid
|
||||
.to_point(&self.snapshot(cx).display_snapshot);
|
||||
let Some((buffer, anchor)) = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.text_anchor_for_position(current_position, cx)
|
||||
else {
|
||||
return Task::ready(Ok(false));
|
||||
};
|
||||
let links = hovered_link_state
|
||||
.links
|
||||
.into_iter()
|
||||
.filter(|link| {
|
||||
if let HoverLink::Text(location) = link {
|
||||
exclude_link_to_position(&buffer, &anchor, location, cx)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
return self.navigate_to_hover_links(None, links, modifiers.alt, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,46 +232,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn revealed_elsewhere(
|
||||
editor: &mut Editor,
|
||||
before_revealing: Range<Anchor>,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> bool {
|
||||
let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let selection_after_revealing = editor.selections.newest::<Point>(cx);
|
||||
let after_revealing_head = selection_after_revealing.head();
|
||||
let after_revealing_tail = selection_after_revealing.tail();
|
||||
let after_revealing = match after_revealing_tail.cmp(&after_revealing_head) {
|
||||
cmp::Ordering::Equal | cmp::Ordering::Less => {
|
||||
multi_buffer_snapshot.anchor_after(after_revealing_tail)
|
||||
..multi_buffer_snapshot.anchor_before(after_revealing_head)
|
||||
}
|
||||
cmp::Ordering::Greater => {
|
||||
multi_buffer_snapshot.anchor_after(after_revealing_head)
|
||||
..multi_buffer_snapshot.anchor_before(after_revealing_tail)
|
||||
}
|
||||
};
|
||||
|
||||
let before_intersects_after_range = (before_revealing
|
||||
.start
|
||||
.cmp(&after_revealing.start, &multi_buffer_snapshot)
|
||||
.is_ge()
|
||||
&& before_revealing
|
||||
.start
|
||||
.cmp(&after_revealing.end, &multi_buffer_snapshot)
|
||||
.is_le())
|
||||
|| (before_revealing
|
||||
.end
|
||||
.cmp(&after_revealing.start, &multi_buffer_snapshot)
|
||||
.is_ge()
|
||||
&& before_revealing
|
||||
.end
|
||||
.cmp(&after_revealing.end, &multi_buffer_snapshot)
|
||||
.is_le());
|
||||
!before_intersects_after_range
|
||||
}
|
||||
|
||||
pub fn update_inlay_link_and_hover_points(
|
||||
snapshot: &EditorSnapshot,
|
||||
point_for_position: PointForPosition,
|
||||
|
||||
99
crates/editor/src/tasks.rs
Normal file
99
crates/editor/src/tasks.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use crate::Editor;
|
||||
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use gpui::WindowContext;
|
||||
use language::{BasicContextProvider, ContextProvider};
|
||||
use project::{Location, WorktreeId};
|
||||
use task::{TaskContext, TaskVariables};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
|
||||
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
|
||||
let cwd = workspace::tasks::task_cwd(workspace, cx)
|
||||
.log_err()
|
||||
.flatten();
|
||||
let editor = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))?;
|
||||
|
||||
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let (buffer, _, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)?;
|
||||
let snapshot = editor.snapshot(cx);
|
||||
Some((selection, buffer, snapshot))
|
||||
})?;
|
||||
let language_context_provider = buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.and_then(|language| language.context_provider())
|
||||
.unwrap_or_else(|| Arc::new(BasicContextProvider));
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let worktree_abs_path = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()))
|
||||
.and_then(|worktree_id| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
});
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let task_variables = combine_task_variables(
|
||||
worktree_abs_path.as_deref(),
|
||||
location,
|
||||
language_context_provider.as_ref(),
|
||||
cx,
|
||||
)
|
||||
.log_err()?;
|
||||
Some(TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
})
|
||||
}
|
||||
|
||||
task_context_impl(workspace, cx).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
worktree_abs_path: Option<&Path>,
|
||||
location: Location,
|
||||
context_provider: &dyn ContextProvider,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> anyhow::Result<TaskVariables> {
|
||||
if context_provider.is_basic() {
|
||||
context_provider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building basic provider context")
|
||||
} else {
|
||||
let mut basic_context = BasicContextProvider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building basic default context")?;
|
||||
basic_context.extend(
|
||||
context_provider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building provider context ")?,
|
||||
);
|
||||
Ok(basic_context)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
|
||||
("dart", &["dart"]),
|
||||
("dockerfile", &["Dockerfile"]),
|
||||
("elisp", &["el"]),
|
||||
("elixir", &["ex", "exs", "heex"]),
|
||||
("elm", &["elm"]),
|
||||
("erlang", &["erl", "hrl"]),
|
||||
("fish", &["fish"]),
|
||||
|
||||
@@ -14,7 +14,7 @@ workspace = true
|
||||
default = []
|
||||
test-support = ["backtrace", "collections/test-support", "util/test-support"]
|
||||
runtime_shaders = []
|
||||
macos-blade = ["blade-graphics", "blade-macros", "blade-rwh", "bytemuck"]
|
||||
macos-blade = ["blade-graphics", "blade-macros", "bytemuck"]
|
||||
|
||||
[lib]
|
||||
path = "src/gpui.rs"
|
||||
@@ -26,7 +26,6 @@ async-task = "4.7"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
blade-graphics = { workspace = true, optional = true }
|
||||
blade-macros = { workspace = true, optional = true }
|
||||
blade-rwh = { workspace = true, optional = true }
|
||||
bytemuck = { version = "1", optional = true }
|
||||
collections.workspace = true
|
||||
ctor.workspace = true
|
||||
@@ -95,7 +94,6 @@ flume = "0.11"
|
||||
#TODO: use these on all platforms
|
||||
blade-graphics.workspace = true
|
||||
blade-macros.workspace = true
|
||||
blade-rwh.workspace = true
|
||||
bytemuck = "1"
|
||||
cosmic-text = "0.11.2"
|
||||
copypasta = "0.10.1"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
AvailableSpace, BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, Empty,
|
||||
Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext,
|
||||
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||
TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
|
||||
Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, AvailableSpace,
|
||||
BackgroundExecutor, BorrowAppContext, Bounds, ClipboardItem, Context, DrawPhase, Drawable,
|
||||
Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model,
|
||||
ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher,
|
||||
TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{channel::oneshot, Stream, StreamExt};
|
||||
@@ -725,21 +726,28 @@ impl VisualTestContext {
|
||||
}
|
||||
|
||||
/// Draw an element to the window. Useful for simulating events or actions
|
||||
pub fn draw(
|
||||
pub fn draw<E>(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
space: Size<AvailableSpace>,
|
||||
f: impl FnOnce(&mut WindowContext) -> AnyElement,
|
||||
) {
|
||||
space: impl Into<Size<AvailableSpace>>,
|
||||
f: impl FnOnce(&mut WindowContext) -> E,
|
||||
) -> (E::RequestLayoutState, E::PrepaintState)
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
self.update(|cx| {
|
||||
cx.with_element_context(|cx| {
|
||||
let mut element = f(cx);
|
||||
element.layout_as_root(space, cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx));
|
||||
element.paint(cx);
|
||||
});
|
||||
cx.window.draw_phase = DrawPhase::Prepaint;
|
||||
let mut element = Drawable::new(f(cx));
|
||||
element.layout_as_root(space.into(), cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx));
|
||||
|
||||
cx.window.draw_phase = DrawPhase::Paint;
|
||||
let (request_layout_state, prepaint_state) = element.paint(cx);
|
||||
|
||||
cx.window.draw_phase = DrawPhase::None;
|
||||
cx.refresh();
|
||||
|
||||
(request_layout_state, prepaint_state)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
//! your own custom layout algorithm or rendering a code editor.
|
||||
|
||||
use crate::{
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
|
||||
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementId, LayoutId,
|
||||
Pixels, Point, Size, Style, ViewContext, WindowContext, ELEMENT_ARENA,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
pub(crate) use smallvec::SmallVec;
|
||||
use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
|
||||
use std::{any::Any, fmt::Debug, mem};
|
||||
|
||||
/// Implemented by types that participate in laying out and painting the contents of a window.
|
||||
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
|
||||
@@ -54,7 +54,7 @@ pub trait Element: 'static + IntoElement {
|
||||
|
||||
/// Before an element can be painted, we need to know where it's going to be and how big it is.
|
||||
/// Use this method to request a layout from Taffy and initialize the element's state.
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState);
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState);
|
||||
|
||||
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
|
||||
/// purposes. The state argument is the same state that was returned from [`Element::request_layout()`].
|
||||
@@ -62,7 +62,7 @@ pub trait Element: 'static + IntoElement {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState;
|
||||
|
||||
/// Once layout has been completed, this method will be called to paint the element to the screen.
|
||||
@@ -72,7 +72,7 @@ pub trait Element: 'static + IntoElement {
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
);
|
||||
|
||||
/// Convert this element into a dynamically-typed [`AnyElement`].
|
||||
@@ -164,18 +164,13 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||
type RequestLayoutState = AnyElement;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut element = self
|
||||
.0
|
||||
.take()
|
||||
.unwrap()
|
||||
.render(cx.deref_mut())
|
||||
.into_any_element();
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut element = self.0.take().unwrap().render(cx).into_any_element();
|
||||
let layout_id = element.request_layout(cx);
|
||||
(layout_id, element)
|
||||
}
|
||||
|
||||
fn prepaint(&mut self, _: Bounds<Pixels>, element: &mut AnyElement, cx: &mut ElementContext) {
|
||||
fn prepaint(&mut self, _: Bounds<Pixels>, element: &mut AnyElement, cx: &mut WindowContext) {
|
||||
element.prepaint(cx);
|
||||
}
|
||||
|
||||
@@ -184,7 +179,7 @@ impl<C: RenderOnce> Element for Component<C> {
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
element.paint(cx)
|
||||
}
|
||||
@@ -205,16 +200,16 @@ pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
|
||||
trait ElementObject {
|
||||
fn inner_element(&mut self) -> &mut dyn Any;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId;
|
||||
|
||||
fn prepaint(&mut self, cx: &mut ElementContext);
|
||||
fn prepaint(&mut self, cx: &mut WindowContext);
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext);
|
||||
fn paint(&mut self, cx: &mut WindowContext);
|
||||
|
||||
fn layout_as_root(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Size<Pixels>;
|
||||
}
|
||||
|
||||
@@ -249,14 +244,14 @@ enum ElementDrawPhase<RequestLayoutState, PrepaintState> {
|
||||
|
||||
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
|
||||
impl<E: Element> Drawable<E> {
|
||||
fn new(element: E) -> Self {
|
||||
pub(crate) fn new(element: E) -> Self {
|
||||
Drawable {
|
||||
element,
|
||||
phase: ElementDrawPhase::Start,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::Start => {
|
||||
let (layout_id, request_layout) = self.element.request_layout(cx);
|
||||
@@ -270,7 +265,7 @@ impl<E: Element> Drawable<E> {
|
||||
}
|
||||
}
|
||||
|
||||
fn prepaint(&mut self, cx: &mut ElementContext) {
|
||||
pub(crate) fn prepaint(&mut self, cx: &mut WindowContext) {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::RequestLayoutState {
|
||||
layout_id,
|
||||
@@ -296,7 +291,10 @@ impl<E: Element> Drawable<E> {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext) -> E::RequestLayoutState {
|
||||
pub(crate) fn paint(
|
||||
&mut self,
|
||||
cx: &mut WindowContext,
|
||||
) -> (E::RequestLayoutState, E::PrepaintState) {
|
||||
match mem::take(&mut self.phase) {
|
||||
ElementDrawPhase::PrepaintState {
|
||||
node_id,
|
||||
@@ -309,16 +307,16 @@ impl<E: Element> Drawable<E> {
|
||||
self.element
|
||||
.paint(bounds, &mut request_layout, &mut prepaint, cx);
|
||||
self.phase = ElementDrawPhase::Painted;
|
||||
request_layout
|
||||
(request_layout, prepaint)
|
||||
}
|
||||
_ => panic!("must call prepaint before paint"),
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_as_root(
|
||||
pub(crate) fn layout_as_root(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Size<Pixels> {
|
||||
if matches!(&self.phase, ElementDrawPhase::Start) {
|
||||
self.request_layout(cx);
|
||||
@@ -368,22 +366,22 @@ where
|
||||
&mut self.element
|
||||
}
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
Drawable::request_layout(self, cx)
|
||||
}
|
||||
|
||||
fn prepaint(&mut self, cx: &mut ElementContext) {
|
||||
fn prepaint(&mut self, cx: &mut WindowContext) {
|
||||
Drawable::prepaint(self, cx);
|
||||
}
|
||||
|
||||
fn paint(&mut self, cx: &mut ElementContext) {
|
||||
fn paint(&mut self, cx: &mut WindowContext) {
|
||||
Drawable::paint(self, cx);
|
||||
}
|
||||
|
||||
fn layout_as_root(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Size<Pixels> {
|
||||
Drawable::layout_as_root(self, available_space, cx)
|
||||
}
|
||||
@@ -411,18 +409,18 @@ impl AnyElement {
|
||||
|
||||
/// Request the layout ID of the element stored in this `AnyElement`.
|
||||
/// Used for laying out child elements in a parent element.
|
||||
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
|
||||
pub fn request_layout(&mut self, cx: &mut WindowContext) -> LayoutId {
|
||||
self.0.request_layout(cx)
|
||||
}
|
||||
|
||||
/// Prepares the element to be painted by storing its bounds, giving it a chance to draw hitboxes and
|
||||
/// request autoscroll before the final paint pass is confirmed.
|
||||
pub fn prepaint(&mut self, cx: &mut ElementContext) {
|
||||
pub fn prepaint(&mut self, cx: &mut WindowContext) {
|
||||
self.0.prepaint(cx)
|
||||
}
|
||||
|
||||
/// Paints the element stored in this `AnyElement`.
|
||||
pub fn paint(&mut self, cx: &mut ElementContext) {
|
||||
pub fn paint(&mut self, cx: &mut WindowContext) {
|
||||
self.0.paint(cx)
|
||||
}
|
||||
|
||||
@@ -430,13 +428,13 @@ impl AnyElement {
|
||||
pub fn layout_as_root(
|
||||
&mut self,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Size<Pixels> {
|
||||
self.0.layout_as_root(available_space, cx)
|
||||
}
|
||||
|
||||
/// Prepaints this element at the given absolute origin.
|
||||
pub fn prepaint_at(&mut self, origin: Point<Pixels>, cx: &mut ElementContext) {
|
||||
pub fn prepaint_at(&mut self, origin: Point<Pixels>, cx: &mut WindowContext) {
|
||||
cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
|
||||
}
|
||||
|
||||
@@ -445,7 +443,7 @@ impl AnyElement {
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
available_space: Size<AvailableSpace>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.layout_as_root(available_space, cx);
|
||||
cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
|
||||
@@ -456,7 +454,7 @@ impl Element for AnyElement {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = self.request_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
@@ -465,7 +463,7 @@ impl Element for AnyElement {
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.prepaint(cx)
|
||||
}
|
||||
@@ -475,7 +473,7 @@ impl Element for AnyElement {
|
||||
_: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.paint(cx)
|
||||
}
|
||||
@@ -508,15 +506,15 @@ impl Element for Empty {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
(cx.request_layout(&crate::Style::default(), None), ())
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
(cx.request_layout(&Style::default(), None), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -525,7 +523,7 @@ impl Element for Empty {
|
||||
_bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_prepaint: &mut Self::PrepaintState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use smallvec::SmallVec;
|
||||
use taffy::style::{Display, Position};
|
||||
|
||||
use crate::{
|
||||
point, AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, ParentElement,
|
||||
Pixels, Point, Size, Style,
|
||||
point, AnyElement, Bounds, Element, IntoElement, LayoutId, ParentElement, Pixels, Point, Size,
|
||||
Style, WindowContext,
|
||||
};
|
||||
|
||||
/// The state that the anchored element element uses to track its children.
|
||||
@@ -74,7 +74,7 @@ impl Element for Anchored {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let child_layout_ids = self
|
||||
.children
|
||||
@@ -97,7 +97,7 @@ impl Element for Anchored {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if request_layout.child_layout_ids.is_empty() {
|
||||
return;
|
||||
@@ -180,7 +180,7 @@ impl Element for Anchored {
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
for child in &mut self.children {
|
||||
child.paint(cx);
|
||||
|
||||
@@ -91,7 +91,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut crate::ElementContext,
|
||||
cx: &mut crate::WindowContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_element_state(Some(self.id.clone()), |state, cx| {
|
||||
let state = state.unwrap().unwrap_or_else(|| AnimationState {
|
||||
@@ -138,7 +138,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
|
||||
&mut self,
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut crate::ElementContext,
|
||||
cx: &mut crate::WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
element.prepaint(cx);
|
||||
}
|
||||
@@ -148,7 +148,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
|
||||
_bounds: crate::Bounds<crate::Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut crate::ElementContext,
|
||||
cx: &mut crate::WindowContext,
|
||||
) {
|
||||
element.paint(cx);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use refineable::Refineable as _;
|
||||
|
||||
use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRefinement, Styled};
|
||||
use crate::{Bounds, Element, IntoElement, Pixels, Style, StyleRefinement, Styled, WindowContext};
|
||||
|
||||
/// Construct a canvas element with the given paint callback.
|
||||
/// Useful for adding short term custom drawing to a view.
|
||||
pub fn canvas<T>(
|
||||
prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
|
||||
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
|
||||
prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut WindowContext) -> T,
|
||||
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut WindowContext),
|
||||
) -> Canvas<T> {
|
||||
Canvas {
|
||||
prepaint: Some(Box::new(prepaint)),
|
||||
@@ -18,8 +18,8 @@ pub fn canvas<T>(
|
||||
/// A canvas element, meant for accessing the low level paint API without defining a whole
|
||||
/// custom element
|
||||
pub struct Canvas<T> {
|
||||
prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
|
||||
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
|
||||
prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut WindowContext) -> T>>,
|
||||
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut WindowContext)>>,
|
||||
style: StyleRefinement,
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ impl<T: 'static> Element for Canvas<T> {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.style);
|
||||
@@ -49,7 +49,7 @@ impl<T: 'static> Element for Canvas<T> {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Style,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<T> {
|
||||
Some(self.prepaint.take().unwrap()(bounds, cx))
|
||||
}
|
||||
@@ -59,7 +59,7 @@ impl<T: 'static> Element for Canvas<T> {
|
||||
bounds: Bounds<Pixels>,
|
||||
style: &mut Style,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let prepaint = prepaint.take().unwrap();
|
||||
style.paint(bounds, cx, |cx| {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{AnyElement, Bounds, Element, ElementContext, IntoElement, LayoutId, Pixels};
|
||||
use crate::{AnyElement, Bounds, Element, IntoElement, LayoutId, Pixels, WindowContext};
|
||||
|
||||
/// Builds a `Deferred` element, which delays the layout and paint of its child.
|
||||
pub fn deferred(child: impl IntoElement) -> Deferred {
|
||||
@@ -29,7 +29,7 @@ impl Element for Deferred {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, ()) {
|
||||
let layout_id = self.child.as_mut().unwrap().request_layout(cx);
|
||||
(layout_id, ())
|
||||
}
|
||||
@@ -38,7 +38,7 @@ impl Element for Deferred {
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let child = self.child.take().unwrap();
|
||||
let element_offset = cx.element_offset();
|
||||
@@ -50,7 +50,7 @@ impl Element for Deferred {
|
||||
_bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
_prepaint: &mut Self::PrepaintState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, Bounds,
|
||||
ClickEvent, DispatchPhase, Element, ElementContext, ElementId, FocusHandle, Global, Hitbox,
|
||||
HitboxId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
|
||||
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
||||
StyleRefinement, Styled, Task, TooltipId, View, Visibility, WindowContext,
|
||||
ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, Global, Hitbox, HitboxId,
|
||||
IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ModifiersChangedEvent,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
|
||||
Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId,
|
||||
View, Visibility, WindowContext,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use refineable::Refineable;
|
||||
@@ -1123,7 +1123,7 @@ impl Element for Div {
|
||||
type RequestLayoutState = DivFrameState;
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut child_layout_ids = SmallVec::new();
|
||||
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
|
||||
cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||
@@ -1142,7 +1142,7 @@ impl Element for Div {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Hitbox> {
|
||||
let mut child_min = point(Pixels::MAX, Pixels::MAX);
|
||||
let mut child_max = Point::default();
|
||||
@@ -1197,7 +1197,7 @@ impl Element for Div {
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.interactivity
|
||||
.paint(bounds, hitbox.as_ref(), cx, |_style, cx| {
|
||||
@@ -1276,8 +1276,8 @@ impl Interactivity {
|
||||
/// Layout this element according to this interactivity state's configured styles
|
||||
pub fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(Style, &mut WindowContext) -> LayoutId,
|
||||
) -> LayoutId {
|
||||
cx.with_element_state::<InteractiveElementState, _>(
|
||||
self.element_id.clone(),
|
||||
@@ -1341,8 +1341,8 @@ impl Interactivity {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
content_size: Size<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut ElementContext) -> R,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut WindowContext) -> R,
|
||||
) -> R {
|
||||
self.content_size = content_size;
|
||||
cx.with_element_state::<InteractiveElementState, _>(
|
||||
@@ -1406,7 +1406,7 @@ impl Interactivity {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
style: &Style,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Point<Pixels> {
|
||||
if let Some(scroll_offset) = self.scroll_offset.as_ref() {
|
||||
if let Some(scroll_handle) = &self.tracked_scroll_handle {
|
||||
@@ -1456,8 +1456,8 @@ impl Interactivity {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
hitbox: Option<&Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&Style, &mut ElementContext),
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&Style, &mut WindowContext),
|
||||
) {
|
||||
self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(cx));
|
||||
cx.with_element_state::<InteractiveElementState, _>(
|
||||
@@ -1482,7 +1482,7 @@ impl Interactivity {
|
||||
return ((), element_state);
|
||||
}
|
||||
|
||||
style.paint(bounds, cx, |cx: &mut ElementContext| {
|
||||
style.paint(bounds, cx, |cx: &mut WindowContext| {
|
||||
cx.with_text_style(style.text_style().cloned(), |cx| {
|
||||
cx.with_content_mask(style.overflow_mask(bounds, cx.rem_size()), |cx| {
|
||||
if let Some(hitbox) = hitbox {
|
||||
@@ -1521,7 +1521,7 @@ impl Interactivity {
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
|
||||
fn paint_debug_info(&mut self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) {
|
||||
if self.element_id.is_some()
|
||||
&& (style.debug || style.debug_below || cx.has_global::<crate::DebugBelow>())
|
||||
&& hitbox.is_hovered(cx)
|
||||
@@ -1530,7 +1530,7 @@ impl Interactivity {
|
||||
let element_id = format!("{:?}", self.element_id.as_ref().unwrap());
|
||||
let str_len = element_id.len();
|
||||
|
||||
let render_debug_text = |cx: &mut ElementContext| {
|
||||
let render_debug_text = |cx: &mut WindowContext| {
|
||||
if let Some(text) = cx
|
||||
.text_system()
|
||||
.shape_text(
|
||||
@@ -1629,7 +1629,7 @@ impl Interactivity {
|
||||
&mut self,
|
||||
hitbox: &Hitbox,
|
||||
element_state: Option<&mut InteractiveElementState>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
// If this element can be focused, register a mouse down listener
|
||||
// that will automatically transfer focus when hitting the element.
|
||||
@@ -1712,11 +1712,11 @@ impl Interactivity {
|
||||
|
||||
let mut can_drop = true;
|
||||
if let Some(predicate) = &can_drop_predicate {
|
||||
can_drop = predicate(drag.value.as_ref(), cx.deref_mut());
|
||||
can_drop = predicate(drag.value.as_ref(), cx);
|
||||
}
|
||||
|
||||
if can_drop {
|
||||
listener(drag.value.as_ref(), cx.deref_mut());
|
||||
listener(drag.value.as_ref(), cx);
|
||||
cx.refresh();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
@@ -1840,7 +1840,7 @@ impl Interactivity {
|
||||
*was_hovered = is_hovered;
|
||||
drop(was_hovered);
|
||||
|
||||
hover_listener(&is_hovered, cx.deref_mut());
|
||||
hover_listener(&is_hovered, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1969,7 +1969,7 @@ impl Interactivity {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_keyboard_listeners(&mut self, cx: &mut ElementContext) {
|
||||
fn paint_keyboard_listeners(&mut self, cx: &mut WindowContext) {
|
||||
let key_down_listeners = mem::take(&mut self.key_down_listeners);
|
||||
let key_up_listeners = mem::take(&mut self.key_up_listeners);
|
||||
let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners);
|
||||
@@ -2004,7 +2004,7 @@ impl Interactivity {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_hover_group_handler(&self, cx: &mut ElementContext) {
|
||||
fn paint_hover_group_handler(&self, cx: &mut WindowContext) {
|
||||
let group_hitbox = self
|
||||
.group_hover_style
|
||||
.as_ref()
|
||||
@@ -2021,7 +2021,7 @@ impl Interactivity {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut ElementContext) {
|
||||
fn paint_scroll_listener(&self, hitbox: &Hitbox, style: &Style, cx: &mut WindowContext) {
|
||||
if let Some(scroll_offset) = self.scroll_offset.clone() {
|
||||
let overflow = style.overflow;
|
||||
let line_height = cx.line_height();
|
||||
@@ -2064,7 +2064,7 @@ impl Interactivity {
|
||||
}
|
||||
|
||||
/// Compute the visual style for this element, based on the current bounds and the element's state.
|
||||
pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut ElementContext) -> Style {
|
||||
pub fn compute_style(&self, hitbox: Option<&Hitbox>, cx: &mut WindowContext) -> Style {
|
||||
cx.with_element_state(self.element_id.clone(), |element_state, cx| {
|
||||
let mut element_state =
|
||||
element_state.map(|element_state| element_state.unwrap_or_default());
|
||||
@@ -2078,7 +2078,7 @@ impl Interactivity {
|
||||
&self,
|
||||
hitbox: Option<&Hitbox>,
|
||||
element_state: Option<&mut InteractiveElementState>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Style {
|
||||
let mut style = Style::default();
|
||||
style.refine(&self.base_style);
|
||||
@@ -2119,7 +2119,7 @@ impl Interactivity {
|
||||
if let Some(drag) = cx.active_drag.take() {
|
||||
let mut can_drop = true;
|
||||
if let Some(can_drop_predicate) = &self.can_drop_predicate {
|
||||
can_drop = can_drop_predicate(drag.value.as_ref(), cx.deref_mut());
|
||||
can_drop = can_drop_predicate(drag.value.as_ref(), cx);
|
||||
}
|
||||
|
||||
if can_drop {
|
||||
@@ -2264,7 +2264,7 @@ where
|
||||
type RequestLayoutState = E::RequestLayoutState;
|
||||
type PrepaintState = E::PrepaintState;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.element.request_layout(cx)
|
||||
}
|
||||
|
||||
@@ -2272,7 +2272,7 @@ where
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> E::PrepaintState {
|
||||
self.element.prepaint(bounds, state, cx)
|
||||
}
|
||||
@@ -2282,7 +2282,7 @@ where
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.element.paint(bounds, request_layout, prepaint, cx)
|
||||
}
|
||||
@@ -2347,7 +2347,7 @@ where
|
||||
type RequestLayoutState = E::RequestLayoutState;
|
||||
type PrepaintState = E::PrepaintState;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.element.request_layout(cx)
|
||||
}
|
||||
|
||||
@@ -2355,7 +2355,7 @@ where
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> E::PrepaintState {
|
||||
self.element.prepaint(bounds, state, cx)
|
||||
}
|
||||
@@ -2365,7 +2365,7 @@ where
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.element.paint(bounds, request_layout, prepaint, cx);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element,
|
||||
ElementContext, Hitbox, ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId,
|
||||
Length, Pixels, SharedUri, Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext,
|
||||
point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element, Hitbox,
|
||||
ImageData, InteractiveElement, Interactivity, IntoElement, LayoutId, Length, Pixels, SharedUri,
|
||||
Size, StyleRefinement, Styled, SvgSize, UriOrPath, WindowContext,
|
||||
};
|
||||
use futures::{AsyncReadExt, Future};
|
||||
use image::{ImageBuffer, ImageError};
|
||||
@@ -232,7 +232,7 @@ impl Element for Img {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
|
||||
if let Some(data) = self.source.data(cx) {
|
||||
let image_size = data.size();
|
||||
@@ -260,7 +260,7 @@ impl Element for Img {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
@@ -271,7 +271,7 @@ impl Element for Img {
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let source = self.source.clone();
|
||||
self.interactivity
|
||||
@@ -319,7 +319,7 @@ impl InteractiveElement for Img {
|
||||
}
|
||||
|
||||
impl ImageSource {
|
||||
fn data(&self, cx: &mut ElementContext) -> Option<Arc<ImageData>> {
|
||||
fn data(&self, cx: &mut WindowContext) -> Option<Arc<ImageData>> {
|
||||
match self {
|
||||
ImageSource::Uri(_) | ImageSource::File(_) => {
|
||||
let uri_or_path: UriOrPath = match self {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
|
||||
Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent,
|
||||
Size, Style, StyleRefinement, Styled, WindowContext,
|
||||
Element, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
|
||||
StyleRefinement, Styled, WindowContext,
|
||||
};
|
||||
use collections::VecDeque;
|
||||
use refineable::Refineable as _;
|
||||
@@ -434,7 +434,7 @@ impl StateInner {
|
||||
available_width: Option<Pixels>,
|
||||
available_height: Pixels,
|
||||
padding: &Edges<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> LayoutItemsResponse {
|
||||
let old_items = self.items.clone();
|
||||
let mut measured_items = VecDeque::new();
|
||||
@@ -609,7 +609,7 @@ impl StateInner {
|
||||
bounds: Bounds<Pixels>,
|
||||
padding: Edges<Pixels>,
|
||||
autoscroll: bool,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<LayoutItemsResponse, ListOffset> {
|
||||
cx.transact(|cx| {
|
||||
let mut layout_response =
|
||||
@@ -706,7 +706,7 @@ impl Element for List {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut crate::ElementContext,
|
||||
cx: &mut crate::WindowContext,
|
||||
) -> (crate::LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = match self.sizing_behavior {
|
||||
ListSizingBehavior::Infer => {
|
||||
@@ -772,7 +772,7 @@ impl Element for List {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> ListPrepaintState {
|
||||
let state = &mut *self.state.0.borrow_mut();
|
||||
state.reset = false;
|
||||
@@ -815,7 +815,7 @@ impl Element for List {
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut crate::ElementContext,
|
||||
cx: &mut crate::WindowContext,
|
||||
) {
|
||||
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
|
||||
for item in &mut prepaint.layout.item_layouts {
|
||||
@@ -951,11 +951,9 @@ mod test {
|
||||
});
|
||||
|
||||
// Paint
|
||||
cx.draw(
|
||||
point(px(0.), px(0.)),
|
||||
size(px(100.), px(20.)).into(),
|
||||
|_| list(state.clone()).w_full().h_full().into_any(),
|
||||
);
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_| {
|
||||
list(state.clone()).w_full().h_full()
|
||||
});
|
||||
|
||||
// Reset
|
||||
state.reset(5);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
geometry::Negate as _, point, px, radians, size, Bounds, Element, ElementContext, Hitbox,
|
||||
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString,
|
||||
Size, StyleRefinement, Styled, TransformationMatrix,
|
||||
geometry::Negate as _, point, px, radians, size, Bounds, Element, Hitbox, InteractiveElement,
|
||||
Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
|
||||
StyleRefinement, Styled, TransformationMatrix, WindowContext,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -40,7 +40,7 @@ impl Element for Svg {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.request_layout(cx, |style, cx| cx.request_layout(&style, None));
|
||||
@@ -51,7 +51,7 @@ impl Element for Svg {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Hitbox> {
|
||||
self.interactivity
|
||||
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
|
||||
@@ -62,7 +62,7 @@ impl Element for Svg {
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
|
||||
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
|
||||
TOOLTIP_DELAY,
|
||||
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementId, HighlightStyle,
|
||||
Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point,
|
||||
SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine, TOOLTIP_DELAY,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
@@ -20,7 +19,7 @@ impl Element for &'static str {
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(SharedString::from(*self), None, cx);
|
||||
(layout_id, state)
|
||||
@@ -30,7 +29,7 @@ impl Element for &'static str {
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -39,7 +38,7 @@ impl Element for &'static str {
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut TextState,
|
||||
_: &mut (),
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
text_state.paint(bounds, self, cx)
|
||||
}
|
||||
@@ -65,7 +64,7 @@ impl Element for SharedString {
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.clone(), None, cx);
|
||||
(layout_id, state)
|
||||
@@ -75,7 +74,7 @@ impl Element for SharedString {
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_text_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -84,7 +83,7 @@ impl Element for SharedString {
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let text_str: &str = self.as_ref();
|
||||
text_state.paint(bounds, text_str, cx)
|
||||
@@ -151,7 +150,7 @@ impl Element for StyledText {
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut state = TextState::default();
|
||||
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
|
||||
(layout_id, state)
|
||||
@@ -161,7 +160,7 @@ impl Element for StyledText {
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::RequestLayoutState,
|
||||
_cx: &mut ElementContext,
|
||||
_cx: &mut WindowContext,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -170,7 +169,7 @@ impl Element for StyledText {
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
text_state.paint(bounds, &self.text, cx)
|
||||
}
|
||||
@@ -204,7 +203,7 @@ impl TextState {
|
||||
&mut self,
|
||||
text: SharedString,
|
||||
runs: Option<Vec<TextRun>>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> LayoutId {
|
||||
let text_style = cx.text_style();
|
||||
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
||||
@@ -279,7 +278,7 @@ impl TextState {
|
||||
layout_id
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut ElementContext) {
|
||||
fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
|
||||
let element_state = self.lock();
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
@@ -405,7 +404,7 @@ impl Element for InteractiveText {
|
||||
type RequestLayoutState = TextState;
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.text.request_layout(cx)
|
||||
}
|
||||
|
||||
@@ -413,7 +412,7 @@ impl Element for InteractiveText {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Hitbox {
|
||||
cx.with_element_state::<InteractiveTextState, _>(
|
||||
Some(self.element_id.clone()),
|
||||
@@ -442,7 +441,7 @@ impl Element for InteractiveText {
|
||||
bounds: Bounds<Pixels>,
|
||||
text_state: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
cx.with_element_state::<InteractiveTextState, _>(
|
||||
Some(self.element_id.clone()),
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
//! elements with uniform height.
|
||||
|
||||
use crate::{
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
|
||||
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
|
||||
ScrollHandle, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementId, Hitbox,
|
||||
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render, ScrollHandle, Size,
|
||||
StyleRefinement, Styled, View, ViewContext, WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
@@ -107,7 +107,7 @@ impl Element for UniformList {
|
||||
type RequestLayoutState = UniformListFrameState;
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let max_items = self.item_count;
|
||||
let item_size = self.measure_item(None, cx);
|
||||
let layout_id = self.interactivity.request_layout(cx, |style, cx| {
|
||||
@@ -141,7 +141,7 @@ impl Element for UniformList {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
frame_state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<Hitbox> {
|
||||
let style = self.interactivity.compute_style(None, cx);
|
||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||
@@ -239,7 +239,7 @@ impl Element for UniformList {
|
||||
bounds: Bounds<crate::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Option<Hitbox>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.interactivity
|
||||
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
|
||||
@@ -265,7 +265,7 @@ impl UniformList {
|
||||
self
|
||||
}
|
||||
|
||||
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut ElementContext) -> Size<Pixels> {
|
||||
fn measure_item(&self, list_width: Option<Pixels>, cx: &mut WindowContext) -> Size<Pixels> {
|
||||
if self.item_count == 0 {
|
||||
return Size::default();
|
||||
}
|
||||
|
||||
@@ -50,9 +50,8 @@
|
||||
/// KeyBinding::new("cmd-k left", pane::SplitLeft, Some("Pane"))
|
||||
///
|
||||
use crate::{
|
||||
Action, ActionRegistry, DispatchPhase, ElementContext, EntityId, FocusId, KeyBinding,
|
||||
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent,
|
||||
WindowContext,
|
||||
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
|
||||
KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent, WindowContext,
|
||||
};
|
||||
use collections::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
@@ -107,8 +106,8 @@ impl ReusedSubtree {
|
||||
}
|
||||
}
|
||||
|
||||
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut ElementContext)>;
|
||||
type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut ElementContext)>;
|
||||
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
|
||||
type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowContext)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DispatchActionListener {
|
||||
|
||||
@@ -12,7 +12,7 @@ use collections::HashMap;
|
||||
#[cfg(target_os = "macos")]
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
#[cfg(target_os = "macos")]
|
||||
use std::ffi::c_void;
|
||||
use std::{ffi::c_void, ptr::NonNull};
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use std::{mem, sync::Arc};
|
||||
@@ -25,35 +25,32 @@ pub type Renderer = BladeRenderer;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub unsafe fn new_renderer(
|
||||
_context: self::Context,
|
||||
native_window: *mut c_void,
|
||||
_native_window: *mut c_void,
|
||||
native_view: *mut c_void,
|
||||
bounds: crate::Size<f32>,
|
||||
) -> Renderer {
|
||||
use raw_window_handle as rwh;
|
||||
struct RawWindow {
|
||||
window: *mut c_void,
|
||||
view: *mut c_void,
|
||||
}
|
||||
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
|
||||
let mut wh = blade_rwh::AppKitWindowHandle::empty();
|
||||
wh.ns_window = self.window;
|
||||
wh.ns_view = self.view;
|
||||
wh.into()
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let view = NonNull::new(self.view).unwrap();
|
||||
let handle = rwh::AppKitWindowHandle::new(view);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
|
||||
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
|
||||
let dh = blade_rwh::AppKitDisplayHandle::empty();
|
||||
dh.into()
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let handle = rwh::AppKitDisplayHandle::new();
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
let gpu = Arc::new(
|
||||
gpu::Context::init_windowed(
|
||||
&RawWindow {
|
||||
window: native_window as *mut _,
|
||||
view: native_view as *mut _,
|
||||
},
|
||||
gpu::ContextDesc {
|
||||
@@ -184,7 +181,7 @@ struct BladePipelines {
|
||||
}
|
||||
|
||||
impl BladePipelines {
|
||||
fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
|
||||
fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo) -> Self {
|
||||
use gpu::ShaderData as _;
|
||||
|
||||
let shader = gpu.create_shader(gpu::ShaderDesc {
|
||||
@@ -216,7 +213,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_quad"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -233,7 +230,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_shadow"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -267,7 +264,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_path"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -284,7 +281,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_underline"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -301,7 +298,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_mono_sprite"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -318,7 +315,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_poly_sprite"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -335,7 +332,7 @@ impl BladePipelines {
|
||||
depth_stencil: None,
|
||||
fragment: shader.at("fs_surface"),
|
||||
color_targets: &[gpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
format: surface_info.format,
|
||||
blend: Some(gpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: gpu::ColorWrites::default(),
|
||||
}],
|
||||
@@ -367,16 +364,18 @@ impl BladeRenderer {
|
||||
//Note: this matches the original logic of the Metal backend,
|
||||
// but ultimaterly we need to switch to `Linear`.
|
||||
color_space: gpu::ColorSpace::Srgb,
|
||||
allow_exclusive_full_screen: false,
|
||||
transparent: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
|
||||
let surface_format = gpu.resize(Self::make_surface_config(size));
|
||||
let surface_info = gpu.resize(Self::make_surface_config(size));
|
||||
let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
|
||||
name: "main",
|
||||
buffer_count: 2,
|
||||
});
|
||||
let pipelines = BladePipelines::new(&gpu, surface_format);
|
||||
let pipelines = BladePipelines::new(&gpu, surface_info);
|
||||
let instance_belt = BladeBelt::new(BladeBeltDescriptor {
|
||||
memory: gpu::Memory::Shared,
|
||||
min_chunk_size: 0x1000,
|
||||
|
||||
@@ -2,16 +2,14 @@ use std::any::Any;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::Arc;
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
|
||||
use collections::{HashMap, HashSet};
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use raw_window_handle::{
|
||||
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
|
||||
};
|
||||
use raw_window_handle as rwh;
|
||||
use wayland_backend::client::ObjectId;
|
||||
use wayland_client::WEnum;
|
||||
use wayland_client::{protocol::wl_surface, Proxy};
|
||||
@@ -49,19 +47,18 @@ struct RawWindow {
|
||||
display: *mut c_void,
|
||||
}
|
||||
|
||||
unsafe impl HasRawWindowHandle for RawWindow {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut wh = blade_rwh::WaylandWindowHandle::empty();
|
||||
wh.surface = self.window;
|
||||
wh.into()
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
let window = NonNull::new(self.window).unwrap();
|
||||
let handle = rwh::WaylandWindowHandle::new(window);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HasRawDisplayHandle for RawWindow {
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
let mut dh = blade_rwh::WaylandDisplayHandle::empty();
|
||||
dh.display = self.display;
|
||||
dh.into()
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
let display = NonNull::new(self.display).unwrap();
|
||||
let handle = rwh::WaylandDisplayHandle::new(display);
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,14 +517,13 @@ impl WaylandWindowStatePtr {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for WaylandWindow {
|
||||
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
|
||||
impl rwh::HasWindowHandle for WaylandWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDisplayHandle for WaylandWindow {
|
||||
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
|
||||
impl rwh::HasDisplayHandle for WaylandWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ pub struct Callbacks {
|
||||
}
|
||||
|
||||
pub(crate) struct X11WindowState {
|
||||
raw: RawWindow,
|
||||
atoms: XcbAtoms,
|
||||
raw: RawWindow,
|
||||
bounds: Bounds<i32>,
|
||||
scale_factor: f32,
|
||||
renderer: BladeRenderer,
|
||||
@@ -96,40 +96,29 @@ pub(crate) struct X11Window {
|
||||
}
|
||||
|
||||
// todo(linux): Remove other RawWindowHandle implementation
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
|
||||
let mut wh = blade_rwh::XcbWindowHandle::empty();
|
||||
wh.window = self.window_id;
|
||||
wh.visual_id = self.visual_id;
|
||||
wh.into()
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
let non_zero = NonZeroU32::new(self.window_id).unwrap();
|
||||
let handle = rwh::XcbWindowHandle::new(non_zero);
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
|
||||
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
|
||||
let mut dh = blade_rwh::XcbDisplayHandle::empty();
|
||||
dh.connection = self.connection;
|
||||
dh.screen = self.screen_id as i32;
|
||||
dh.into()
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
let non_zero = NonNull::new(self.connection).unwrap();
|
||||
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for X11Window {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let non_zero = NonZeroU32::new(self.state.borrow().raw.window_id).unwrap();
|
||||
let handle = rwh::XcbWindowHandle::new(non_zero);
|
||||
rwh::WindowHandle::borrow_raw(handle.into())
|
||||
})
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
impl rwh::HasDisplayHandle for X11Window {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let this = self.state.borrow();
|
||||
let non_zero = NonNull::new(this.raw.connection).unwrap();
|
||||
let handle = rwh::XcbDisplayHandle::new(Some(non_zero), this.raw.screen_id as i32);
|
||||
rwh::DisplayHandle::borrow_raw(handle.into())
|
||||
})
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,7 @@ use objc::{
|
||||
sel, sel_impl,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use raw_window_handle::{
|
||||
AppKitDisplayHandle, AppKitWindowHandle, DisplayHandle, HasDisplayHandle, HasWindowHandle,
|
||||
RawWindowHandle, WindowHandle,
|
||||
};
|
||||
use raw_window_handle as rwh;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -1141,25 +1138,25 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for MacWindow {
|
||||
fn window_handle(
|
||||
&self,
|
||||
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
|
||||
impl rwh::HasWindowHandle for MacWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
// SAFETY: The AppKitWindowHandle is a wrapper around a pointer to an NSView
|
||||
unsafe {
|
||||
Ok(WindowHandle::borrow_raw(RawWindowHandle::AppKit(
|
||||
AppKitWindowHandle::new(self.0.lock().native_view.cast()),
|
||||
Ok(rwh::WindowHandle::borrow_raw(rwh::RawWindowHandle::AppKit(
|
||||
rwh::AppKitWindowHandle::new(self.0.lock().native_view.cast()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDisplayHandle for MacWindow {
|
||||
fn display_handle(
|
||||
&self,
|
||||
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
|
||||
impl rwh::HasDisplayHandle for MacWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
// SAFETY: This is a no-op on macOS
|
||||
unsafe { Ok(DisplayHandle::borrow_raw(AppKitDisplayHandle::new().into())) }
|
||||
unsafe {
|
||||
Ok(rwh::DisplayHandle::borrow_raw(
|
||||
rwh::AppKitDisplayHandle::new().into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ use windows::{
|
||||
Direct2D::{Common::*, *},
|
||||
DirectWrite::*,
|
||||
Dxgi::Common::*,
|
||||
Gdi::LOGFONTW,
|
||||
Imaging::{D2D::IWICImagingFactory2, *},
|
||||
},
|
||||
System::{Com::*, SystemServices::LOCALE_NAME_MAX_LENGTH},
|
||||
UI::WindowsAndMessaging::*,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -51,6 +53,7 @@ unsafe impl Send for DirectWriteComponent {}
|
||||
|
||||
struct DirectWriteState {
|
||||
components: DirectWriteComponent,
|
||||
system_ui_font_name: SharedString,
|
||||
system_font_collection: IDWriteFontCollection1,
|
||||
custom_font_collection: IDWriteFontCollection1,
|
||||
fonts: Vec<FontInfo>,
|
||||
@@ -106,9 +109,11 @@ impl DirectWriteTextSystem {
|
||||
.factory
|
||||
.CreateFontCollectionFromFontSet(&custom_font_set)?
|
||||
};
|
||||
let system_ui_font_name = get_system_ui_font_name();
|
||||
|
||||
Ok(Self(RwLock::new(DirectWriteState {
|
||||
components,
|
||||
system_ui_font_name,
|
||||
system_font_collection,
|
||||
custom_font_collection,
|
||||
fonts: Vec::new(),
|
||||
@@ -309,53 +314,55 @@ impl DirectWriteState {
|
||||
}
|
||||
|
||||
fn select_font(&mut self, target_font: &Font) -> FontId {
|
||||
let family_name = if target_font.family == ".SystemUIFont" {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
|
||||
// Segoe UI is the Windows font intended for user interface text strings.
|
||||
"Segoe UI"
|
||||
} else {
|
||||
target_font.family.as_ref()
|
||||
};
|
||||
unsafe {
|
||||
// try to find target font in custom font collection first
|
||||
self.get_font_id_from_font_collection(
|
||||
family_name,
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
false,
|
||||
)
|
||||
.or_else(|| {
|
||||
self.get_font_id_from_font_collection(
|
||||
family_name,
|
||||
if target_font.family == ".SystemUIFont" {
|
||||
let family = self.system_ui_font_name.clone();
|
||||
self.find_font_id(
|
||||
family.as_ref(),
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
self.find_font_id(
|
||||
target_font.family.as_ref(),
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
let family = self.system_ui_font_name.clone();
|
||||
log::error!("{} not found, use {} instead.", target_font.family, family);
|
||||
self.get_font_id_from_font_collection(
|
||||
family.as_ref(),
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
true,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn find_font_id(
|
||||
&mut self,
|
||||
family_name: &str,
|
||||
weight: FontWeight,
|
||||
style: FontStyle,
|
||||
features: &FontFeatures,
|
||||
) -> Option<FontId> {
|
||||
// try to find target font in custom font collection first
|
||||
self.get_font_id_from_font_collection(family_name, weight, style, features, false)
|
||||
.or_else(|| {
|
||||
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
|
||||
})
|
||||
.or_else(|| {
|
||||
self.update_system_font_collection();
|
||||
self.get_font_id_from_font_collection(
|
||||
family_name,
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
true,
|
||||
)
|
||||
self.get_font_id_from_font_collection(family_name, weight, style, features, true)
|
||||
})
|
||||
.or_else(|| {
|
||||
log::error!("{} not found, use Arial instead.", family_name);
|
||||
self.get_font_id_from_font_collection(
|
||||
"Arial",
|
||||
target_font.weight,
|
||||
target_font.style,
|
||||
&target_font.features,
|
||||
false,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
@@ -1271,6 +1278,29 @@ fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_system_ui_font_name() -> SharedString {
|
||||
unsafe {
|
||||
let mut info: LOGFONTW = std::mem::zeroed();
|
||||
let font_family = if SystemParametersInfoW(
|
||||
SPI_GETICONTITLELOGFONT,
|
||||
std::mem::size_of::<LOGFONTW>() as u32,
|
||||
Some(&mut info as *mut _ as _),
|
||||
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS(0),
|
||||
)
|
||||
.log_err()
|
||||
.is_none()
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
|
||||
// Segoe UI is the Windows font intended for user interface text strings.
|
||||
"Segoe UI".into()
|
||||
} else {
|
||||
String::from_utf16_lossy(&info.lfFaceName).into()
|
||||
};
|
||||
log::info!("Use {} as UI font.", font_family);
|
||||
font_family
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
|
||||
const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
|
||||
r: 1.0,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
ffi::c_void,
|
||||
iter::once,
|
||||
num::NonZeroIsize,
|
||||
path::PathBuf,
|
||||
@@ -18,7 +17,7 @@ use anyhow::Context;
|
||||
use blade_graphics as gpu;
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
use raw_window_handle as rwh;
|
||||
use smallvec::SmallVec;
|
||||
use std::result::Result;
|
||||
use windows::{
|
||||
@@ -77,20 +76,24 @@ impl WindowsWindowInner {
|
||||
let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
|
||||
let input_handler = Cell::new(None);
|
||||
struct RawWindow {
|
||||
hwnd: *mut c_void,
|
||||
hwnd: isize,
|
||||
}
|
||||
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
|
||||
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
|
||||
let mut handle = blade_rwh::Win32WindowHandle::empty();
|
||||
handle.hwnd = self.hwnd;
|
||||
handle.into()
|
||||
impl rwh::HasWindowHandle for RawWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
Ok(unsafe {
|
||||
let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
|
||||
let handle = rwh::Win32WindowHandle::new(hwnd);
|
||||
rwh::WindowHandle::borrow_raw(handle.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
|
||||
fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
|
||||
blade_rwh::WindowsDisplayHandle::empty().into()
|
||||
impl rwh::HasDisplayHandle for RawWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
let handle = rwh::WindowsDisplayHandle::new();
|
||||
Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
|
||||
}
|
||||
}
|
||||
|
||||
let raw = RawWindow { hwnd: hwnd.0 as _ };
|
||||
let gpu = Arc::new(
|
||||
unsafe {
|
||||
@@ -698,12 +701,13 @@ impl WindowsWindowInner {
|
||||
if let Some(callback) = callbacks.input.as_mut() {
|
||||
let x = lparam.signed_loword() as f32;
|
||||
let y = lparam.signed_hiword() as f32;
|
||||
let click_count = self.click_state.borrow().current_count;
|
||||
let scale_factor = self.scale_factor.get();
|
||||
let event = MouseUpEvent {
|
||||
button,
|
||||
position: logical_point(x, y, scale_factor),
|
||||
modifiers: self.current_modifiers(),
|
||||
click_count: 1,
|
||||
click_count,
|
||||
};
|
||||
if callback(PlatformInput::MouseUp(event)).default_prevented {
|
||||
return Some(0);
|
||||
@@ -1316,23 +1320,18 @@ impl WindowsWindow {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWindowHandle for WindowsWindow {
|
||||
fn window_handle(
|
||||
&self,
|
||||
) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
|
||||
let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
|
||||
NonZeroIsize::new_unchecked(self.inner.hwnd.0)
|
||||
})
|
||||
.into();
|
||||
Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
|
||||
impl rwh::HasWindowHandle for WindowsWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
let raw =
|
||||
rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) })
|
||||
.into();
|
||||
Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
|
||||
}
|
||||
}
|
||||
|
||||
// todo(windows)
|
||||
impl HasDisplayHandle for WindowsWindow {
|
||||
fn display_handle(
|
||||
&self,
|
||||
) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
|
||||
impl rwh::HasDisplayHandle for WindowsWindow {
|
||||
fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::{iter, mem, ops::Range};
|
||||
|
||||
use crate::{
|
||||
black, phi, point, quad, rems, AbsoluteLength, Bounds, ContentMask, Corners, CornersRefinement,
|
||||
CursorStyle, DefiniteLength, Edges, EdgesRefinement, ElementContext, Font, FontFeatures,
|
||||
FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size,
|
||||
SizeRefinement, Styled, TextRun,
|
||||
CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight,
|
||||
Hsla, Length, Pixels, Point, PointRefinement, Rgba, SharedString, Size, SizeRefinement, Styled,
|
||||
TextRun, WindowContext,
|
||||
};
|
||||
use collections::HashSet;
|
||||
use refineable::Refineable;
|
||||
@@ -391,8 +391,8 @@ impl Style {
|
||||
pub fn paint(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
continuation: impl FnOnce(&mut ElementContext),
|
||||
cx: &mut WindowContext,
|
||||
continuation: impl FnOnce(&mut WindowContext),
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.debug_below {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
black, fill, point, px, size, Bounds, ElementContext, Hsla, LineLayout, Pixels, Point, Result,
|
||||
SharedString, StrikethroughStyle, UnderlineStyle, WrapBoundary, WrappedLineLayout,
|
||||
black, fill, point, px, size, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
|
||||
StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use smallvec::SmallVec;
|
||||
@@ -48,7 +48,7 @@ impl ShapedLine {
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
paint_line(
|
||||
origin,
|
||||
@@ -86,7 +86,7 @@ impl WrappedLine {
|
||||
&self,
|
||||
origin: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
paint_line(
|
||||
origin,
|
||||
@@ -107,7 +107,7 @@ fn paint_line(
|
||||
line_height: Pixels,
|
||||
decoration_runs: &[DecorationRun],
|
||||
wrap_boundaries: &[WrapBoundary],
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Result<()> {
|
||||
let line_bounds = Bounds::new(origin, size(layout.width, line_height));
|
||||
cx.paint_layer(line_bounds, |cx| {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds, ContentMask, Element,
|
||||
ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement,
|
||||
LayoutId, Model, PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement,
|
||||
TextStyle, ViewContext, VisualContext, WeakModel,
|
||||
ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement, LayoutId, Model,
|
||||
PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, ViewContext,
|
||||
VisualContext, WeakModel, WindowContext,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use refineable::Refineable;
|
||||
@@ -93,7 +93,7 @@ impl<V: Render> Element for View<V> {
|
||||
type RequestLayoutState = AnyElement;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
|
||||
let layout_id = element.request_layout(cx);
|
||||
@@ -105,7 +105,7 @@ impl<V: Render> Element for View<V> {
|
||||
&mut self,
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
cx.set_view_id(self.entity_id());
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
@@ -118,7 +118,7 @@ impl<V: Render> Element for View<V> {
|
||||
_: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
|
||||
element.paint(cx)
|
||||
@@ -220,7 +220,7 @@ impl<V> Eq for WeakView<V> {}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyView {
|
||||
model: AnyModel,
|
||||
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||
render: fn(&AnyView, &mut WindowContext) -> AnyElement,
|
||||
cached_style: Option<StyleRefinement>,
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ impl Element for AnyView {
|
||||
type RequestLayoutState = Option<AnyElement>;
|
||||
type PrepaintState = Option<AnyElement>;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
if let Some(style) = self.cached_style.as_ref() {
|
||||
let mut root_style = Style::default();
|
||||
root_style.refine(style);
|
||||
@@ -298,7 +298,7 @@ impl Element for AnyView {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<AnyElement> {
|
||||
cx.set_view_id(self.entity_id());
|
||||
if self.cached_style.is_some() {
|
||||
@@ -359,7 +359,7 @@ impl Element for AnyView {
|
||||
_bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
element: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
if self.cached_style.is_some() {
|
||||
cx.with_element_state::<AnyViewState, _>(
|
||||
@@ -408,7 +408,7 @@ impl IntoElement for AnyView {
|
||||
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
|
||||
pub struct AnyWeakView {
|
||||
model: AnyWeakModel,
|
||||
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
|
||||
render: fn(&AnyView, &mut WindowContext) -> AnyElement,
|
||||
}
|
||||
|
||||
impl AnyWeakView {
|
||||
@@ -447,11 +447,11 @@ impl std::fmt::Debug for AnyWeakView {
|
||||
}
|
||||
|
||||
mod any_view {
|
||||
use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render};
|
||||
use crate::{AnyElement, AnyView, IntoElement, Render, WindowContext};
|
||||
|
||||
pub(crate) fn render<V: 'static + Render>(
|
||||
view: &AnyView,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
let view = view.clone().downcast::<V>().unwrap();
|
||||
view.update(cx, |view, cx| view.render(cx).into_any_element())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ project.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
settings.workspace = true
|
||||
shellexpand.workspace = true
|
||||
postage.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -180,7 +180,8 @@ impl DevServer {
|
||||
_: Arc<Client>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let path = std::path::Path::new(&envelope.payload.path);
|
||||
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
|
||||
let path = std::path::Path::new(&expanded);
|
||||
let fs = cx.read_model(&this, |this, _| this.app_state.fs.clone())?;
|
||||
|
||||
let path_exists = fs.is_dir(path).await;
|
||||
@@ -232,9 +233,11 @@ impl DevServer {
|
||||
(this.client.clone(), project)
|
||||
})?;
|
||||
|
||||
let path = shellexpand::tilde(&remote_project.path).to_string();
|
||||
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_local_worktree(&remote_project.path, true, cx)
|
||||
project.find_or_create_local_worktree(&path, true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ impl FocusableView for ImageView {
|
||||
|
||||
impl Render for ImageView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut ElementContext| {
|
||||
let checkered_background = |bounds: Bounds<Pixels>, _, cx: &mut WindowContext| {
|
||||
let square_size = 32.0;
|
||||
|
||||
let start_y = bounds.origin.y.0;
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||
SyntaxSnapshot, ToTreeSitterPoint,
|
||||
},
|
||||
LanguageScope, Outline,
|
||||
LanguageScope, Outline, RunnableTag,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
pub use clock::ReplicaId;
|
||||
@@ -498,6 +498,13 @@ pub enum CharKind {
|
||||
Word,
|
||||
}
|
||||
|
||||
/// A runnable is a set of data about a region that could be resolved into a task
|
||||
pub struct Runnable {
|
||||
pub tags: SmallVec<[RunnableTag; 1]>,
|
||||
pub language: Arc<Language>,
|
||||
pub buffer: BufferId,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Create a new buffer with the given base text.
|
||||
pub fn local<T: Into<String>>(base_text: T, cx: &mut ModelContext<Self>) -> Self {
|
||||
@@ -2948,6 +2955,53 @@ impl BufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn runnable_ranges(
|
||||
&self,
|
||||
range: Range<Anchor>,
|
||||
) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
|
||||
let offset_range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
|
||||
let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| {
|
||||
grammar.runnable_config.as_ref().map(|config| &config.query)
|
||||
});
|
||||
|
||||
let test_configs = syntax_matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|grammar| grammar.runnable_config.as_ref())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
iter::from_fn(move || {
|
||||
let test_range = syntax_matches
|
||||
.peek()
|
||||
.and_then(|mat| {
|
||||
test_configs[mat.grammar_index].and_then(|test_configs| {
|
||||
let tags = SmallVec::from_iter(mat.captures.iter().filter_map(|capture| {
|
||||
test_configs.runnable_tags.get(&capture.index).cloned()
|
||||
}));
|
||||
|
||||
if tags.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
mat.captures
|
||||
.iter()
|
||||
.find(|capture| capture.index == test_configs.run_capture_ix)?,
|
||||
Runnable {
|
||||
tags,
|
||||
language: mat.language,
|
||||
buffer: self.remote_id(),
|
||||
},
|
||||
))
|
||||
})
|
||||
})
|
||||
.map(|(mat, test_tags)| (mat.node.byte_range(), test_tags));
|
||||
syntax_matches.advance();
|
||||
test_range
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns selections for remote peers intersecting the given range.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn remote_selections_in_range(
|
||||
|
||||
@@ -56,6 +56,7 @@ use std::{
|
||||
},
|
||||
};
|
||||
use syntax_map::SyntaxSnapshot;
|
||||
use task::RunnableTag;
|
||||
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
|
||||
use theme::SyntaxTheme;
|
||||
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
||||
@@ -828,6 +829,7 @@ pub struct Grammar {
|
||||
pub(crate) highlights_query: Option<Query>,
|
||||
pub(crate) brackets_config: Option<BracketConfig>,
|
||||
pub(crate) redactions_config: Option<RedactionConfig>,
|
||||
pub(crate) runnable_config: Option<RunnableConfig>,
|
||||
pub(crate) indents_config: Option<IndentConfig>,
|
||||
pub outline_config: Option<OutlineConfig>,
|
||||
pub embedding_config: Option<EmbeddingConfig>,
|
||||
@@ -874,6 +876,14 @@ struct RedactionConfig {
|
||||
pub redaction_capture_ix: u32,
|
||||
}
|
||||
|
||||
struct RunnableConfig {
|
||||
pub query: Query,
|
||||
/// A mapping from captures indices to known test tags
|
||||
pub runnable_tags: HashMap<u32, RunnableTag>,
|
||||
/// index of the capture that corresponds to @run
|
||||
pub run_capture_ix: u32,
|
||||
}
|
||||
|
||||
struct OverrideConfig {
|
||||
query: Query,
|
||||
values: HashMap<u32, (String, LanguageConfigOverride)>,
|
||||
@@ -915,6 +925,7 @@ impl Language {
|
||||
injection_config: None,
|
||||
override_config: None,
|
||||
redactions_config: None,
|
||||
runnable_config: None,
|
||||
error_query: Query::new(&ts_language, "(ERROR) @error").unwrap(),
|
||||
ts_language,
|
||||
highlight_map: Default::default(),
|
||||
@@ -970,6 +981,11 @@ impl Language {
|
||||
.with_redaction_query(query.as_ref())
|
||||
.context("Error loading redaction query")?;
|
||||
}
|
||||
if let Some(query) = queries.runnables {
|
||||
self = self
|
||||
.with_runnable_query(query.as_ref())
|
||||
.context("Error loading tests query")?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@@ -981,6 +997,33 @@ impl Language {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_runnable_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
.ok_or_else(|| anyhow!("cannot mutate grammar"))?;
|
||||
|
||||
let query = Query::new(&grammar.ts_language, source)?;
|
||||
let mut run_capture_index = None;
|
||||
let mut runnable_tags = HashMap::default();
|
||||
for (ix, name) in query.capture_names().iter().enumerate() {
|
||||
if *name == "run" {
|
||||
run_capture_index = Some(ix as u32);
|
||||
} else if !name.starts_with('_') {
|
||||
runnable_tags.insert(ix as u32, RunnableTag(name.to_string().into()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(run_capture_ix) = run_capture_index {
|
||||
grammar.runnable_config = Some(RunnableConfig {
|
||||
query,
|
||||
run_capture_ix,
|
||||
runnable_tags,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_outline_query(mut self, source: &str) -> Result<Self> {
|
||||
let grammar = self
|
||||
.grammar_mut()
|
||||
|
||||
@@ -46,6 +46,8 @@ struct LanguageRegistryState {
|
||||
available_languages: Vec<AvailableLanguage>,
|
||||
grammars: HashMap<Arc<str>, AvailableGrammar>,
|
||||
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
|
||||
available_lsp_adapters:
|
||||
HashMap<LanguageServerName, Arc<dyn Fn() -> Arc<CachedLspAdapter> + 'static + Send + Sync>>,
|
||||
loading_languages: HashMap<LanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
|
||||
subscription: (watch::Sender<()>, watch::Receiver<()>),
|
||||
theme: Option<Arc<Theme>>,
|
||||
@@ -122,6 +124,7 @@ pub const QUERY_FILENAME_PREFIXES: &[(
|
||||
("injections", |q| &mut q.injections),
|
||||
("overrides", |q| &mut q.overrides),
|
||||
("redactions", |q| &mut q.redactions),
|
||||
("runnables", |q| &mut q.runnables),
|
||||
];
|
||||
|
||||
/// Tree-sitter language queries for a given language.
|
||||
@@ -135,6 +138,7 @@ pub struct LanguageQueries {
|
||||
pub injections: Option<Cow<'static, str>>,
|
||||
pub overrides: Option<Cow<'static, str>>,
|
||||
pub redactions: Option<Cow<'static, str>>,
|
||||
pub runnables: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -153,6 +157,7 @@ impl LanguageRegistry {
|
||||
language_settings: Default::default(),
|
||||
loading_languages: Default::default(),
|
||||
lsp_adapters: Default::default(),
|
||||
available_lsp_adapters: HashMap::default(),
|
||||
subscription: watch::channel(),
|
||||
theme: Default::default(),
|
||||
version: 0,
|
||||
@@ -213,6 +218,38 @@ impl LanguageRegistry {
|
||||
)
|
||||
}
|
||||
|
||||
/// Registers an available language server adapter.
|
||||
///
|
||||
/// The language server is registered under the language server name, but
|
||||
/// not bound to a particular language.
|
||||
///
|
||||
/// When a language wants to load this particular language server, it will
|
||||
/// invoke the `load` function.
|
||||
pub fn register_available_lsp_adapter(
|
||||
&self,
|
||||
name: LanguageServerName,
|
||||
load: impl Fn() -> Arc<dyn LspAdapter> + 'static + Send + Sync,
|
||||
) {
|
||||
self.state.write().available_lsp_adapters.insert(
|
||||
name,
|
||||
Arc::new(move || {
|
||||
let lsp_adapter = load();
|
||||
CachedLspAdapter::new(lsp_adapter, true)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Loads the language server adapter for the language server with the given name.
|
||||
pub fn load_available_lsp_adapter(
|
||||
&self,
|
||||
name: &LanguageServerName,
|
||||
) -> Option<Arc<CachedLspAdapter>> {
|
||||
let state = self.state.read();
|
||||
let load_lsp_adapter = state.available_lsp_adapters.get(name)?;
|
||||
|
||||
Some(load_lsp_adapter())
|
||||
}
|
||||
|
||||
pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
|
||||
self.state
|
||||
.write()
|
||||
|
||||
@@ -789,5 +789,24 @@ mod tests {
|
||||
),
|
||||
language_server_names(&["deno", "eslint", "tailwind"])
|
||||
);
|
||||
|
||||
// Adding a language server not in the list of available language servers adds it to the list.
|
||||
assert_eq!(
|
||||
LanguageSettings::resolve_language_servers(
|
||||
&[
|
||||
"my-cool-language-server".into(),
|
||||
LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
|
||||
],
|
||||
&available_language_servers
|
||||
),
|
||||
language_server_names(&[
|
||||
"my-cool-language-server",
|
||||
"typescript-language-server",
|
||||
"biome",
|
||||
"deno",
|
||||
"eslint",
|
||||
"tailwind",
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ pub struct SyntaxMapCapture<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SyntaxMapMatch<'a> {
|
||||
pub language: Arc<Language>,
|
||||
pub depth: usize,
|
||||
pub pattern_index: usize,
|
||||
pub captures: &'a [QueryCapture<'a>],
|
||||
@@ -74,6 +75,7 @@ struct SyntaxMapCapturesLayer<'a> {
|
||||
}
|
||||
|
||||
struct SyntaxMapMatchesLayer<'a> {
|
||||
language: Arc<Language>,
|
||||
depth: usize,
|
||||
next_pattern_index: usize,
|
||||
next_captures: Vec<QueryCapture<'a>>,
|
||||
@@ -1004,6 +1006,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||
result.grammars.len() - 1
|
||||
});
|
||||
let mut layer = SyntaxMapMatchesLayer {
|
||||
language: layer.language.clone(),
|
||||
depth: layer.depth,
|
||||
grammar_index,
|
||||
matches,
|
||||
@@ -1036,10 +1039,13 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||
|
||||
pub fn peek(&self) -> Option<SyntaxMapMatch> {
|
||||
let layer = self.layers.first()?;
|
||||
|
||||
if !layer.has_next {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(SyntaxMapMatch {
|
||||
language: layer.language.clone(),
|
||||
depth: layer.depth,
|
||||
grammar_index: layer.grammar_index,
|
||||
pattern_index: layer.next_pattern_index,
|
||||
|
||||
@@ -26,12 +26,9 @@ project.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rust-embed = "8.2.0"
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
shellexpand.workspace = true
|
||||
smol.workspace = true
|
||||
task.workspace = true
|
||||
toml.workspace = true
|
||||
@@ -39,12 +36,10 @@ tree-sitter-bash.workspace = true
|
||||
tree-sitter-c.workspace = true
|
||||
tree-sitter-cpp.workspace = true
|
||||
tree-sitter-css.workspace = true
|
||||
tree-sitter-elixir.workspace = true
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-gomod.workspace = true
|
||||
tree-sitter-gowork.workspace = true
|
||||
tree-sitter-heex.workspace = true
|
||||
tree-sitter-jsdoc.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name = "Shell Script"
|
||||
code_fence_block_name = "bash"
|
||||
grammar = "bash"
|
||||
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"]
|
||||
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env", "PKGBUILD"]
|
||||
line_comments = ["# "]
|
||||
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
|
||||
brackets = [
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, AsyncAppContext, Task};
|
||||
pub use language::*;
|
||||
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use settings::{Settings, SettingsSources};
|
||||
use smol::fs::{self, File};
|
||||
use std::{
|
||||
any::Any,
|
||||
env::consts,
|
||||
ops::Deref,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use task::{TaskTemplate, TaskTemplates, VariableName};
|
||||
use util::{
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
maybe, ResultExt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ElixirSettings {
|
||||
pub lsp: ElixirLspSetting,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ElixirLspSetting {
|
||||
ElixirLs,
|
||||
NextLs,
|
||||
Local {
|
||||
path: String,
|
||||
arguments: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
|
||||
pub struct ElixirSettingsContent {
|
||||
lsp: Option<ElixirLspSetting>,
|
||||
}
|
||||
|
||||
impl Settings for ElixirSettings {
|
||||
const KEY: Option<&'static str> = Some("elixir");
|
||||
|
||||
type FileContent = ElixirSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElixirLspAdapter;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for ElixirLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("elixir-ls".into())
|
||||
}
|
||||
|
||||
fn will_start_server(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const NOTIFICATION_MESSAGE: &str = "Could not run the elixir language server, `elixir-ls`, because `elixir` was not found.";
|
||||
|
||||
let delegate = delegate.clone();
|
||||
Some(cx.spawn(|cx| async move {
|
||||
let elixir_output = smol::process::Command::new("elixir")
|
||||
.args(["--version"])
|
||||
.output()
|
||||
.await;
|
||||
if elixir_output.is_err() {
|
||||
if DID_SHOW_NOTIFICATION
|
||||
.compare_exchange(false, true, SeqCst, SeqCst)
|
||||
.is_ok()
|
||||
{
|
||||
cx.update(|cx| {
|
||||
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
|
||||
})?
|
||||
}
|
||||
return Err(anyhow!("cannot run elixir-ls"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let http = delegate.http_client();
|
||||
let release = latest_github_release("elixir-lsp/elixir-ls", true, false, http).await?;
|
||||
|
||||
let asset_name = format!("elixir-ls-{}.zip", &release.tag_name);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {asset_name:?}"))?;
|
||||
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.tag_name.clone(),
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
let zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
|
||||
let folder_path = container_dir.join("elixir-ls");
|
||||
let binary_path = folder_path.join("language_server.sh");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.context("error downloading release")?;
|
||||
let mut file = File::create(&zip_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
|
||||
fs::create_dir_all(&folder_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
|
||||
let unzip_status = smol::process::Command::new("unzip")
|
||||
.arg(&zip_path)
|
||||
.arg("-d")
|
||||
.arg(&folder_path)
|
||||
.output()
|
||||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != folder_path).await;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary_elixir_ls(container_dir).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary_elixir_ls(container_dir).await
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
match completion.kind.zip(completion.detail.as_ref()) {
|
||||
Some((_, detail)) if detail.starts_with("(function)") => {
|
||||
let text = detail.strip_prefix("(function) ")?;
|
||||
let filter_range = 0..text.find('(').unwrap_or(text.len());
|
||||
let source = Rope::from(format!("def {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 4..4 + text.len());
|
||||
return Some(CodeLabel {
|
||||
text: text.to_string(),
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
}
|
||||
Some((_, detail)) if detail.starts_with("(macro)") => {
|
||||
let text = detail.strip_prefix("(macro) ")?;
|
||||
let filter_range = 0..text.find('(').unwrap_or(text.len());
|
||||
let source = Rope::from(format!("defmacro {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 9..9 + text.len());
|
||||
return Some(CodeLabel {
|
||||
text: text.to_string(),
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
}
|
||||
Some((
|
||||
CompletionItemKind::CLASS
|
||||
| CompletionItemKind::MODULE
|
||||
| CompletionItemKind::INTERFACE
|
||||
| CompletionItemKind::STRUCT,
|
||||
_,
|
||||
)) => {
|
||||
let filter_range = 0..completion
|
||||
.label
|
||||
.find(" (")
|
||||
.unwrap_or(completion.label.len());
|
||||
let text = &completion.label[filter_range.clone()];
|
||||
let source = Rope::from(format!("defmodule {text}").as_str());
|
||||
let runs = language.highlight_text(&source, 10..10 + text.len());
|
||||
return Some(CodeLabel {
|
||||
text: completion.label.clone(),
|
||||
runs,
|
||||
filter_range,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
kind: SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let (text, filter_range, display_range) = match kind {
|
||||
SymbolKind::METHOD | SymbolKind::FUNCTION => {
|
||||
let text = format!("def {}", name);
|
||||
let filter_range = 4..4 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => {
|
||||
let text = format!("defmodule {}", name);
|
||||
let filter_range = 10..10 + name.len();
|
||||
let display_range = 0..filter_range.end;
|
||||
(text, filter_range, display_range)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
|
||||
text: text[display_range].to_string(),
|
||||
filter_range,
|
||||
})
|
||||
}
|
||||
|
||||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
let settings = cx.update(|cx| {
|
||||
ProjectSettings::get_global(cx)
|
||||
.lsp
|
||||
.get("elixir-ls")
|
||||
.and_then(|s| s.settings.clone())
|
||||
.unwrap_or_default()
|
||||
})?;
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"elixirLS": settings
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary_elixir_ls(
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let server_path = container_dir.join("elixir-ls/language_server.sh");
|
||||
if server_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: server_path,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
log::error!("missing executable in directory {:?}", server_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NextLspAdapter;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for NextLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("next-ls".into())
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let platform = match consts::ARCH {
|
||||
"x86_64" => "darwin_amd64",
|
||||
"aarch64" => "darwin_arm64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let release =
|
||||
latest_github_release("elixir-tools/next-ls", true, false, delegate.http_client())
|
||||
.await?;
|
||||
let version = release.tag_name;
|
||||
let asset_name = format!("next_ls_{platform}");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.with_context(|| format!("no asset found matching {asset_name:?}"))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: version,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
|
||||
let binary_path = container_dir.join("next-ls");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
|
||||
let mut file = smol::fs::File::create(&binary_path).await?;
|
||||
if !response.status().is_success() {
|
||||
Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
|
||||
// todo("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
&binary_path,
|
||||
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: vec!["--stdio".into()],
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary_next(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--stdio".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary_next(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--help".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
label_for_completion_elixir(completion, language)
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
symbol_kind: SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
label_for_symbol_elixir(name, symbol_kind, language)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary_next(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
maybe!(async {
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name == "next-ls")
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
env: None,
|
||||
arguments: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
pub struct LocalLspAdapter {
|
||||
pub path: String,
|
||||
pub arguments: Vec<String>,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspAdapter for LocalLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("local-ls".into())
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
Ok(Box::new(()) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let path = shellexpand::full(&self.path)?;
|
||||
Ok(LanguageServerBinary {
|
||||
path: PathBuf::from(path.deref()),
|
||||
env: None,
|
||||
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let path = shellexpand::full(&self.path).ok()?;
|
||||
Some(LanguageServerBinary {
|
||||
path: PathBuf::from(path.deref()),
|
||||
env: None,
|
||||
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
let path = shellexpand::full(&self.path).ok()?;
|
||||
Some(LanguageServerBinary {
|
||||
path: PathBuf::from(path.deref()),
|
||||
env: None,
|
||||
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
label_for_completion_elixir(completion, language)
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
name: &str,
|
||||
symbol: SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
label_for_symbol_elixir(name, symbol, language)
|
||||
}
|
||||
}
|
||||
|
||||
fn label_for_completion_elixir(
|
||||
completion: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
return Some(CodeLabel {
|
||||
runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
|
||||
text: completion.label.clone(),
|
||||
filter_range: 0..completion.label.len(),
|
||||
});
|
||||
}
|
||||
|
||||
fn label_for_symbol_elixir(
|
||||
name: &str,
|
||||
_: SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
Some(CodeLabel {
|
||||
runs: language.highlight_text(&name.into(), 0..name.len()),
|
||||
text: name.to_string(),
|
||||
filter_range: 0..name.len(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn elixir_task_context() -> ContextProviderWithTasks {
|
||||
// Taken from https://gist.github.com/josevalim/2e4f60a14ccd52728e3256571259d493#gistcomment-4995881
|
||||
ContextProviderWithTasks::new(TaskTemplates(vec![
|
||||
TaskTemplate {
|
||||
label: "mix test".to_owned(),
|
||||
command: "mix".to_owned(),
|
||||
args: vec!["test".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "mix test --failed".to_owned(),
|
||||
command: "mix".to_owned(),
|
||||
args: vec!["test".to_owned(), "--failed".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!("mix test {}", VariableName::Symbol.template_value()),
|
||||
command: "mix".to_owned(),
|
||||
args: vec!["test".to_owned(), VariableName::Symbol.template_value()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: format!(
|
||||
"mix test {}:{}",
|
||||
VariableName::File.template_value(),
|
||||
VariableName::Row.template_value()
|
||||
),
|
||||
command: "mix".to_owned(),
|
||||
args: vec![
|
||||
"test".to_owned(),
|
||||
format!(
|
||||
"{}:{}",
|
||||
VariableName::File.template_value(),
|
||||
VariableName::Row.template_value()
|
||||
),
|
||||
],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
label: "Elixir: break line".to_owned(),
|
||||
command: "iex".to_owned(),
|
||||
args: vec![
|
||||
"-S".to_owned(),
|
||||
"mix".to_owned(),
|
||||
"test".to_owned(),
|
||||
"-b".to_owned(),
|
||||
format!(
|
||||
"{}:{}",
|
||||
VariableName::File.template_value(),
|
||||
VariableName::Row.template_value()
|
||||
),
|
||||
],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
]))
|
||||
}
|
||||
@@ -3,22 +3,16 @@ use gpui::{AppContext, BorrowAppContext};
|
||||
pub use language::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use rust_embed::RustEmbed;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{str, sync::Arc};
|
||||
use util::{asset_str, ResultExt};
|
||||
|
||||
use crate::{
|
||||
bash::bash_task_context, elixir::elixir_task_context, python::python_task_context,
|
||||
rust::RustContextProvider,
|
||||
};
|
||||
|
||||
use self::elixir::ElixirSettings;
|
||||
use crate::{bash::bash_task_context, python::python_task_context, rust::RustContextProvider};
|
||||
|
||||
mod bash;
|
||||
mod c;
|
||||
mod css;
|
||||
mod elixir;
|
||||
mod go;
|
||||
mod json;
|
||||
mod python;
|
||||
@@ -28,15 +22,6 @@ mod tailwind;
|
||||
mod typescript;
|
||||
mod yaml;
|
||||
|
||||
// 1. Add tree-sitter-{language} parser to zed crate
|
||||
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
|
||||
// 3. Add config.toml to the newly created language directory using existing languages as a template
|
||||
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
|
||||
// Note: github highlights take the last match while zed takes the first
|
||||
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
|
||||
// and autoclosing brackets respectively
|
||||
// 6. If the language has injections add an injections.scm query file
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/"]
|
||||
#[exclude = "*.rs"]
|
||||
@@ -47,14 +32,11 @@ pub fn init(
|
||||
node_runtime: Arc<dyn NodeRuntime>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
ElixirSettings::register(cx);
|
||||
|
||||
languages.register_native_grammars([
|
||||
("bash", tree_sitter_bash::language()),
|
||||
("c", tree_sitter_c::language()),
|
||||
("cpp", tree_sitter_cpp::language()),
|
||||
("css", tree_sitter_css::language()),
|
||||
("elixir", tree_sitter_elixir::language()),
|
||||
(
|
||||
"embedded_template",
|
||||
tree_sitter_embedded_template::language(),
|
||||
@@ -62,7 +44,6 @@ pub fn init(
|
||||
("go", tree_sitter_go::language()),
|
||||
("gomod", tree_sitter_gomod::language()),
|
||||
("gowork", tree_sitter_gowork::language()),
|
||||
("heex", tree_sitter_heex::language()),
|
||||
("jsdoc", tree_sitter_jsdoc::language()),
|
||||
("json", tree_sitter_json::language()),
|
||||
("markdown", tree_sitter_markdown::language()),
|
||||
@@ -126,51 +107,11 @@ pub fn init(
|
||||
language!("cpp", vec![Arc::new(c::CLspAdapter)]);
|
||||
language!(
|
||||
"css",
|
||||
vec![
|
||||
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
vec![Arc::new(css::CssLspAdapter::new(node_runtime.clone())),]
|
||||
);
|
||||
|
||||
match &ElixirSettings::get(None, cx).lsp {
|
||||
elixir::ElixirLspSetting::ElixirLs => {
|
||||
language!(
|
||||
"elixir",
|
||||
vec![
|
||||
Arc::new(elixir::ElixirLspAdapter),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
],
|
||||
elixir_task_context()
|
||||
);
|
||||
}
|
||||
elixir::ElixirLspSetting::NextLs => {
|
||||
language!(
|
||||
"elixir",
|
||||
vec![Arc::new(elixir::NextLspAdapter)],
|
||||
elixir_task_context()
|
||||
);
|
||||
}
|
||||
elixir::ElixirLspSetting::Local { path, arguments } => {
|
||||
language!(
|
||||
"elixir",
|
||||
vec![Arc::new(elixir::LocalLspAdapter {
|
||||
path: path.clone(),
|
||||
arguments: arguments.clone(),
|
||||
})],
|
||||
elixir_task_context()
|
||||
);
|
||||
}
|
||||
}
|
||||
language!("go", vec![Arc::new(go::GoLspAdapter)]);
|
||||
language!("gomod");
|
||||
language!("gowork");
|
||||
language!(
|
||||
"heex",
|
||||
vec![
|
||||
Arc::new(elixir::ElixirLspAdapter),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
);
|
||||
language!(
|
||||
"json",
|
||||
vec![Arc::new(json::JsonLspAdapter::new(
|
||||
@@ -193,24 +134,21 @@ pub fn init(
|
||||
);
|
||||
language!(
|
||||
"tsx",
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||
node_runtime.clone()
|
||||
))]
|
||||
);
|
||||
language!(
|
||||
"typescript",
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||
node_runtime.clone()
|
||||
))]
|
||||
);
|
||||
language!(
|
||||
"javascript",
|
||||
vec![
|
||||
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
vec![Arc::new(typescript::TypeScriptLspAdapter::new(
|
||||
node_runtime.clone()
|
||||
))]
|
||||
);
|
||||
language!(
|
||||
"jsdoc",
|
||||
@@ -219,13 +157,7 @@ pub fn init(
|
||||
))]
|
||||
);
|
||||
language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
|
||||
language!(
|
||||
"erb",
|
||||
vec![
|
||||
Arc::new(ruby::RubyLanguageServer),
|
||||
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
|
||||
]
|
||||
);
|
||||
language!("erb", vec![Arc::new(ruby::RubyLanguageServer),]);
|
||||
language!("regex");
|
||||
language!(
|
||||
"yaml",
|
||||
@@ -233,13 +165,42 @@ pub fn init(
|
||||
);
|
||||
language!("proto");
|
||||
|
||||
// Register Tailwind globally as an available language server.
|
||||
//
|
||||
// This will allow users to add Tailwind support for a given language via
|
||||
// the `language_servers` setting:
|
||||
//
|
||||
// ```json
|
||||
// {
|
||||
// "languages": {
|
||||
// "My Language": {
|
||||
// "language_servers": ["tailwindcss-language-server", "..."]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
languages.register_available_lsp_adapter(
|
||||
LanguageServerName("tailwindcss-language-server".into()),
|
||||
{
|
||||
let node_runtime = node_runtime.clone();
|
||||
move || Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone()))
|
||||
},
|
||||
);
|
||||
|
||||
// Register Tailwind for the existing languages that should have it by default.
|
||||
//
|
||||
// This can be driven by the `language_servers` setting once we have a way for
|
||||
// extensions to provide their own default value for that setting.
|
||||
let tailwind_languages = [
|
||||
"Astro",
|
||||
"CSS",
|
||||
"ERB",
|
||||
"HEEX",
|
||||
"HTML",
|
||||
"JavaScript",
|
||||
"PHP",
|
||||
"Svelte",
|
||||
"TSX",
|
||||
"JavaScript",
|
||||
"Vue.js",
|
||||
];
|
||||
|
||||
@@ -250,6 +211,14 @@ pub fn init(
|
||||
);
|
||||
}
|
||||
|
||||
let eslint_languages = ["TSX", "TypeScript", "JavaScript", "Vue.js"];
|
||||
for language in eslint_languages {
|
||||
languages.register_secondary_lsp_adapter(
|
||||
language.into(),
|
||||
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
|
||||
);
|
||||
}
|
||||
|
||||
let mut subscription = languages.subscribe();
|
||||
let mut prev_language_settings = languages.language_settings();
|
||||
|
||||
|
||||
@@ -389,6 +389,7 @@ impl ContextProvider for RustContextProvider {
|
||||
"--".into(),
|
||||
"--nocapture".into(),
|
||||
],
|
||||
tags: vec!["rust-test".to_owned()],
|
||||
..TaskTemplate::default()
|
||||
},
|
||||
TaskTemplate {
|
||||
|
||||
@@ -64,6 +64,16 @@
|
||||
"<" @punctuation.bracket
|
||||
">" @punctuation.bracket)
|
||||
|
||||
[
|
||||
";"
|
||||
","
|
||||
"::"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"#"
|
||||
] @punctuation.special
|
||||
|
||||
[
|
||||
"as"
|
||||
"async"
|
||||
@@ -122,3 +132,50 @@
|
||||
(line_comment)
|
||||
(block_comment)
|
||||
] @comment
|
||||
|
||||
[
|
||||
"!"
|
||||
"!="
|
||||
"%"
|
||||
"%="
|
||||
"&"
|
||||
"&="
|
||||
"&&"
|
||||
"*"
|
||||
"*="
|
||||
"*"
|
||||
"+"
|
||||
"+="
|
||||
","
|
||||
"-"
|
||||
"-="
|
||||
"->"
|
||||
"."
|
||||
".."
|
||||
"..="
|
||||
"..."
|
||||
"/"
|
||||
"/="
|
||||
":"
|
||||
";"
|
||||
"<<"
|
||||
"<<="
|
||||
"<"
|
||||
"<="
|
||||
"="
|
||||
"=="
|
||||
"=>"
|
||||
">"
|
||||
">="
|
||||
">>"
|
||||
">>="
|
||||
"@"
|
||||
"^"
|
||||
"^="
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"?"
|
||||
] @operator
|
||||
|
||||
(lifetime) @lifetime
|
||||
|
||||
7
crates/languages/src/rust/runnables.scm
Normal file
7
crates/languages/src/rust/runnables.scm
Normal file
@@ -0,0 +1,7 @@
|
||||
(
|
||||
(attribute_item (attribute) @_attribute
|
||||
(#match? @_attribute ".*test.*"))
|
||||
.
|
||||
(function_item
|
||||
name: (_) @run)
|
||||
) @rust-test
|
||||
@@ -13,7 +13,7 @@ use language::{
|
||||
language_settings::{language_settings, LanguageSettings},
|
||||
AutoindentMode, Buffer, BufferChunks, BufferSnapshot, Capability, CharKind, Chunk, CursorShape,
|
||||
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
|
||||
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
|
||||
Outline, OutlineItem, Point, PointUtf16, Runnable, Selection, TextDimension, ToOffset as _,
|
||||
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
@@ -3132,6 +3132,31 @@ impl MultiBufferSnapshot {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn runnable_ranges(
|
||||
&self,
|
||||
range: Range<Anchor>,
|
||||
) -> impl Iterator<Item = (Range<usize>, Runnable)> + '_ {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
self.excerpts_for_range(range.clone())
|
||||
.flat_map(move |(excerpt, excerpt_offset)| {
|
||||
let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||
|
||||
excerpt
|
||||
.buffer
|
||||
.runnable_ranges(excerpt.range.context.clone())
|
||||
.map(move |(mut match_range, runnable)| {
|
||||
// Re-base onto the excerpts coordinates in the multibuffer
|
||||
match_range.start =
|
||||
excerpt_offset + (match_range.start - excerpt_buffer_start);
|
||||
match_range.end = excerpt_offset + (match_range.end - excerpt_buffer_start);
|
||||
|
||||
(match_range, runnable)
|
||||
})
|
||||
.skip_while(move |(match_range, _)| match_range.end < range.start)
|
||||
.take_while(move |(match_range, _)| match_range.start < range.end)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diagnostics_update_count(&self) -> usize {
|
||||
self.diagnostics_update_count
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ impl PickerDelegate for OutlineViewDelegate {
|
||||
.selected(selected)
|
||||
.child(
|
||||
div()
|
||||
.text_ui()
|
||||
.text_ui(cx)
|
||||
.pl(rems(outline_item.depth as f32))
|
||||
.child(styled_text),
|
||||
),
|
||||
|
||||
@@ -3066,12 +3066,34 @@ impl Project {
|
||||
.map(|lsp_adapter| lsp_adapter.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let enabled_language_servers =
|
||||
let desired_language_servers =
|
||||
settings.customized_language_servers(&available_language_servers);
|
||||
let enabled_lsp_adapters = available_lsp_adapters
|
||||
.into_iter()
|
||||
.filter(|adapter| enabled_language_servers.contains(&adapter.name))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut enabled_lsp_adapters: Vec<Arc<CachedLspAdapter>> = Vec::new();
|
||||
for desired_language_server in desired_language_servers {
|
||||
if let Some(adapter) = available_lsp_adapters
|
||||
.iter()
|
||||
.find(|adapter| adapter.name == desired_language_server)
|
||||
{
|
||||
enabled_lsp_adapters.push(adapter.clone());
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(adapter) = self
|
||||
.languages
|
||||
.load_available_lsp_adapter(&desired_language_server)
|
||||
{
|
||||
self.languages()
|
||||
.register_lsp_adapter(language.name(), adapter.adapter.clone());
|
||||
enabled_lsp_adapters.push(adapter);
|
||||
continue;
|
||||
}
|
||||
|
||||
log::warn!(
|
||||
"no language server found matching '{}'",
|
||||
desired_language_server.0
|
||||
);
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"starting language servers for {language}: {adapters}",
|
||||
@@ -3083,7 +3105,7 @@ impl Project {
|
||||
);
|
||||
|
||||
for adapter in enabled_lsp_adapters {
|
||||
self.start_language_server(worktree, adapter.clone(), language.clone(), cx);
|
||||
self.start_language_server(worktree, adapter, language.clone(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7574,17 +7596,15 @@ impl Project {
|
||||
} else {
|
||||
let fs = self.fs.clone();
|
||||
let task_abs_path = abs_path.clone();
|
||||
let tasks_file_rx =
|
||||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_tasks_for_worktree",
|
||||
},
|
||||
|cx| {
|
||||
let tasks_file_rx =
|
||||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
StaticSource::new(TrackedFile::new(tasks_file_rx, cx), cx)
|
||||
},
|
||||
StaticSource::new(TrackedFile::new(tasks_file_rx, cx)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -7596,23 +7616,20 @@ impl Project {
|
||||
} else {
|
||||
let fs = self.fs.clone();
|
||||
let task_abs_path = abs_path.clone();
|
||||
let tasks_file_rx =
|
||||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_vscode_tasks_for_worktree",
|
||||
},
|
||||
|cx| {
|
||||
let tasks_file_rx =
|
||||
watch_config_file(&cx.background_executor(), fs, task_abs_path);
|
||||
StaticSource::new(
|
||||
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
|
||||
tasks_file_rx,
|
||||
cx,
|
||||
),
|
||||
StaticSource::new(
|
||||
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
|
||||
tasks_file_rx,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use serde_json::json;
|
||||
#[cfg(not(windows))]
|
||||
use std::os;
|
||||
use std::task::Poll;
|
||||
use task::{TaskContext, TaskSource, TaskTemplate, TaskTemplates};
|
||||
use task::{TaskContext, TaskTemplate, TaskTemplates};
|
||||
use unindent::Unindent as _;
|
||||
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
|
||||
use worktree::WorktreeModelHandle as _;
|
||||
@@ -168,12 +168,11 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
@@ -215,13 +214,9 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
let inventory = project.task_inventory();
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (mut old, new) =
|
||||
inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context);
|
||||
old.extend(new);
|
||||
let (_, resolved_task) = old
|
||||
.into_iter()
|
||||
@@ -231,41 +226,39 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
})
|
||||
});
|
||||
|
||||
let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate {
|
||||
label: "cargo check".to_string(),
|
||||
command: "cargo".to_string(),
|
||||
args: vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string(),
|
||||
],
|
||||
env: HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Zunstable-options".to_string(),
|
||||
))),
|
||||
..TaskTemplate::default()
|
||||
}]))
|
||||
.unwrap();
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
let templates = cx.update(|cx| TrackedFile::new(rx, cx));
|
||||
tx.unbounded_send(tasks).unwrap();
|
||||
|
||||
let source = StaticSource::new(templates);
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update(|cx| {
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json"));
|
||||
inventory.add_source(
|
||||
global_task_source_kind.clone(),
|
||||
|cx| {
|
||||
cx.new_model(|_| {
|
||||
let source = TestTaskSource {
|
||||
tasks: TaskTemplates(vec![TaskTemplate {
|
||||
label: "cargo check".to_string(),
|
||||
command: "cargo".to_string(),
|
||||
args: vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string(),
|
||||
],
|
||||
env: HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Zunstable-options".to_string(),
|
||||
))),
|
||||
..TaskTemplate::default()
|
||||
}]),
|
||||
};
|
||||
Box::new(source) as Box<_>
|
||||
})
|
||||
},
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(global_task_source_kind.clone(), source, cx);
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
cx,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
@@ -317,20 +310,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
}
|
||||
|
||||
struct TestTaskSource {
|
||||
tasks: TaskTemplates,
|
||||
}
|
||||
|
||||
impl TaskSource for TestTaskSource {
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
|
||||
self.tasks.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
|
||||
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp::{self, Reverse},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use collections::{hash_map, HashMap, VecDeque};
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use gpui::{AppContext, Context, Model, ModelContext};
|
||||
use itertools::{Either, Itertools};
|
||||
use language::Language;
|
||||
use task::{ResolvedTask, TaskContext, TaskId, TaskSource, TaskTemplate, VariableName};
|
||||
use task::{
|
||||
static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, VariableName,
|
||||
};
|
||||
use util::{post_inc, NumericPrefixWithSuffix};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
@@ -22,14 +23,12 @@ pub struct Inventory {
|
||||
}
|
||||
|
||||
struct SourceInInventory {
|
||||
source: Model<Box<dyn TaskSource>>,
|
||||
_subscription: Subscription,
|
||||
type_id: TypeId,
|
||||
source: StaticSource,
|
||||
kind: TaskSourceKind,
|
||||
}
|
||||
|
||||
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum TaskSourceKind {
|
||||
/// bash-like commands spawned by users, not associated with any path
|
||||
UserInput,
|
||||
@@ -95,7 +94,7 @@ impl Inventory {
|
||||
pub fn add_source(
|
||||
&mut self,
|
||||
kind: TaskSourceKind,
|
||||
create_source: impl FnOnce(&mut ModelContext<Self>) -> Model<Box<dyn TaskSource>>,
|
||||
source: StaticSource,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let abs_path = kind.abs_path();
|
||||
@@ -106,16 +105,7 @@ impl Inventory {
|
||||
}
|
||||
}
|
||||
|
||||
let source = create_source(cx);
|
||||
let type_id = source.read(cx).type_id();
|
||||
let source = SourceInInventory {
|
||||
_subscription: cx.observe(&source, |_, _, cx| {
|
||||
cx.notify();
|
||||
}),
|
||||
source,
|
||||
type_id,
|
||||
kind,
|
||||
};
|
||||
let source = SourceInInventory { source, kind };
|
||||
self.sources.push(source);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -136,31 +126,12 @@ impl Inventory {
|
||||
self.sources.retain(|s| s.kind.worktree() != Some(worktree));
|
||||
}
|
||||
|
||||
pub fn source<T: TaskSource>(&self) -> Option<(Model<Box<dyn TaskSource>>, TaskSourceKind)> {
|
||||
let target_type_id = std::any::TypeId::of::<T>();
|
||||
self.sources.iter().find_map(
|
||||
|SourceInInventory {
|
||||
type_id,
|
||||
source,
|
||||
kind,
|
||||
..
|
||||
}| {
|
||||
if &target_type_id == type_id {
|
||||
Some((source.clone(), kind.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Pulls its task sources relevant to the worktree and the language given,
|
||||
/// returns all task templates with their source kinds, in no specific order.
|
||||
pub fn list_tasks(
|
||||
&self,
|
||||
language: Option<Arc<Language>>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
|
||||
name: language.name(),
|
||||
@@ -180,7 +151,7 @@ impl Inventory {
|
||||
.flat_map(|source| {
|
||||
source
|
||||
.source
|
||||
.update(cx, |source, cx| source.tasks_to_schedule(cx))
|
||||
.tasks_to_schedule()
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|task| (&source.kind, task))
|
||||
@@ -199,7 +170,6 @@ impl Inventory {
|
||||
language: Option<Arc<Language>>,
|
||||
worktree: Option<WorktreeId>,
|
||||
task_context: &TaskContext,
|
||||
cx: &mut AppContext,
|
||||
) -> (
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
@@ -246,7 +216,7 @@ impl Inventory {
|
||||
.flat_map(|source| {
|
||||
source
|
||||
.source
|
||||
.update(cx, |source, cx| source.tasks_to_schedule(cx))
|
||||
.tasks_to_schedule()
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|task| (&source.kind, task))
|
||||
@@ -387,9 +357,12 @@ fn task_variables_preference(task: &ResolvedTask) -> Reverse<usize> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_inventory {
|
||||
use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext};
|
||||
use gpui::{AppContext, Model, TestAppContext};
|
||||
use itertools::Itertools;
|
||||
use task::{TaskContext, TaskId, TaskSource, TaskTemplate, TaskTemplates};
|
||||
use task::{
|
||||
static_source::{StaticSource, TrackedFile},
|
||||
TaskContext, TaskTemplate, TaskTemplates,
|
||||
};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
use crate::Inventory;
|
||||
@@ -398,55 +371,28 @@ mod test_inventory {
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TestTask {
|
||||
id: task::TaskId,
|
||||
name: String,
|
||||
}
|
||||
|
||||
pub struct StaticTestSource {
|
||||
pub tasks: Vec<TestTask>,
|
||||
}
|
||||
|
||||
impl StaticTestSource {
|
||||
pub(super) fn new(
|
||||
task_names: impl IntoIterator<Item = String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|_| {
|
||||
Box::new(Self {
|
||||
tasks: task_names
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, name)| TestTask {
|
||||
id: TaskId(format!("task_{i}_{name}")),
|
||||
name,
|
||||
})
|
||||
.collect(),
|
||||
}) as Box<dyn TaskSource>
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskSource for StaticTestSource {
|
||||
fn tasks_to_schedule(
|
||||
&mut self,
|
||||
_cx: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> TaskTemplates {
|
||||
TaskTemplates(
|
||||
self.tasks
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|task| TaskTemplate {
|
||||
label: task.name,
|
||||
command: "test command".to_string(),
|
||||
..TaskTemplate::default()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
pub(super) fn static_test_source(
|
||||
task_names: impl IntoIterator<Item = String>,
|
||||
cx: &mut AppContext,
|
||||
) -> StaticSource {
|
||||
let tasks = TaskTemplates(
|
||||
task_names
|
||||
.into_iter()
|
||||
.map(|name| TaskTemplate {
|
||||
label: name,
|
||||
command: "test command".to_owned(),
|
||||
..TaskTemplate::default()
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
let file = TrackedFile::new(rx, cx);
|
||||
tx.unbounded_send(serde_json::to_string(&tasks).unwrap())
|
||||
.unwrap();
|
||||
StaticSource::new(file)
|
||||
}
|
||||
|
||||
pub(super) fn task_template_names(
|
||||
@@ -454,9 +400,9 @@ mod test_inventory {
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
inventory
|
||||
.list_tasks(None, worktree, cx)
|
||||
.list_tasks(None, worktree)
|
||||
.into_iter()
|
||||
.map(|(_, task)| task.label)
|
||||
.sorted()
|
||||
@@ -469,13 +415,9 @@ mod test_inventory {
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (used, current) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
worktree,
|
||||
&TaskContext::default(),
|
||||
cx,
|
||||
);
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (used, current) =
|
||||
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
|
||||
used.into_iter()
|
||||
.chain(current)
|
||||
.map(|(_, task)| task.original_task().label.clone())
|
||||
@@ -488,9 +430,9 @@ mod test_inventory {
|
||||
task_name: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (task_source_kind, task) = inventory
|
||||
.list_tasks(None, None, cx)
|
||||
.list_tasks(None, None)
|
||||
.into_iter()
|
||||
.find(|(_, task)| task.label == task_name)
|
||||
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
|
||||
@@ -508,13 +450,9 @@ mod test_inventory {
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<(TaskSourceKind, String)> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let (used, current) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
worktree,
|
||||
&TaskContext::default(),
|
||||
cx,
|
||||
);
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (used, current) =
|
||||
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
|
||||
let mut all = used;
|
||||
all.extend(current);
|
||||
all.into_iter()
|
||||
@@ -549,27 +487,25 @@ mod tests {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| StaticTestSource::new(vec!["3_task".to_string()], cx),
|
||||
static_test_source(vec!["3_task".to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec![
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(
|
||||
vec![
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
let expected_initial_state = [
|
||||
"1_a_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
@@ -622,12 +558,11 @@ mod tests {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx)
|
||||
},
|
||||
static_test_source(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
let expected_updated_state = [
|
||||
"10_hello".to_string(),
|
||||
"11_hello".to_string(),
|
||||
@@ -680,15 +615,11 @@ mod tests {
|
||||
let worktree_path_1 = Path::new("worktree_path_1");
|
||||
let worktree_2 = WorktreeId::from_usize(2);
|
||||
let worktree_path_2 = Path::new("worktree_path_2");
|
||||
|
||||
inventory_with_statics.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["user_input".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(vec!["user_input".to_string(), common_name.to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
@@ -696,12 +627,10 @@ mod tests {
|
||||
id_base: "test source",
|
||||
abs_path: path_1.to_path_buf(),
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["static_source_1".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(
|
||||
vec!["static_source_1".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
@@ -709,12 +638,10 @@ mod tests {
|
||||
id_base: "test source",
|
||||
abs_path: path_2.to_path_buf(),
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["static_source_2".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(
|
||||
vec!["static_source_2".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
@@ -723,12 +650,7 @@ mod tests {
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["worktree_1".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(vec!["worktree_1".to_string(), common_name.to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
@@ -737,16 +659,11 @@ mod tests {
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["worktree_2".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
static_test_source(vec!["worktree_2".to_string(), common_name.to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
let worktree_independent_tasks = vec![
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
|
||||
@@ -36,7 +36,7 @@ theme.workspace = true
|
||||
ui = { workspace = true, features = ["stories"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
embed-manifest = "1.4.0"
|
||||
winresource = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -12,7 +12,13 @@ fn main() {
|
||||
|
||||
let manifest = std::path::Path::new("../zed/resources/windows/manifest.xml");
|
||||
println!("cargo:rerun-if-changed={}", manifest.display());
|
||||
embed_manifest::embed_manifest(embed_manifest::new_manifest(manifest.to_str().unwrap()))
|
||||
.unwrap();
|
||||
|
||||
let mut res = winresource::WindowsResource::new();
|
||||
res.set_manifest_file(manifest.to_str().unwrap());
|
||||
|
||||
if let Err(e) = res.compile() {
|
||||
eprintln!("{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ collections.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
hex.workspace = true
|
||||
parking_lot.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
|
||||
@@ -6,9 +6,8 @@ mod task_template;
|
||||
mod vscode_format;
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::ModelContext;
|
||||
use gpui::SharedString;
|
||||
use serde::Serialize;
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -103,6 +102,8 @@ pub enum VariableName {
|
||||
Column,
|
||||
/// Text from the latest selection.
|
||||
SelectedText,
|
||||
/// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
|
||||
RunnableSymbol,
|
||||
/// Custom variable, provided by the plugin or other external source.
|
||||
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
@@ -132,6 +133,7 @@ impl std::fmt::Display for VariableName {
|
||||
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
|
||||
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
||||
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
|
||||
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
|
||||
Self::Custom(s) => write!(f, "{ZED_VARIABLE_NAME_PREFIX}CUSTOM_{s}"),
|
||||
}
|
||||
}
|
||||
@@ -169,13 +171,6 @@ pub struct TaskContext {
|
||||
pub task_variables: TaskVariables,
|
||||
}
|
||||
|
||||
/// [`Source`] produces tasks that can be scheduled.
|
||||
///
|
||||
/// Implementations of this trait could be e.g. [`StaticSource`] that parses tasks from a .json files and provides process templates to be spawned;
|
||||
/// another one could be a language server providing lenses with tests or build server listing all targets for a given project.
|
||||
pub trait TaskSource: Any {
|
||||
/// A way to erase the type of the source, processing and storing them generically.
|
||||
fn as_any(&mut self) -> &mut dyn Any;
|
||||
/// Collects all tasks available for scheduling.
|
||||
fn tasks_to_schedule(&mut self, cx: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates;
|
||||
}
|
||||
/// This is a new type representing a 'tag' on a 'runnable symbol', typically a test of main() function, found via treesitter.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RunnableTag(pub SharedString);
|
||||
|
||||
@@ -1,134 +1,110 @@
|
||||
//! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use gpui::AppContext;
|
||||
use parking_lot::RwLock;
|
||||
use serde::Deserialize;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{TaskSource, TaskTemplates};
|
||||
use crate::TaskTemplates;
|
||||
use futures::channel::mpsc::UnboundedReceiver;
|
||||
|
||||
/// The source of tasks defined in a tasks config file.
|
||||
pub struct StaticSource {
|
||||
tasks: TaskTemplates,
|
||||
_templates: Model<TrackedFile<TaskTemplates>>,
|
||||
_subscription: Subscription,
|
||||
tasks: TrackedFile<TaskTemplates>,
|
||||
}
|
||||
|
||||
/// A Wrapper around deserializable T that keeps track of its contents
|
||||
/// via a provided channel. Once T value changes, the observers of [`TrackedFile`] are
|
||||
/// notified.
|
||||
pub struct TrackedFile<T> {
|
||||
parsed_contents: T,
|
||||
parsed_contents: Arc<RwLock<T>>,
|
||||
}
|
||||
|
||||
impl<T: PartialEq + 'static> TrackedFile<T> {
|
||||
impl<T: PartialEq + 'static + Sync> TrackedFile<T> {
|
||||
/// Initializes new [`TrackedFile`] with a type that's deserializable.
|
||||
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Model<Self>
|
||||
pub fn new(mut tracker: UnboundedReceiver<String>, cx: &mut AppContext) -> Self
|
||||
where
|
||||
T: for<'a> Deserialize<'a> + Default,
|
||||
T: for<'a> Deserialize<'a> + Default + Send,
|
||||
{
|
||||
cx.new_model(move |cx| {
|
||||
cx.spawn(|tracked_file, mut cx| async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if !new_contents.trim().is_empty() {
|
||||
// String -> T (ZedTaskFormat)
|
||||
// String -> U (VsCodeFormat) -> Into::into T
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str(&new_contents).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {
|
||||
if tracked_file.parsed_contents != new_contents {
|
||||
tracked_file.parsed_contents = new_contents;
|
||||
cx.notify();
|
||||
let parsed_contents: Arc<RwLock<T>> = Arc::default();
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let parsed_contents = parsed_contents.clone();
|
||||
async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if Arc::strong_count(&parsed_contents) == 1 {
|
||||
// We're no longer being observed. Stop polling.
|
||||
break;
|
||||
}
|
||||
if !new_contents.trim().is_empty() {
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str::<T>(&new_contents).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
})?;
|
||||
let mut contents = parsed_contents.write();
|
||||
*contents = new_contents;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Self {
|
||||
parsed_contents: Default::default(),
|
||||
}
|
||||
})
|
||||
Self { parsed_contents }
|
||||
}
|
||||
|
||||
/// Initializes new [`TrackedFile`] with a type that's convertible from another deserializable type.
|
||||
pub fn new_convertible<U: for<'a> Deserialize<'a> + TryInto<T, Error = anyhow::Error>>(
|
||||
mut tracker: UnboundedReceiver<String>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self>
|
||||
) -> Self
|
||||
where
|
||||
T: Default,
|
||||
T: Default + Send,
|
||||
{
|
||||
cx.new_model(move |cx| {
|
||||
cx.spawn(|tracked_file, mut cx| async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if !new_contents.trim().is_empty() {
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str::<U>(&new_contents).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(new_contents) = new_contents.try_into().log_err() else {
|
||||
continue;
|
||||
};
|
||||
tracked_file.update(&mut cx, |tracked_file: &mut TrackedFile<T>, cx| {
|
||||
if tracked_file.parsed_contents != new_contents {
|
||||
tracked_file.parsed_contents = new_contents;
|
||||
cx.notify();
|
||||
let parsed_contents: Arc<RwLock<T>> = Arc::default();
|
||||
cx.background_executor()
|
||||
.spawn({
|
||||
let parsed_contents = parsed_contents.clone();
|
||||
async move {
|
||||
while let Some(new_contents) = tracker.next().await {
|
||||
if Arc::strong_count(&parsed_contents) == 1 {
|
||||
// We're no longer being observed. Stop polling.
|
||||
break;
|
||||
}
|
||||
|
||||
if !new_contents.trim().is_empty() {
|
||||
let Some(new_contents) =
|
||||
serde_json_lenient::from_str::<U>(&new_contents).log_err()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
})?;
|
||||
let Some(new_contents) = new_contents.try_into().log_err() else {
|
||||
continue;
|
||||
};
|
||||
let mut contents = parsed_contents.write();
|
||||
*contents = new_contents;
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
Self {
|
||||
parsed_contents: Default::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get(&self) -> &T {
|
||||
&self.parsed_contents
|
||||
Self {
|
||||
parsed_contents: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticSource {
|
||||
/// Initializes the static source, reacting on tasks config changes.
|
||||
pub fn new(
|
||||
templates: Model<TrackedFile<TaskTemplates>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|cx| {
|
||||
let _subscription = cx.observe(
|
||||
&templates,
|
||||
move |source: &mut Box<(dyn TaskSource + 'static)>, new_templates, cx| {
|
||||
if let Some(static_source) = source.as_any().downcast_mut::<Self>() {
|
||||
static_source.tasks = new_templates.read(cx).get().clone();
|
||||
cx.notify();
|
||||
}
|
||||
},
|
||||
);
|
||||
Box::new(Self {
|
||||
tasks: TaskTemplates::default(),
|
||||
_templates: templates,
|
||||
_subscription,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskSource for StaticSource {
|
||||
fn tasks_to_schedule(&mut self, _: &mut ModelContext<Box<dyn TaskSource>>) -> TaskTemplates {
|
||||
self.tasks.clone()
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
pub fn new(tasks: TrackedFile<TaskTemplates>) -> Self {
|
||||
Self { tasks }
|
||||
}
|
||||
/// Returns current list of tasks
|
||||
pub fn tasks_to_schedule(&self) -> TaskTemplates {
|
||||
self.tasks.parsed_contents.read().clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ pub struct TaskTemplate {
|
||||
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
#[serde(default)]
|
||||
pub reveal: RevealStrategy,
|
||||
|
||||
/// Represents the tags which this template attaches to. Adding this removes this task from other UI.
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
|
||||
@@ -9,7 +9,6 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
editor.workspace = true
|
||||
file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::settings::Settings;
|
||||
use anyhow::Context;
|
||||
use editor::Editor;
|
||||
use editor::{tasks::task_context, Editor};
|
||||
use gpui::{AppContext, ViewContext, WindowContext};
|
||||
use language::{BasicContextProvider, ContextProvider, Language};
|
||||
use language::Language;
|
||||
use modal::TasksModal;
|
||||
use project::{Location, TaskSourceKind, WorktreeId};
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate, TaskVariables};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
use project::WorktreeId;
|
||||
use workspace::tasks::schedule_task;
|
||||
use workspace::{tasks::schedule_resolved_task, Workspace};
|
||||
|
||||
mod modal;
|
||||
mod settings;
|
||||
@@ -93,9 +88,9 @@ fn spawn_task_with_name(name: String, cx: &mut ViewContext<Workspace>) {
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
let (worktree, language) = active_item_selection_properties(workspace, cx);
|
||||
let tasks = workspace.project().update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
inventory.list_tasks(language, worktree, cx)
|
||||
})
|
||||
project
|
||||
.task_inventory()
|
||||
.update(cx, |inventory, _| inventory.list_tasks(language, worktree))
|
||||
});
|
||||
let (task_source_kind, target_task) =
|
||||
tasks.into_iter().find(|(_, task)| task.label == name)?;
|
||||
@@ -148,168 +143,6 @@ fn active_item_selection_properties(
|
||||
(worktree_id, language)
|
||||
}
|
||||
|
||||
fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
|
||||
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
|
||||
let cwd = task_cwd(workspace, cx).log_err().flatten();
|
||||
let editor = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))?;
|
||||
|
||||
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let (buffer, _, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)?;
|
||||
let snapshot = editor.snapshot(cx);
|
||||
Some((selection, buffer, snapshot))
|
||||
})?;
|
||||
let language_context_provider = buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.and_then(|language| language.context_provider())
|
||||
.unwrap_or_else(|| Arc::new(BasicContextProvider));
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let worktree_abs_path = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()))
|
||||
.and_then(|worktree_id| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
});
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let task_variables = combine_task_variables(
|
||||
worktree_abs_path.as_deref(),
|
||||
location,
|
||||
language_context_provider.as_ref(),
|
||||
cx,
|
||||
)
|
||||
.log_err()?;
|
||||
Some(TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
})
|
||||
}
|
||||
|
||||
task_context_impl(workspace, cx).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
worktree_abs_path: Option<&Path>,
|
||||
location: Location,
|
||||
context_provider: &dyn ContextProvider,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> anyhow::Result<TaskVariables> {
|
||||
if context_provider.is_basic() {
|
||||
context_provider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building basic provider context")
|
||||
} else {
|
||||
let mut basic_context = BasicContextProvider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building basic default context")?;
|
||||
basic_context.extend(
|
||||
context_provider
|
||||
.build_context(worktree_abs_path, &location, cx)
|
||||
.context("building provider context ")?,
|
||||
);
|
||||
Ok(basic_context)
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_task(
|
||||
workspace: &Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
task_to_resolve: &TaskTemplate,
|
||||
task_cx: &TaskContext,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
if let Some(spawn_in_terminal) =
|
||||
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
|
||||
{
|
||||
schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
spawn_in_terminal,
|
||||
omit_history,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_resolved_task(
|
||||
workspace: &Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
mut resolved_task: ResolvedTask,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
||||
if !omit_history {
|
||||
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.task_scheduled(task_source_kind, resolved_task);
|
||||
})
|
||||
});
|
||||
}
|
||||
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
|
||||
}
|
||||
}
|
||||
|
||||
fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result<Option<PathBuf>> {
|
||||
let project = workspace.project().read(cx);
|
||||
let available_worktrees = project
|
||||
.worktrees()
|
||||
.filter(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
worktree.is_visible()
|
||||
&& worktree.is_local()
|
||||
&& worktree.root_entry().map_or(false, |e| e.is_dir())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let cwd = match available_worktrees.len() {
|
||||
0 => None,
|
||||
1 => Some(available_worktrees[0].read(cx).abs_path()),
|
||||
_ => {
|
||||
let cwd_for_active_entry = project.active_entry().and_then(|entry_id| {
|
||||
available_worktrees.into_iter().find_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
if worktree.contains_entry(entry_id) {
|
||||
Some(worktree.abs_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
anyhow::ensure!(
|
||||
cwd_for_active_entry.is_some(),
|
||||
"Cannot determine task cwd for multiple worktrees"
|
||||
);
|
||||
cwd_for_active_entry
|
||||
}
|
||||
};
|
||||
Ok(cwd.map(|path| path.to_path_buf()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{active_item_selection_properties, schedule_resolved_task};
|
||||
use crate::active_item_selection_properties;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, Global,
|
||||
@@ -16,7 +16,7 @@ use ui::{
|
||||
Tooltip, WindowContext,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{ModalView, Workspace};
|
||||
use workspace::{tasks::schedule_resolved_task, ModalView, Workspace};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -209,12 +209,11 @@ impl PickerDelegate for TasksModalDelegate {
|
||||
return Vec::new();
|
||||
};
|
||||
let (used, current) =
|
||||
picker.delegate.inventory.update(cx, |inventory, cx| {
|
||||
picker.delegate.inventory.update(cx, |inventory, _| {
|
||||
inventory.used_and_current_resolved_tasks(
|
||||
language,
|
||||
worktree,
|
||||
&picker.delegate.task_context,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
picker.delegate.last_used_candidate_index = if used.is_empty() {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use editor::{CursorLayout, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, ElementContext,
|
||||
FocusHandle, Font, FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler,
|
||||
InteractiveElement, Interactivity, IntoElement, LayoutId, Model, ModelContext,
|
||||
ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, Point, ShapedLine,
|
||||
StatefulInteractiveElement, StrikethroughStyle, Styled, TextRun, TextStyle, UnderlineStyle,
|
||||
WeakView, WhiteSpace, WindowContext, WindowTextSystem,
|
||||
div, fill, point, px, relative, AnyElement, Bounds, DispatchPhase, Element, FocusHandle, Font,
|
||||
FontStyle, FontWeight, HighlightStyle, Hitbox, Hsla, InputHandler, InteractiveElement,
|
||||
Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton,
|
||||
MouseMoveEvent, Pixels, Point, ShapedLine, StatefulInteractiveElement, StrikethroughStyle,
|
||||
Styled, TextRun, TextStyle, UnderlineStyle, WeakView, WhiteSpace, WindowContext,
|
||||
WindowTextSystem,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::CursorShape;
|
||||
@@ -85,7 +85,7 @@ impl LayoutCell {
|
||||
origin: Point<Pixels>,
|
||||
layout: &LayoutState,
|
||||
_visible_bounds: Bounds<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let pos = {
|
||||
let point = self.point;
|
||||
@@ -124,7 +124,7 @@ impl LayoutRect {
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut ElementContext) {
|
||||
fn paint(&self, origin: Point<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
|
||||
let position = {
|
||||
let alac_point = self.point;
|
||||
point(
|
||||
@@ -418,7 +418,7 @@ impl TerminalElement {
|
||||
origin: Point<Pixels>,
|
||||
mode: TermMode,
|
||||
hitbox: &Hitbox,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus = self.focus.clone();
|
||||
let terminal = self.terminal.clone();
|
||||
@@ -544,7 +544,7 @@ impl Element for TerminalElement {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = LayoutState;
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
self.interactivity.occlude_mouse();
|
||||
let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
|
||||
style.size.width = relative(1.).into();
|
||||
@@ -560,7 +560,7 @@ impl Element for TerminalElement {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
self.interactivity
|
||||
.prepaint(bounds, bounds.size, cx, |_, _, hitbox, cx| {
|
||||
@@ -777,7 +777,7 @@ impl Element for TerminalElement {
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
layout: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext<'_>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) {
|
||||
cx.paint_quad(fill(bounds, layout.background_color));
|
||||
let origin = bounds.origin + Point::new(layout.gutter, px(0.));
|
||||
|
||||
@@ -160,7 +160,7 @@ impl RenderOnce for Key {
|
||||
}
|
||||
})
|
||||
.h(rems_from_px(14.))
|
||||
.text_ui()
|
||||
.text_ui(cx)
|
||||
.line_height(relative(1.))
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(self.key.clone())
|
||||
|
||||
@@ -108,10 +108,10 @@ impl RenderOnce for LabelLike {
|
||||
)
|
||||
})
|
||||
.map(|this| match self.size {
|
||||
LabelSize::Large => this.text_ui_lg(),
|
||||
LabelSize::Default => this.text_ui(),
|
||||
LabelSize::Small => this.text_ui_sm(),
|
||||
LabelSize::XSmall => this.text_ui_xs(),
|
||||
LabelSize::Large => this.text_ui_lg(cx),
|
||||
LabelSize::Default => this.text_ui(cx),
|
||||
LabelSize::Small => this.text_ui_sm(cx),
|
||||
LabelSize::XSmall => this.text_ui_xs(cx),
|
||||
})
|
||||
.when(self.line_height_style == LineHeightStyle::UiLabel, |this| {
|
||||
this.line_height(relative(1.))
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds,
|
||||
DismissEvent, DispatchPhase, Element, ElementContext, ElementId, HitboxId, InteractiveElement,
|
||||
IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
|
||||
VisualContext, WindowContext,
|
||||
DismissEvent, DispatchPhase, Element, ElementId, HitboxId, InteractiveElement, IntoElement,
|
||||
LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
@@ -112,8 +112,8 @@ impl<M: ManagedView> PopoverMenu<M> {
|
||||
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut ElementContext) -> R,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut Self, &mut PopoverMenuElementState<M>, &mut WindowContext) -> R,
|
||||
) -> R {
|
||||
cx.with_element_state::<PopoverMenuElementState<M>, _>(
|
||||
Some(self.id.clone()),
|
||||
@@ -173,7 +173,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
let mut menu_layout_id = None;
|
||||
@@ -221,7 +221,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
&mut self,
|
||||
_bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<HitboxId> {
|
||||
self.with_element_state(cx, |_this, element_state, cx| {
|
||||
if let Some(child) = request_layout.child_element.as_mut() {
|
||||
@@ -245,7 +245,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
|
||||
_: Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
child_hitbox: &mut Option<HitboxId>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.with_element_state(cx, |_this, _element_state, cx| {
|
||||
if let Some(mut child) = request_layout.child_element.take() {
|
||||
|
||||
@@ -2,9 +2,8 @@ use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
anchored, deferred, div, AnchorCorner, AnyElement, Bounds, DismissEvent, DispatchPhase,
|
||||
Element, ElementContext, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId,
|
||||
ManagedView, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext,
|
||||
WindowContext,
|
||||
Element, ElementId, Hitbox, InteractiveElement, IntoElement, LayoutId, ManagedView,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, View, VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
pub struct RightClickMenu<M: ManagedView> {
|
||||
@@ -41,8 +40,8 @@ impl<M: ManagedView> RightClickMenu<M> {
|
||||
|
||||
fn with_element_state<R>(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut ElementContext) -> R,
|
||||
cx: &mut WindowContext,
|
||||
f: impl FnOnce(&mut Self, &mut MenuHandleElementState<M>, &mut WindowContext) -> R,
|
||||
) -> R {
|
||||
cx.with_element_state::<MenuHandleElementState<M>, _>(
|
||||
Some(self.id.clone()),
|
||||
@@ -89,19 +88,24 @@ impl<M> Default for MenuHandleElementState<M> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MenuHandleFrameState {
|
||||
pub struct RequestLayoutState {
|
||||
child_layout_id: Option<LayoutId>,
|
||||
child_element: Option<AnyElement>,
|
||||
menu_element: Option<AnyElement>,
|
||||
}
|
||||
|
||||
pub struct PrepaintState {
|
||||
hitbox: Hitbox,
|
||||
child_bounds: Option<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
type RequestLayoutState = MenuHandleFrameState;
|
||||
type PrepaintState = Hitbox;
|
||||
type RequestLayoutState = RequestLayoutState;
|
||||
type PrepaintState = PrepaintState;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
let mut menu_layout_id = None;
|
||||
@@ -137,7 +141,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
|
||||
(
|
||||
layout_id,
|
||||
MenuHandleFrameState {
|
||||
RequestLayoutState {
|
||||
child_element,
|
||||
child_layout_id,
|
||||
menu_element,
|
||||
@@ -150,8 +154,8 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
) -> Hitbox {
|
||||
cx: &mut WindowContext,
|
||||
) -> PrepaintState {
|
||||
cx.with_element_id(Some(self.id.clone()), |cx| {
|
||||
let hitbox = cx.insert_hitbox(bounds, false);
|
||||
|
||||
@@ -163,7 +167,12 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
menu.prepaint(cx);
|
||||
}
|
||||
|
||||
hitbox
|
||||
PrepaintState {
|
||||
hitbox,
|
||||
child_bounds: request_layout
|
||||
.child_layout_id
|
||||
.map(|layout_id| cx.layout_bounds(layout_id)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -171,8 +180,8 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
&mut self,
|
||||
_bounds: Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
prepaint_state: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
self.with_element_state(cx, |this, element_state, cx| {
|
||||
if let Some(mut child) = request_layout.child_element.take() {
|
||||
@@ -191,10 +200,9 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
let attach = this.attach;
|
||||
let menu = element_state.menu.clone();
|
||||
let position = element_state.position.clone();
|
||||
let child_layout_id = request_layout.child_layout_id;
|
||||
let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
|
||||
let child_bounds = prepaint_state.child_bounds;
|
||||
|
||||
let hitbox_id = hitbox.id;
|
||||
let hitbox_id = prepaint_state.hitbox.id;
|
||||
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
&& event.button == MouseButton::Right
|
||||
@@ -219,7 +227,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
|
||||
.detach();
|
||||
cx.focus_view(&new_menu);
|
||||
*menu.borrow_mut() = Some(new_menu);
|
||||
*position.borrow_mut() = if child_layout_id.is_some() {
|
||||
*position.borrow_mut() = if let Some(child_bounds) = child_bounds {
|
||||
if let Some(attach) = attach {
|
||||
attach.corner(child_bounds)
|
||||
} else {
|
||||
|
||||
@@ -96,7 +96,7 @@ pub fn tooltip_container<V>(
|
||||
v_flex()
|
||||
.elevation_2(cx)
|
||||
.font_family(ui_font)
|
||||
.text_ui()
|
||||
.text_ui(cx)
|
||||
.text_color(cx.theme().colors().text)
|
||||
.py_1()
|
||||
.px_2()
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
pub use gpui::prelude::*;
|
||||
pub use gpui::{
|
||||
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementContext,
|
||||
ElementId, InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled,
|
||||
ViewContext, WindowContext,
|
||||
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
|
||||
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
pub use crate::clickable::*;
|
||||
pub use crate::disableable::*;
|
||||
pub use crate::fixed::*;
|
||||
pub use crate::selectable::*;
|
||||
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle};
|
||||
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography};
|
||||
pub use crate::visible_on_hover::*;
|
||||
pub use crate::{h_flex, v_flex};
|
||||
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use gpui::{hsla, px, Styled, WindowContext};
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{ElevationIndex, UiTextSize};
|
||||
use crate::ElevationIndex;
|
||||
|
||||
fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -> E {
|
||||
this.bg(cx.theme().colors().elevated_surface_background)
|
||||
@@ -29,66 +27,6 @@ pub trait StyledExt: Styled + Sized {
|
||||
self.flex().flex_col()
|
||||
}
|
||||
|
||||
/// Sets the text size using a [`UiTextSize`].
|
||||
fn text_ui_size(self, size: UiTextSize) -> Self {
|
||||
self.text_size(size.rems())
|
||||
}
|
||||
|
||||
/// The large size for UI text.
|
||||
///
|
||||
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_lg(self) -> Self {
|
||||
self.text_size(UiTextSize::Large.rems())
|
||||
}
|
||||
|
||||
/// The default size for UI text.
|
||||
///
|
||||
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui_sm` for smaller text.
|
||||
fn text_ui(self) -> Self {
|
||||
self.text_size(UiTextSize::default().rems())
|
||||
}
|
||||
|
||||
/// The small size for UI text.
|
||||
///
|
||||
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_sm(self) -> Self {
|
||||
self.text_size(UiTextSize::Small.rems())
|
||||
}
|
||||
|
||||
/// The extra small size for UI text.
|
||||
///
|
||||
/// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_xs(self) -> Self {
|
||||
self.text_size(UiTextSize::XSmall.rems())
|
||||
}
|
||||
|
||||
/// The font size for buffer text.
|
||||
///
|
||||
/// Retrieves the default font size, or the user's custom font size if set.
|
||||
///
|
||||
/// This should only be used for text that is displayed in a buffer,
|
||||
/// or other places that text needs to match the user's buffer font size.
|
||||
fn text_buffer(self, cx: &mut WindowContext) -> Self {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
self.text_size(settings.buffer_font_size(cx))
|
||||
}
|
||||
|
||||
/// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
|
||||
///
|
||||
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
|
||||
|
||||
@@ -6,8 +6,73 @@ use theme::{ActiveTheme, ThemeSettings};
|
||||
|
||||
use crate::rems_from_px;
|
||||
|
||||
/// Extends [`gpui::Styled`] with typography-related styling methods.
|
||||
pub trait StyledTypography: Styled + Sized {
|
||||
/// Sets the text size using a [`UiTextSize`].
|
||||
fn text_ui_size(self, size: TextSize, cx: &WindowContext) -> Self {
|
||||
self.text_size(size.rems(cx))
|
||||
}
|
||||
|
||||
/// The large size for UI text.
|
||||
///
|
||||
/// `1rem` or `16px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_lg(self, cx: &WindowContext) -> Self {
|
||||
self.text_size(TextSize::Large.rems(cx))
|
||||
}
|
||||
|
||||
/// The default size for UI text.
|
||||
///
|
||||
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui_sm` for smaller text.
|
||||
fn text_ui(self, cx: &WindowContext) -> Self {
|
||||
self.text_size(TextSize::default().rems(cx))
|
||||
}
|
||||
|
||||
/// The small size for UI text.
|
||||
///
|
||||
/// `0.75rem` or `12px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_sm(self, cx: &WindowContext) -> Self {
|
||||
self.text_size(TextSize::Small.rems(cx))
|
||||
}
|
||||
|
||||
/// The extra small size for UI text.
|
||||
///
|
||||
/// `0.625rem` or `10px` at the default scale of `1rem` = `16px`.
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
///
|
||||
/// Use `text_ui` for regular-sized text.
|
||||
fn text_ui_xs(self, cx: &WindowContext) -> Self {
|
||||
self.text_size(TextSize::XSmall.rems(cx))
|
||||
}
|
||||
|
||||
/// The font size for buffer text.
|
||||
///
|
||||
/// Retrieves the default font size, or the user's custom font size if set.
|
||||
///
|
||||
/// This should only be used for text that is displayed in a buffer,
|
||||
/// or other places that text needs to match the user's buffer font size.
|
||||
fn text_buffer(self, cx: &mut WindowContext) -> Self {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
self.text_size(settings.buffer_font_size(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Styled> StyledTypography for E {}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum UiTextSize {
|
||||
pub enum TextSize {
|
||||
/// The default size for UI text.
|
||||
///
|
||||
/// `0.825rem` or `14px` at the default scale of `1rem` = `16px`.
|
||||
@@ -35,15 +100,28 @@ pub enum UiTextSize {
|
||||
///
|
||||
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
|
||||
XSmall,
|
||||
|
||||
/// The `ui_font_size` set by the user.
|
||||
UI,
|
||||
/// The `buffer_font_size` set by the user.
|
||||
Editor,
|
||||
// TODO: The terminal settings will need to be passed to
|
||||
// ThemeSettings before we can enable this.
|
||||
//// The `terminal.font_size` set by the user.
|
||||
// Terminal,
|
||||
}
|
||||
|
||||
impl UiTextSize {
|
||||
pub fn rems(self) -> Rems {
|
||||
impl TextSize {
|
||||
pub fn rems(self, cx: &WindowContext) -> Rems {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
|
||||
match self {
|
||||
Self::Large => rems_from_px(16.),
|
||||
Self::Default => rems_from_px(14.),
|
||||
Self::Small => rems_from_px(12.),
|
||||
Self::XSmall => rems_from_px(10.),
|
||||
Self::UI => rems_from_px(theme_settings.ui_font_size.into()),
|
||||
Self::Editor => rems_from_px(theme_settings.buffer_font_size.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ collections.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
editor.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = [
|
||||
|
||||
@@ -42,6 +42,9 @@ fn focused(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
|
||||
fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
Vim::update(cx, |vim, cx| {
|
||||
if !vim.enabled {
|
||||
return;
|
||||
}
|
||||
if let Some(previous_editor) = vim.active_editor.clone() {
|
||||
vim.stop_recording_immediately(NormalBefore.boxed_clone());
|
||||
if previous_editor
|
||||
@@ -51,6 +54,9 @@ fn blurred(editor: View<Editor>, cx: &mut WindowContext) {
|
||||
vim.clear_operator(cx);
|
||||
}
|
||||
}
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_cursor_shape(language::CursorShape::Hollow, cx);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ impl Render for ModeIndicator {
|
||||
|
||||
Label::new(format!("{} -- {} --", self.operators, mode))
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(LineHeightStyle::UiLabel)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ use editor::{
|
||||
movement::{self, FindRange},
|
||||
Bias, DisplayPoint,
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use gpui::{actions, impl_actions, ViewContext, WindowContext};
|
||||
use language::{char_kind, BufferSnapshot, CharKind, Point, Selection};
|
||||
use serde::Deserialize;
|
||||
@@ -801,15 +804,20 @@ fn surrounding_markers(
|
||||
let mut matched_closes = 0;
|
||||
let mut opening = None;
|
||||
|
||||
let mut before_ch = match movement::chars_before(map, point).next() {
|
||||
Some((ch, _)) => ch,
|
||||
_ => '\0',
|
||||
};
|
||||
if let Some((ch, range)) = movement::chars_after(map, point).next() {
|
||||
if ch == open_marker {
|
||||
if ch == open_marker && before_ch != '\\' {
|
||||
if open_marker == close_marker {
|
||||
let mut total = 0;
|
||||
for (ch, _) in movement::chars_before(map, point) {
|
||||
for ((ch, _), (before_ch, _)) in movement::chars_before(map, point).tuple_windows()
|
||||
{
|
||||
if ch == '\n' {
|
||||
break;
|
||||
}
|
||||
if ch == open_marker {
|
||||
if ch == open_marker && before_ch != '\\' {
|
||||
total += 1;
|
||||
}
|
||||
}
|
||||
@@ -823,11 +831,15 @@ fn surrounding_markers(
|
||||
}
|
||||
|
||||
if opening.is_none() {
|
||||
for (ch, range) in movement::chars_before(map, point) {
|
||||
for ((ch, range), (before_ch, _)) in movement::chars_before(map, point).tuple_windows() {
|
||||
if ch == '\n' && !search_across_lines {
|
||||
break;
|
||||
}
|
||||
|
||||
if before_ch == '\\' {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ch == open_marker {
|
||||
if matched_closes == 0 {
|
||||
opening = Some(range);
|
||||
@@ -839,15 +851,18 @@ fn surrounding_markers(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opening.is_none() {
|
||||
for (ch, range) in movement::chars_after(map, point) {
|
||||
if ch == open_marker {
|
||||
opening = Some(range);
|
||||
break;
|
||||
} else if ch == close_marker {
|
||||
break;
|
||||
if before_ch != '\\' {
|
||||
if ch == open_marker {
|
||||
opening = Some(range);
|
||||
break;
|
||||
} else if ch == close_marker {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
before_ch = ch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,21 +872,28 @@ fn surrounding_markers(
|
||||
|
||||
let mut matched_opens = 0;
|
||||
let mut closing = None;
|
||||
|
||||
before_ch = match movement::chars_before(map, opening.end).next() {
|
||||
Some((ch, _)) => ch,
|
||||
_ => '\0',
|
||||
};
|
||||
for (ch, range) in movement::chars_after(map, opening.end) {
|
||||
if ch == '\n' && !search_across_lines {
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == close_marker {
|
||||
if matched_opens == 0 {
|
||||
closing = Some(range);
|
||||
break;
|
||||
if before_ch != '\\' {
|
||||
if ch == close_marker {
|
||||
if matched_opens == 0 {
|
||||
closing = Some(range);
|
||||
break;
|
||||
}
|
||||
matched_opens -= 1;
|
||||
} else if ch == open_marker {
|
||||
matched_opens += 1;
|
||||
}
|
||||
matched_opens -= 1;
|
||||
} else if ch == open_marker {
|
||||
matched_opens += 1;
|
||||
}
|
||||
|
||||
before_ch = ch;
|
||||
}
|
||||
|
||||
let Some(mut closing) = closing else {
|
||||
@@ -1467,6 +1489,32 @@ mod test {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_singleline_surrounding_character_objects_with_escape(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.set_shared_state(indoc! {
|
||||
"h\"e\\\"lˇlo \\\"world\"!"
|
||||
})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
|
||||
cx.assert_shared_state(indoc! {
|
||||
"h\"«e\\\"llo \\\"worldˇ»\"!"
|
||||
})
|
||||
.await;
|
||||
|
||||
cx.set_shared_state(indoc! {
|
||||
"hello \"teˇst \\\"inside\\\" world\""
|
||||
})
|
||||
.await;
|
||||
cx.simulate_shared_keystrokes(["v", "i", "\""]).await;
|
||||
cx.assert_shared_state(indoc! {
|
||||
"hello \"«test \\\"inside\\\" worldˇ»\""
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_vertical_bars(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{"Put":{"state":"h\"e\\\"lˇlo \\\"world\"!"}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"\""}
|
||||
{"Get":{"state":"h\"«e\\\"llo \\\"worldˇ»\"!","mode":"Visual"}}
|
||||
{"Put":{"state":"hello \"teˇst \\\"inside\\\" world\""}}
|
||||
{"Key":"v"}
|
||||
{"Key":"i"}
|
||||
{"Key":"\""}
|
||||
{"Get":{"state":"hello \"«test \\\"inside\\\" worldˇ»\"","mode":"Visual"}}
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
},
|
||||
toolbar::Toolbar,
|
||||
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
|
||||
CloseWindow, NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
|
||||
NewCenterTerminal, NewFile, NewSearch, OpenInTerminal, OpenTerminal, OpenVisible,
|
||||
SplitDirection, ToggleZoom, Workspace,
|
||||
};
|
||||
use anyhow::Result;
|
||||
@@ -879,8 +879,6 @@ impl Pane {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
// Close the window when there's no active items to close.
|
||||
cx.dispatch_action(Box::new(CloseWindow));
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].item_id();
|
||||
|
||||
@@ -759,7 +759,7 @@ mod element {
|
||||
fn layout_handle(
|
||||
axis: Axis,
|
||||
pane_bounds: Bounds<Pixels>,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> PaneAxisHandleLayout {
|
||||
let handle_bounds = Bounds {
|
||||
origin: pane_bounds.origin.apply_along(axis, |origin| {
|
||||
@@ -797,7 +797,7 @@ mod element {
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
cx: &mut ui::prelude::ElementContext,
|
||||
cx: &mut ui::prelude::WindowContext,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.flex_grow = 1.;
|
||||
@@ -812,7 +812,7 @@ mod element {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
_state: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) -> PaneAxisLayout {
|
||||
let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
|
||||
Some(self.basis.into()),
|
||||
@@ -900,7 +900,7 @@ mod element {
|
||||
bounds: gpui::Bounds<ui::prelude::Pixels>,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
layout: &mut Self::PrepaintState,
|
||||
cx: &mut ui::prelude::ElementContext,
|
||||
cx: &mut ui::prelude::WindowContext,
|
||||
) {
|
||||
for child in &mut layout.children {
|
||||
child.element.paint(cx);
|
||||
|
||||
83
crates/workspace/src/tasks.rs
Normal file
83
crates/workspace/src/tasks.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use project::TaskSourceKind;
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate};
|
||||
use ui::{ViewContext, WindowContext};
|
||||
|
||||
use crate::Workspace;
|
||||
|
||||
pub fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result<Option<PathBuf>> {
|
||||
let project = workspace.project().read(cx);
|
||||
let available_worktrees = project
|
||||
.worktrees()
|
||||
.filter(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
worktree.is_visible()
|
||||
&& worktree.is_local()
|
||||
&& worktree.root_entry().map_or(false, |e| e.is_dir())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let cwd = match available_worktrees.len() {
|
||||
0 => None,
|
||||
1 => Some(available_worktrees[0].read(cx).abs_path()),
|
||||
_ => {
|
||||
let cwd_for_active_entry = project.active_entry().and_then(|entry_id| {
|
||||
available_worktrees.into_iter().find_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
if worktree.contains_entry(entry_id) {
|
||||
Some(worktree.abs_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
anyhow::ensure!(
|
||||
cwd_for_active_entry.is_some(),
|
||||
"Cannot determine task cwd for multiple worktrees"
|
||||
);
|
||||
cwd_for_active_entry
|
||||
}
|
||||
};
|
||||
Ok(cwd.map(|path| path.to_path_buf()))
|
||||
}
|
||||
|
||||
pub fn schedule_task(
|
||||
workspace: &Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
task_to_resolve: &TaskTemplate,
|
||||
task_cx: &TaskContext,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
if let Some(spawn_in_terminal) =
|
||||
task_to_resolve.resolve_task(&task_source_kind.to_id_base(), task_cx)
|
||||
{
|
||||
schedule_resolved_task(
|
||||
workspace,
|
||||
task_source_kind,
|
||||
spawn_in_terminal,
|
||||
omit_history,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule_resolved_task(
|
||||
workspace: &Workspace,
|
||||
task_source_kind: TaskSourceKind,
|
||||
mut resolved_task: ResolvedTask,
|
||||
omit_history: bool,
|
||||
cx: &mut ViewContext<'_, Workspace>,
|
||||
) {
|
||||
if let Some(spawn_in_terminal) = resolved_task.resolved.take() {
|
||||
if !omit_history {
|
||||
resolved_task.resolved = Some(spawn_in_terminal.clone());
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.task_scheduled(task_source_kind, resolved_task);
|
||||
})
|
||||
});
|
||||
}
|
||||
cx.emit(crate::Event::SpawnTask(spawn_in_terminal));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod persistence;
|
||||
pub mod searchable;
|
||||
pub mod shared_screen;
|
||||
mod status_bar;
|
||||
pub mod tasks;
|
||||
mod toolbar;
|
||||
mod workspace_settings;
|
||||
|
||||
@@ -78,9 +79,9 @@ use theme::{ActiveTheme, SystemAppearance, ThemeSettings};
|
||||
pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||
pub use ui;
|
||||
use ui::{
|
||||
div, h_flex, Context as _, Div, Element, ElementContext, FluentBuilder,
|
||||
InteractiveElement as _, IntoElement, Label, ParentElement as _, Pixels, SharedString,
|
||||
Styled as _, ViewContext, VisualContext as _, WindowContext,
|
||||
div, h_flex, Context as _, Div, Element, FluentBuilder, InteractiveElement as _, IntoElement,
|
||||
Label, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext, VisualContext as _,
|
||||
WindowContext,
|
||||
};
|
||||
use util::{maybe, ResultExt};
|
||||
use uuid::Uuid;
|
||||
@@ -4991,7 +4992,7 @@ impl Element for DisconnectedOverlay {
|
||||
type RequestLayoutState = AnyElement;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
fn request_layout(&mut self, cx: &mut WindowContext) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut background = cx.theme().colors().elevated_surface_background;
|
||||
background.fade_out(0.2);
|
||||
let mut overlay = div()
|
||||
@@ -5016,7 +5017,7 @@ impl Element for DisconnectedOverlay {
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
overlay: &mut Self::RequestLayoutState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
cx.insert_hitbox(bounds, true);
|
||||
overlay.prepaint(cx);
|
||||
@@ -5027,7 +5028,7 @@ impl Element for DisconnectedOverlay {
|
||||
_: Bounds<Pixels>,
|
||||
overlay: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::PrepaintState,
|
||||
cx: &mut ElementContext,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
overlay.paint(cx)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition = "2021"
|
||||
name = "zed"
|
||||
version = "0.133.0"
|
||||
version = "0.134.0"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
@@ -96,7 +96,6 @@ workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.build-dependencies]
|
||||
embed-manifest = "1.4.0"
|
||||
winresource = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user