Compare commits
3 Commits
max-ts-ser
...
debug-proj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d41e6aa35 | ||
|
|
5d0bf9904e | ||
|
|
1ca4cfe2f8 |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -339,7 +339,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
bundle-linux-aarch64: # this runs on ubuntu22.04
|
||||
bundle-linux-aarch64:
|
||||
timeout-minutes: 60
|
||||
name: Create arm64 Linux bundle
|
||||
runs-on:
|
||||
@@ -360,8 +360,8 @@ jobs:
|
||||
- name: Set up Clang
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm-15 clang-15 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-15/bin" >> $GITHUB_PATH
|
||||
sudo apt-get install -y llvm-10 clang-10 build-essential cmake pkg-config libasound2-dev libfontconfig-dev libwayland-dev libxkbcommon-x11-dev libssl-dev libsqlite3-dev libzstd-dev libvulkan1 libgit2-dev
|
||||
echo "/usr/lib/llvm-10/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: rui314/setup-mold@0bf4f07ef9048ec62a45f9dbf2f098afa49695f0 # v1
|
||||
with:
|
||||
|
||||
36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -1660,8 +1660,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blade-graphics"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
@@ -1690,8 +1690,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "blade-macros"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
version = "0.2.1"
|
||||
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1701,7 +1701,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "blade-util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/kvark/blade?rev=e142a3a5e678eb6a13e642ad8401b1f3aa38e969#e142a3a5e678eb6a13e642ad8401b1f3aa38e969"
|
||||
source = "git+https://github.com/kvark/blade?rev=fee06c42f658b36dd9ac85444a9ee2a481383695#fee06c42f658b36dd9ac85444a9ee2a481383695"
|
||||
dependencies = [
|
||||
"blade-graphics",
|
||||
"bytemuck",
|
||||
@@ -4886,9 +4886,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.14.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f865cbd94bd355b89611211e49508da98a1fce0ad755c1e8448fb96711b24528"
|
||||
checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"slotmap",
|
||||
@@ -7868,6 +7868,21 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "performance"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"gpui",
|
||||
"log",
|
||||
"schemars",
|
||||
"serde",
|
||||
"settings",
|
||||
"util",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "perplexity"
|
||||
version = "0.1.0"
|
||||
@@ -14197,7 +14212,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.154.0"
|
||||
version = "0.153.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -14260,6 +14275,7 @@ dependencies = [
|
||||
"outline_panel",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"performance",
|
||||
"profiling",
|
||||
"project",
|
||||
"project_panel",
|
||||
@@ -14371,7 +14387,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_erlang"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -14441,7 +14457,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_php"
|
||||
version = "0.2.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -72,6 +72,7 @@ members = [
|
||||
"crates/outline",
|
||||
"crates/outline_panel",
|
||||
"crates/paths",
|
||||
"crates/performance",
|
||||
"crates/picker",
|
||||
"crates/prettier",
|
||||
"crates/project",
|
||||
@@ -166,7 +167,7 @@ members = [
|
||||
# Tooling
|
||||
#
|
||||
|
||||
"tooling/xtask",
|
||||
"tooling/xtask"
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
||||
@@ -245,6 +246,7 @@ open_ai = { path = "crates/open_ai" }
|
||||
outline = { path = "crates/outline" }
|
||||
outline_panel = { path = "crates/outline_panel" }
|
||||
paths = { path = "crates/paths" }
|
||||
performance = { path = "crates/performance" }
|
||||
picker = { path = "crates/picker" }
|
||||
plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
@@ -322,9 +324,9 @@ async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
base64 = "0.22"
|
||||
bitflags = "2.6.0"
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" }
|
||||
blade-graphics = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
blade-macros = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
blade-util = { git = "https://github.com/kvark/blade", rev = "fee06c42f658b36dd9ac85444a9ee2a481383695" }
|
||||
cargo_metadata = "0.18"
|
||||
cargo_toml = "0.20"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -571,18 +573,14 @@ single_range_in_vec_init = "allow"
|
||||
# There are a bunch of rules currently failing in the `style` group, so
|
||||
# allow all of those, for now.
|
||||
style = { level = "allow", priority = -1 }
|
||||
|
||||
# Temporary list of style lints that we've fixed so far.
|
||||
module_inception = { level = "deny" }
|
||||
question_mark = { level = "deny" }
|
||||
redundant_closure = { level = "deny" }
|
||||
# Individual rules that have violations in the codebase:
|
||||
type_complexity = "allow"
|
||||
# We often return trait objects from `new` functions.
|
||||
new_ret_no_self = { level = "allow" }
|
||||
# We have a few `next` functions that differ in lifetimes
|
||||
# compared to Iterator::next. Yet, clippy complains about those.
|
||||
should_implement_trait = { level = "allow" }
|
||||
|
||||
# Individual rules that have violations in the codebase:
|
||||
type_complexity = "allow"
|
||||
|
||||
[workspace.metadata.cargo-machete]
|
||||
ignored = ["bindgen", "cbindgen", "prost_build", "serde"]
|
||||
|
||||
@@ -553,7 +553,6 @@
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -563,8 +563,8 @@
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-cmd-r": "project_panel::RevealInFileManager",
|
||||
"ctrl-shift-enter": "project_panel::OpenWithSystem",
|
||||
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory",
|
||||
"shift-down": "menu::SelectNext",
|
||||
"shift-up": "menu::SelectPrev",
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"shift-y": "vim::YankToEndOfLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
"a": "vim::InsertAfter",
|
||||
@@ -323,14 +323,9 @@
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-k": ["vim::PushOperator", { "Digraph": {} }],
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||
"bindings": {
|
||||
"ctrl-p": "editor::ShowCompletions",
|
||||
"ctrl-n": "editor::ShowCompletions"
|
||||
"ctrl-n": "editor::ShowCompletions",
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -493,7 +488,6 @@
|
||||
"v": "project_panel::OpenPermanent",
|
||||
"p": "project_panel::Open",
|
||||
"x": "project_panel::RevealInFileManager",
|
||||
"s": "project_panel::OpenWithSystem",
|
||||
"shift-g": "menu::SelectLast",
|
||||
"g g": "menu::SelectFirst",
|
||||
"-": "project_panel::SelectParent",
|
||||
|
||||
@@ -279,13 +279,6 @@
|
||||
"relative_line_numbers": false,
|
||||
// If 'search_wrap' is disabled, search result do not wrap around the end of the file.
|
||||
"search_wrap": true,
|
||||
// Search options to enable by default when opening new project and buffer searches.
|
||||
"search": {
|
||||
"whole_word": false,
|
||||
"case_sensitive": false,
|
||||
"include_ignored": false,
|
||||
"regex": false
|
||||
},
|
||||
// When to populate a new search's query based on the text under the cursor.
|
||||
// This setting can take the following three values:
|
||||
//
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
allow-private-module-inception = true
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
},
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore,
|
||||
Assist, CacheStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
|
||||
ContextStoreEvent, CycleMessageRole, DeployHistory, DeployPromptLibrary, InlineAssistId,
|
||||
InlineAssistant, InsertDraggedFiles, InsertIntoEditor, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, PendingSlashCommand,
|
||||
@@ -26,15 +26,14 @@ use collections::{BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||
display_map::{
|
||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CreaseMetadata,
|
||||
CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
|
||||
BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, CustomBlockId, FoldId,
|
||||
RenderBlock, ToDisplayPoint,
|
||||
},
|
||||
scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
|
||||
Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
|
||||
};
|
||||
use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
|
||||
@@ -51,19 +50,17 @@ use language_model::{
|
||||
provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelRegistry, Role,
|
||||
};
|
||||
use language_model::{LanguageModelImage, LanguageModelToolUse};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::lsp_store::ProjectLspAdapterDelegate;
|
||||
use project::{Project, Worktree};
|
||||
use project::{Project, ProjectLspAdapterDelegate, Worktree};
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp,
|
||||
collections::hash_map,
|
||||
fmt::Write,
|
||||
ops::{ControlFlow, Range},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
@@ -332,7 +329,7 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
let model_summary_editor = cx.new_view(Editor::single_line);
|
||||
let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
let context_editor_toolbar = cx.new_view(|_| {
|
||||
ContextEditorToolbarItem::new(
|
||||
workspace,
|
||||
@@ -344,7 +341,7 @@ impl AssistantPanel {
|
||||
let pane = cx.new_view(|cx| {
|
||||
let mut pane = Pane::new(
|
||||
workspace.weak_handle(),
|
||||
workspace.project().clone(),
|
||||
workspace.project().downgrade(),
|
||||
Default::default(),
|
||||
None,
|
||||
NewContext.boxed_clone(),
|
||||
@@ -942,16 +939,9 @@ impl AssistantPanel {
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
let did_create_context = panel
|
||||
.update(cx, |panel, cx| {
|
||||
panel.new_context(cx)?;
|
||||
|
||||
Some(())
|
||||
})
|
||||
.is_some();
|
||||
if did_create_context {
|
||||
ContextEditor::quote_selection(workspace, &Default::default(), cx);
|
||||
}
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.new_context(cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,7 +1097,7 @@ impl AssistantPanel {
|
||||
pane.activate_item(configuration_item_ix, true, true, cx);
|
||||
});
|
||||
} else {
|
||||
let configuration = cx.new_view(ConfigurationView::new);
|
||||
let configuration = cx.new_view(|cx| ConfigurationView::new(cx));
|
||||
self.configuration_subscription = Some(cx.subscribe(
|
||||
&configuration,
|
||||
|this, _, event: &ConfigurationViewEvent, cx| match event {
|
||||
@@ -1998,20 +1988,6 @@ impl ContextEditor {
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_content(
|
||||
Content::ToolUse {
|
||||
range: tool_use.source_range.clone(),
|
||||
tool_use: LanguageModelToolUse {
|
||||
id: tool_use.id.to_string(),
|
||||
name: tool_use.name.clone(),
|
||||
input: tool_use.input.clone(),
|
||||
},
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
Crease::new(
|
||||
start..end,
|
||||
placeholder,
|
||||
@@ -2515,26 +2491,20 @@ impl ContextEditor {
|
||||
.unwrap();
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
creases.push(
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.view().downgrade(),
|
||||
section.icon,
|
||||
section.label.clone(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
)
|
||||
.with_metadata(CreaseMetadata {
|
||||
icon: section.icon,
|
||||
label: section.label,
|
||||
}),
|
||||
);
|
||||
creases.push(Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
render: render_fold_icon_button(
|
||||
cx.view().downgrade(),
|
||||
section.icon,
|
||||
section.label.clone(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
));
|
||||
}
|
||||
|
||||
editor.insert_creases(creases, cx);
|
||||
@@ -3210,93 +3180,87 @@ impl ContextEditor {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut creases = vec![];
|
||||
editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all_adjusted(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
for selection in selections {
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
|
||||
let editor = editor.read(cx);
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let range = editor::ToOffset::to_offset(&selection.start, &buffer)
|
||||
..editor::ToOffset::to_offset(&selection.end, &buffer);
|
||||
let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.code_fence_block_name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text =
|
||||
if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("");
|
||||
let filename = buffer
|
||||
.file_at(selection.start)
|
||||
.map(|file| file.full_path(cx));
|
||||
let text = if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
let start_symbols = buffer
|
||||
.symbols_containing(selection.start, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
let end_symbols = buffer
|
||||
.symbols_containing(selection.end, None)
|
||||
.map(|(_, symbols)| symbols);
|
||||
|
||||
let outline_text = if let Some((start_symbols, end_symbols)) =
|
||||
start_symbols.zip(end_symbols)
|
||||
{
|
||||
Some(
|
||||
start_symbols
|
||||
.into_iter()
|
||||
.zip(end_symbols)
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a.text)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" > "),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
|
||||
let line_comment_prefix = start_language
|
||||
.and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..selection.end.row),
|
||||
);
|
||||
|
||||
let fence = codeblock_fence_for_path(
|
||||
filename.as_deref(),
|
||||
Some(selection.start.row..selection.end.row),
|
||||
);
|
||||
|
||||
if let Some((line_comment_prefix, outline_text)) =
|
||||
line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb =
|
||||
format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
};
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
creases.push((text, crease_title));
|
||||
if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
|
||||
{
|
||||
let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
|
||||
format!("{fence}{breadcrumb}{selected_text}\n```")
|
||||
} else {
|
||||
format!("{fence}{selected_text}\n```")
|
||||
}
|
||||
});
|
||||
if creases.is_empty() {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let crease_title = if let Some(path) = filename {
|
||||
let start_line = selection.start.row + 1;
|
||||
let end_line = selection.end.row + 1;
|
||||
if start_line == end_line {
|
||||
format!("{}, Line {}", path.display(), start_line)
|
||||
} else {
|
||||
format!("{}, Lines {} to {}", path.display(), start_line, end_line)
|
||||
}
|
||||
} else {
|
||||
"Quoted selection".to_string()
|
||||
};
|
||||
|
||||
// Activate the panel
|
||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
@@ -3313,40 +3277,39 @@ impl ContextEditor {
|
||||
context.update(cx, |context, cx| {
|
||||
context.editor.update(cx, |editor, cx| {
|
||||
editor.insert("\n", cx);
|
||||
for (text, crease_title) in creases {
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
|
||||
editor.insert(&text, cx);
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
editor.insert(&text, cx);
|
||||
|
||||
editor.insert("\n", cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
|
||||
let fold_placeholder = quote_selection_fold_placeholder(
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::new(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
editor.insert("\n", cx);
|
||||
|
||||
let fold_placeholder = quote_selection_fold_placeholder(
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::new(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
};
|
||||
@@ -3355,113 +3318,39 @@ impl ContextEditor {
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
|
||||
if self.editor.read(cx).selections.count() == 1 {
|
||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
copied_text,
|
||||
metadata,
|
||||
));
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
|
||||
if self.editor.read(cx).selections.count() == 1 {
|
||||
let (copied_text, metadata) = self.get_clipboard_contents(cx);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let selections = editor.selections.all::<Point>(cx);
|
||||
|
||||
editor.transact(cx, |this, cx| {
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select(selections);
|
||||
});
|
||||
this.insert("", cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
copied_text,
|
||||
metadata,
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
cx.stop_propagation();
|
||||
return;
|
||||
}
|
||||
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn get_clipboard_contents(&mut self, cx: &mut ViewContext<Self>) -> (String, CopyMetadata) {
|
||||
let creases = self.editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.newest::<Point>(cx);
|
||||
let selection_start = editor.selections.newest::<usize>(cx).start;
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
editor.display_map.update(cx, |display_map, cx| {
|
||||
display_map
|
||||
.snapshot(cx)
|
||||
.crease_snapshot
|
||||
.creases_in_range(
|
||||
MultiBufferRow(selection.start.row)..MultiBufferRow(selection.end.row + 1),
|
||||
&snapshot,
|
||||
)
|
||||
.filter_map(|crease| {
|
||||
if let Some(metadata) = &crease.metadata {
|
||||
let start = crease
|
||||
.range
|
||||
.start
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
let end = crease
|
||||
.range
|
||||
.end
|
||||
.to_offset(&snapshot)
|
||||
.saturating_sub(selection_start);
|
||||
|
||||
let range_relative_to_selection = start..end;
|
||||
|
||||
if range_relative_to_selection.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SelectedCreaseMetadata {
|
||||
range_relative_to_selection,
|
||||
crease: metadata.clone(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
|
||||
let editor = self.editor.read(cx);
|
||||
let context = self.context.read(cx);
|
||||
let selection = self.editor.read(cx).selections.newest::<usize>(cx);
|
||||
let mut text = String::new();
|
||||
for message in context.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
if !range.is_empty() {
|
||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||
text.push_str(chunk);
|
||||
if editor.selections.count() == 1 {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let mut copied_text = String::new();
|
||||
let mut spanned_messages = 0;
|
||||
for message in context.messages(cx) {
|
||||
if message.offset_range.start >= selection.range().end {
|
||||
break;
|
||||
} else if message.offset_range.end >= selection.range().start {
|
||||
let range = cmp::max(message.offset_range.start, selection.range().start)
|
||||
..cmp::min(message.offset_range.end, selection.range().end);
|
||||
if !range.is_empty() {
|
||||
spanned_messages += 1;
|
||||
write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
|
||||
for chunk in context.buffer().read(cx).text_for_range(range) {
|
||||
copied_text.push_str(chunk);
|
||||
}
|
||||
copied_text.push('\n');
|
||||
}
|
||||
text.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if spanned_messages > 1 {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(copied_text));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
(text, CopyMetadata { creases })
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||
cx.stop_propagation();
|
||||
|
||||
fn paste(&mut self, _: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
|
||||
let images = if let Some(item) = cx.read_from_clipboard() {
|
||||
item.into_entries()
|
||||
.filter_map(|entry| {
|
||||
@@ -3476,62 +3365,9 @@ impl ContextEditor {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let metadata = if let Some(item) = cx.read_from_clipboard() {
|
||||
item.entries().first().and_then(|entry| {
|
||||
if let ClipboardEntry::String(text) = entry {
|
||||
text.metadata_json::<CopyMetadata>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if images.is_empty() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let paste_position = editor.selections.newest::<usize>(cx).head();
|
||||
editor.paste(action, cx);
|
||||
|
||||
if let Some(metadata) = metadata {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
|
||||
let mut buffer_rows_to_fold = BTreeSet::new();
|
||||
let weak_editor = cx.view().downgrade();
|
||||
editor.insert_creases(
|
||||
metadata.creases.into_iter().map(|metadata| {
|
||||
let start = buffer.anchor_after(
|
||||
paste_position + metadata.range_relative_to_selection.start,
|
||||
);
|
||||
let end = buffer.anchor_before(
|
||||
paste_position + metadata.range_relative_to_selection.end,
|
||||
);
|
||||
|
||||
let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
|
||||
buffer_rows_to_fold.insert(buffer_row);
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
render: render_fold_icon_button(
|
||||
weak_editor.clone(),
|
||||
metadata.crease.icon,
|
||||
metadata.crease.label.clone(),
|
||||
),
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
)
|
||||
.with_metadata(metadata.crease.clone())
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
for buffer_row in buffer_rows_to_fold.into_iter().rev() {
|
||||
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
// If we didn't find any valid image data to paste, propagate to let normal pasting happen.
|
||||
cx.propagate();
|
||||
} else {
|
||||
let mut image_positions = Vec::new();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
@@ -3552,22 +3388,10 @@ impl ContextEditor {
|
||||
|
||||
self.context.update(cx, |context, cx| {
|
||||
for image in images {
|
||||
let Some(render_image) = image.to_image_data(cx).log_err() else {
|
||||
continue;
|
||||
};
|
||||
let image_id = image.id();
|
||||
let image_task = LanguageModelImage::from_image(image, cx).shared();
|
||||
|
||||
context.insert_image(image, cx);
|
||||
for image_position in image_positions.iter() {
|
||||
context.insert_content(
|
||||
Content::Image {
|
||||
anchor: image_position.text_anchor,
|
||||
image_id,
|
||||
image: image_task.clone(),
|
||||
render_image: render_image.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
context.insert_image_anchor(image_id, image_position.text_anchor, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3582,23 +3406,11 @@ impl ContextEditor {
|
||||
let new_blocks = self
|
||||
.context
|
||||
.read(cx)
|
||||
.contents(cx)
|
||||
.filter_map(|content| {
|
||||
if let Content::Image {
|
||||
anchor,
|
||||
render_image,
|
||||
..
|
||||
} = content
|
||||
{
|
||||
Some((anchor, render_image))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter_map(|(anchor, render_image)| {
|
||||
.images(cx)
|
||||
.filter_map(|image| {
|
||||
const MAX_HEIGHT_IN_LINES: u32 = 8;
|
||||
let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
|
||||
let image = render_image.clone();
|
||||
let anchor = buffer.anchor_in_excerpt(excerpt_id, image.anchor).unwrap();
|
||||
let image = image.render_image.clone();
|
||||
anchor.is_valid(&buffer).then(|| BlockProperties {
|
||||
position: anchor,
|
||||
height: MAX_HEIGHT_IN_LINES,
|
||||
@@ -4225,17 +4037,6 @@ fn render_fold_icon_button(
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CopyMetadata {
|
||||
creases: Vec<SelectedCreaseMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct SelectedCreaseMetadata {
|
||||
range_relative_to_selection: Range<usize>,
|
||||
crease: CreaseMetadata,
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for ContextEditor {}
|
||||
impl EventEmitter<SearchEvent> for ContextEditor {}
|
||||
|
||||
@@ -4261,7 +4062,6 @@ impl Render for ContextEditor {
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
.capture_action(cx.listener(ContextEditor::save))
|
||||
.capture_action(cx.listener(ContextEditor::copy))
|
||||
.capture_action(cx.listener(ContextEditor::cut))
|
||||
.capture_action(cx.listener(ContextEditor::paste))
|
||||
.capture_action(cx.listener(ContextEditor::cycle_message_role))
|
||||
.capture_action(cx.listener(ContextEditor::confirm_command))
|
||||
@@ -4422,7 +4222,8 @@ impl Item for ContextEditor {
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::deactivated(editor, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5354,17 +5155,9 @@ fn make_lsp_adapter_delegate(
|
||||
.worktrees(cx)
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
|
||||
let fs = if project.is_local() {
|
||||
Some(project.fs().clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let http_client = project.client().http_client().clone();
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
Ok(
|
||||
ProjectLspAdapterDelegate::new(lsp_store, &worktree, http_client, fs, None, cx)
|
||||
as Arc<dyn LspAdapterDelegate>,
|
||||
)
|
||||
Ok(ProjectLspAdapterDelegate::new(lsp_store, &worktree, cx)
|
||||
as Arc<dyn LspAdapterDelegate>)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -160,12 +160,10 @@ impl AssistantSettingsContent {
|
||||
.filter_map(|model| match model {
|
||||
OpenAiModel::Custom {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
} => Some(open_ai::AvailableModel {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
}),
|
||||
|
||||
@@ -17,27 +17,28 @@ use feature_flags::{FeatureFlag, FeatureFlagAppExt};
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
future::{self, Shared},
|
||||
stream::FuturesUnordered,
|
||||
FutureExt, StreamExt,
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage,
|
||||
SharedString, Subscription, Task,
|
||||
AppContext, AsyncAppContext, Context as _, EventEmitter, Image, Model, ModelContext,
|
||||
RenderImage, SharedString, Subscription, Task,
|
||||
};
|
||||
|
||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
|
||||
LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role,
|
||||
StopReason,
|
||||
LanguageModelRequestTool, MessageContent, Role, StopReason,
|
||||
};
|
||||
use open_ai::Model as OpenAiModel;
|
||||
use paths::contexts_dir;
|
||||
use paths::{context_images_dir, contexts_dir};
|
||||
use project::Project;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
cmp::{self, max, Ordering},
|
||||
collections::hash_map,
|
||||
fmt::Debug,
|
||||
iter, mem,
|
||||
ops::Range,
|
||||
@@ -48,7 +49,7 @@ use std::{
|
||||
};
|
||||
use telemetry_events::AssistantKind;
|
||||
use text::BufferSnapshot;
|
||||
use util::{post_inc, TryFutureExt};
|
||||
use util::{post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
@@ -376,8 +377,23 @@ impl MessageMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MessageImage {
|
||||
image_id: u64,
|
||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
}
|
||||
|
||||
impl PartialEq for MessageImage {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.image_id == other.image_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for MessageImage {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message {
|
||||
pub image_offsets: SmallVec<[(usize, MessageImage); 1]>,
|
||||
pub offset_range: Range<usize>,
|
||||
pub index_range: Range<usize>,
|
||||
pub anchor_range: Range<language::Anchor>,
|
||||
@@ -387,43 +403,60 @@ pub struct Message {
|
||||
pub cache: Option<MessageCacheMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Content {
|
||||
Image {
|
||||
anchor: language::Anchor,
|
||||
image_id: u64,
|
||||
render_image: Arc<RenderImage>,
|
||||
image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
},
|
||||
ToolUse {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use: LanguageModelToolUse,
|
||||
},
|
||||
ToolResult {
|
||||
range: Range<language::Anchor>,
|
||||
tool_use_id: Arc<str>,
|
||||
},
|
||||
impl Message {
|
||||
fn to_request_message(&self, buffer: &Buffer) -> Option<LanguageModelRequestMessage> {
|
||||
let mut content = Vec::new();
|
||||
|
||||
let mut range_start = self.offset_range.start;
|
||||
for (image_offset, message_image) in self.image_offsets.iter() {
|
||||
if *image_offset != range_start {
|
||||
if let Some(text) = Self::collect_text_content(buffer, range_start..*image_offset) {
|
||||
content.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(image) = message_image.image.clone().now_or_never().flatten() {
|
||||
content.push(language_model::MessageContent::Image(image));
|
||||
}
|
||||
|
||||
range_start = *image_offset;
|
||||
}
|
||||
|
||||
if range_start != self.offset_range.end {
|
||||
if let Some(text) =
|
||||
Self::collect_text_content(buffer, range_start..self.offset_range.end)
|
||||
{
|
||||
content.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(LanguageModelRequestMessage {
|
||||
role: self.role,
|
||||
content,
|
||||
cache: self.cache.as_ref().map_or(false, |cache| cache.is_anchor),
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<MessageContent> {
|
||||
let text: String = buffer.text_for_range(range.clone()).collect();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(MessageContent::Text(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
fn range(&self) -> Range<language::Anchor> {
|
||||
match self {
|
||||
Self::Image { anchor, .. } => *anchor..*anchor,
|
||||
Self::ToolUse { range, .. } | Self::ToolResult { range, .. } => range.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp(&self, other: &Self, buffer: &BufferSnapshot) -> Ordering {
|
||||
let self_range = self.range();
|
||||
let other_range = other.range();
|
||||
if self_range.end.cmp(&other_range.start, buffer).is_lt() {
|
||||
Ordering::Less
|
||||
} else if self_range.start.cmp(&other_range.end, buffer).is_gt() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ImageAnchor {
|
||||
pub anchor: language::Anchor,
|
||||
pub image_id: u64,
|
||||
pub render_image: Arc<RenderImage>,
|
||||
pub image: Shared<Task<Option<LanguageModelImage>>>,
|
||||
}
|
||||
|
||||
struct PendingCompletion {
|
||||
@@ -467,7 +500,8 @@ pub struct Context {
|
||||
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||
pending_tool_uses_by_id: HashMap<Arc<str>, PendingToolUse>,
|
||||
message_anchors: Vec<MessageAnchor>,
|
||||
contents: Vec<Content>,
|
||||
images: HashMap<u64, (Arc<RenderImage>, Shared<Task<Option<LanguageModelImage>>>)>,
|
||||
image_anchors: Vec<ImageAnchor>,
|
||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||
summary: Option<ContextSummary>,
|
||||
pending_summary: Task<Option<()>>,
|
||||
@@ -561,7 +595,8 @@ impl Context {
|
||||
pending_ops: Vec::new(),
|
||||
operations: Vec::new(),
|
||||
message_anchors: Default::default(),
|
||||
contents: Default::default(),
|
||||
image_anchors: Default::default(),
|
||||
images: Default::default(),
|
||||
messages_metadata: Default::default(),
|
||||
pending_slash_commands: Vec::new(),
|
||||
finished_slash_commands: HashSet::default(),
|
||||
@@ -624,6 +659,11 @@ impl Context {
|
||||
id: message.id,
|
||||
start: message.offset_range.start,
|
||||
metadata: self.messages_metadata[&message.id].clone(),
|
||||
image_offsets: message
|
||||
.image_offsets
|
||||
.iter()
|
||||
.map(|image_offset| (image_offset.0, image_offset.1.image_id))
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
summary: self
|
||||
@@ -660,7 +700,7 @@ impl Context {
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let id = saved_context.id.clone().unwrap_or_else(ContextId::new);
|
||||
let id = saved_context.id.clone().unwrap_or_else(|| ContextId::new());
|
||||
let mut this = Self::new(
|
||||
id,
|
||||
ReplicaId::default(),
|
||||
@@ -1917,14 +1957,6 @@ impl Context {
|
||||
output_range
|
||||
});
|
||||
|
||||
this.insert_content(
|
||||
Content::ToolResult {
|
||||
range: anchor_range.clone(),
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.emit(ContextEvent::ToolFinished {
|
||||
tool_use_id,
|
||||
output_range: anchor_range,
|
||||
@@ -2006,7 +2038,6 @@ impl Context {
|
||||
let stream_completion = async {
|
||||
let request_start = Instant::now();
|
||||
let mut events = stream.await?;
|
||||
let mut stop_reason = StopReason::EndTurn;
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
if response_latency.is_none() {
|
||||
@@ -2019,7 +2050,7 @@ impl Context {
|
||||
.message_anchors
|
||||
.iter()
|
||||
.position(|message| message.id == assistant_message_id)?;
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let event_to_emit = this.buffer.update(cx, |buffer, cx| {
|
||||
let message_old_end_offset = this.message_anchors[message_ix + 1..]
|
||||
.iter()
|
||||
.find(|message| message.start.is_valid(buffer))
|
||||
@@ -2028,9 +2059,13 @@ impl Context {
|
||||
});
|
||||
|
||||
match event {
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
}
|
||||
LanguageModelCompletionEvent::Stop(reason) => match reason {
|
||||
StopReason::ToolUse => {
|
||||
return Some(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
},
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
buffer.edit(
|
||||
[(
|
||||
@@ -2081,9 +2116,14 @@ impl Context {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
});
|
||||
|
||||
cx.emit(ContextEvent::StreamedCompletion);
|
||||
if let Some(event) = event_to_emit {
|
||||
cx.emit(event);
|
||||
}
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
@@ -2096,14 +2136,13 @@ impl Context {
|
||||
this.update_cache_status_for_completion(cx);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(stop_reason)
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = stream_completion.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let error_message = result
|
||||
.as_ref()
|
||||
.err()
|
||||
.map(|error| error.to_string().trim().to_string());
|
||||
|
||||
@@ -2131,16 +2170,6 @@ impl Context {
|
||||
error_message,
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(stop_reason) = result {
|
||||
match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
cx.emit(ContextEvent::UsePendingTools);
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
StopReason::MaxTokens => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -2157,94 +2186,18 @@ impl Context {
|
||||
|
||||
pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let request_messages = self
|
||||
.messages(cx)
|
||||
.filter(|message| message.status == MessageStatus::Done)
|
||||
.filter_map(|message| message.to_request_message(&buffer))
|
||||
.collect();
|
||||
|
||||
let mut contents = self.contents(cx).peekable();
|
||||
|
||||
fn collect_text_content(buffer: &Buffer, range: Range<usize>) -> Option<String> {
|
||||
let text: String = buffer.text_for_range(range.clone()).collect();
|
||||
if text.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(text)
|
||||
}
|
||||
}
|
||||
|
||||
let mut completion_request = LanguageModelRequest {
|
||||
messages: Vec::new(),
|
||||
LanguageModelRequest {
|
||||
messages: request_messages,
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: 1.0,
|
||||
};
|
||||
for message in self.messages(cx) {
|
||||
if message.status != MessageStatus::Done {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut offset = message.offset_range.start;
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: message.role,
|
||||
content: Vec::new(),
|
||||
cache: message
|
||||
.cache
|
||||
.as_ref()
|
||||
.map_or(false, |cache| cache.is_anchor),
|
||||
};
|
||||
|
||||
while let Some(content) = contents.peek() {
|
||||
if content
|
||||
.range()
|
||||
.end
|
||||
.cmp(&message.anchor_range.end, buffer)
|
||||
.is_lt()
|
||||
{
|
||||
let content = contents.next().unwrap();
|
||||
let range = content.range().to_offset(buffer);
|
||||
request_message.content.extend(
|
||||
collect_text_content(buffer, offset..range.start).map(MessageContent::Text),
|
||||
);
|
||||
|
||||
match content {
|
||||
Content::Image { image, .. } => {
|
||||
if let Some(image) = image.clone().now_or_never().flatten() {
|
||||
request_message
|
||||
.content
|
||||
.push(language_model::MessageContent::Image(image));
|
||||
}
|
||||
}
|
||||
Content::ToolUse { tool_use, .. } => {
|
||||
request_message
|
||||
.content
|
||||
.push(language_model::MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
Content::ToolResult { tool_use_id, .. } => {
|
||||
request_message.content.push(
|
||||
language_model::MessageContent::ToolResult(
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.to_string(),
|
||||
is_error: false,
|
||||
content: collect_text_content(buffer, range.clone())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
offset = range.end;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
request_message.content.extend(
|
||||
collect_text_content(buffer, offset..message.offset_range.end)
|
||||
.map(MessageContent::Text),
|
||||
);
|
||||
|
||||
completion_request.messages.push(request_message);
|
||||
}
|
||||
|
||||
completion_request
|
||||
}
|
||||
|
||||
pub fn cancel_last_assist(&mut self, cx: &mut ModelContext<Self>) -> bool {
|
||||
@@ -2371,31 +2324,53 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_content(&mut self, content: Content, cx: &mut ModelContext<Self>) {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let insertion_ix = match self
|
||||
.contents
|
||||
.binary_search_by(|probe| probe.cmp(&content, buffer))
|
||||
{
|
||||
Ok(ix) => {
|
||||
self.contents.remove(ix);
|
||||
ix
|
||||
}
|
||||
Err(ix) => ix,
|
||||
};
|
||||
self.contents.insert(insertion_ix, content);
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
pub fn insert_image(&mut self, image: Image, cx: &mut ModelContext<Self>) -> Option<()> {
|
||||
if let hash_map::Entry::Vacant(entry) = self.images.entry(image.id()) {
|
||||
entry.insert((
|
||||
image.to_image_data(cx).log_err()?,
|
||||
LanguageModelImage::from_image(image, cx).shared(),
|
||||
));
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn contents<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Content> {
|
||||
pub fn insert_image_anchor(
|
||||
&mut self,
|
||||
image_id: u64,
|
||||
anchor: language::Anchor,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> bool {
|
||||
cx.emit(ContextEvent::MessagesEdited);
|
||||
|
||||
let buffer = self.buffer.read(cx);
|
||||
self.contents
|
||||
.iter()
|
||||
.filter(|content| {
|
||||
let range = content.range();
|
||||
range.start.is_valid(buffer) && range.end.is_valid(buffer)
|
||||
})
|
||||
.cloned()
|
||||
let insertion_ix = match self
|
||||
.image_anchors
|
||||
.binary_search_by(|existing_anchor| anchor.cmp(&existing_anchor.anchor, buffer))
|
||||
{
|
||||
Ok(ix) => ix,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
|
||||
if let Some((render_image, image)) = self.images.get(&image_id) {
|
||||
self.image_anchors.insert(
|
||||
insertion_ix,
|
||||
ImageAnchor {
|
||||
anchor,
|
||||
image_id,
|
||||
image: image.clone(),
|
||||
render_image: render_image.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn images<'a>(&'a self, _cx: &'a AppContext) -> impl 'a + Iterator<Item = ImageAnchor> {
|
||||
self.image_anchors.iter().cloned()
|
||||
}
|
||||
|
||||
pub fn split_message(
|
||||
@@ -2558,14 +2533,22 @@ impl Context {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut request = self.to_completion_request(cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
"Summarize the context into a short title without punctuation.".into(),
|
||||
],
|
||||
cache: false,
|
||||
});
|
||||
let messages = self
|
||||
.messages(cx)
|
||||
.filter_map(|message| message.to_request_message(self.buffer.read(cx)))
|
||||
.chain(Some(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
"Summarize the context into a short title without punctuation.".into(),
|
||||
],
|
||||
cache: false,
|
||||
}));
|
||||
let request = LanguageModelRequest {
|
||||
messages: messages.collect(),
|
||||
tools: Vec::new(),
|
||||
stop: Vec::new(),
|
||||
temperature: 1.0,
|
||||
};
|
||||
|
||||
self.pending_summary = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
@@ -2665,8 +2648,10 @@ impl Context {
|
||||
cx: &'a AppContext,
|
||||
) -> impl 'a + Iterator<Item = Message> {
|
||||
let buffer = self.buffer.read(cx);
|
||||
let messages = message_anchors.enumerate();
|
||||
let images = self.image_anchors.iter();
|
||||
|
||||
Self::messages_from_iters(buffer, &self.messages_metadata, message_anchors.enumerate())
|
||||
Self::messages_from_iters(buffer, &self.messages_metadata, messages, images)
|
||||
}
|
||||
|
||||
pub fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
|
||||
@@ -2677,8 +2662,10 @@ impl Context {
|
||||
buffer: &'a Buffer,
|
||||
metadata: &'a HashMap<MessageId, MessageMetadata>,
|
||||
messages: impl Iterator<Item = (usize, &'a MessageAnchor)> + 'a,
|
||||
images: impl Iterator<Item = &'a ImageAnchor> + 'a,
|
||||
) -> impl 'a + Iterator<Item = Message> {
|
||||
let mut messages = messages.peekable();
|
||||
let mut images = images.peekable();
|
||||
|
||||
iter::from_fn(move || {
|
||||
if let Some((start_ix, message_anchor)) = messages.next() {
|
||||
@@ -2699,6 +2686,22 @@ impl Context {
|
||||
let message_end_anchor = message_end.unwrap_or(language::Anchor::MAX);
|
||||
let message_end = message_end_anchor.to_offset(buffer);
|
||||
|
||||
let mut image_offsets = SmallVec::new();
|
||||
while let Some(image_anchor) = images.peek() {
|
||||
if image_anchor.anchor.cmp(&message_end_anchor, buffer).is_lt() {
|
||||
image_offsets.push((
|
||||
image_anchor.anchor.to_offset(buffer),
|
||||
MessageImage {
|
||||
image_id: image_anchor.image_id,
|
||||
image: image_anchor.image.clone(),
|
||||
},
|
||||
));
|
||||
images.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(Message {
|
||||
index_range: start_ix..end_ix,
|
||||
offset_range: message_start..message_end,
|
||||
@@ -2707,6 +2710,7 @@ impl Context {
|
||||
role: metadata.role,
|
||||
status: metadata.status.clone(),
|
||||
cache: metadata.cache.clone(),
|
||||
image_offsets,
|
||||
});
|
||||
}
|
||||
None
|
||||
@@ -2744,6 +2748,9 @@ impl Context {
|
||||
})?;
|
||||
|
||||
if let Some(summary) = summary {
|
||||
this.read_with(&cx, |this, cx| this.serialize_images(fs.clone(), cx))?
|
||||
.await;
|
||||
|
||||
let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
|
||||
let mut discriminant = 1;
|
||||
let mut new_path;
|
||||
@@ -2783,6 +2790,45 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn serialize_images(&self, fs: Arc<dyn Fs>, cx: &AppContext) -> Task<()> {
|
||||
let mut images_to_save = self
|
||||
.images
|
||||
.iter()
|
||||
.map(|(id, (_, llm_image))| {
|
||||
let fs = fs.clone();
|
||||
let llm_image = llm_image.clone();
|
||||
let id = *id;
|
||||
async move {
|
||||
if let Some(llm_image) = llm_image.await {
|
||||
let path: PathBuf =
|
||||
context_images_dir().join(&format!("{}.png.base64", id));
|
||||
if fs
|
||||
.metadata(path.as_path())
|
||||
.await
|
||||
.log_err()
|
||||
.flatten()
|
||||
.is_none()
|
||||
{
|
||||
fs.atomic_write(path, llm_image.source.to_string())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
cx.background_executor().spawn(async move {
|
||||
if fs
|
||||
.create_dir(context_images_dir().as_ref())
|
||||
.await
|
||||
.log_err()
|
||||
.is_some()
|
||||
{
|
||||
while let Some(_) = images_to_save.next().await {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
|
||||
let timestamp = self.next_timestamp();
|
||||
let summary = self.summary.get_or_insert(ContextSummary::default());
|
||||
@@ -2868,6 +2914,9 @@ pub struct SavedMessage {
|
||||
pub id: MessageId,
|
||||
pub start: usize,
|
||||
pub metadata: MessageMetadata,
|
||||
#[serde(default)]
|
||||
// This is defaulted for backwards compatibility with JSON files created before August 2024. We didn't always have this field.
|
||||
pub image_offsets: Vec<(usize, u64)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -3053,6 +3102,7 @@ impl SavedContextV0_3_0 {
|
||||
timestamp,
|
||||
cache: None,
|
||||
},
|
||||
image_offsets: Vec::new(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -390,7 +390,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
@@ -527,7 +527,7 @@ impl ContextStore {
|
||||
context_proto
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(ContextOperation::from_proto)
|
||||
.map(|op| ContextOperation::from_proto(op))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -1921,7 +1921,7 @@ impl PromptEditor {
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_fallbacks: settings.ui_font.fallbacks.clone(),
|
||||
font_size: settings.ui_font_size.into(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(1.3),
|
||||
..Default::default()
|
||||
@@ -2373,7 +2373,20 @@ impl Codegen {
|
||||
None
|
||||
};
|
||||
|
||||
let language_name = language_name.as_ref();
|
||||
// Higher Temperature increases the randomness of model outputs.
|
||||
// If Markdown or No Language is Known, increase the randomness for more creative output
|
||||
// If Code, decrease temperature to get more deterministic outputs
|
||||
let temperature = if let Some(language) = language_name.clone() {
|
||||
if language.as_ref() == "Markdown" {
|
||||
1.0
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
let language_name = language_name.as_deref();
|
||||
let start = buffer.point_to_buffer_offset(edit_range.start);
|
||||
let end = buffer.point_to_buffer_offset(edit_range.end);
|
||||
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||
@@ -2408,7 +2421,7 @@ impl Codegen {
|
||||
messages,
|
||||
tools: Vec::new(),
|
||||
stop: vec!["|END|>".to_string()],
|
||||
temperature: 1.,
|
||||
temperature,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3060,7 +3073,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range,
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3132,7 +3145,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3207,7 +3220,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -3281,7 +3294,7 @@ mod tests {
|
||||
codegen.handle_stream(
|
||||
String::new(),
|
||||
range.clone(),
|
||||
future::ready(Ok(chunks_rx.map(Ok).boxed())),
|
||||
future::ready(Ok(chunks_rx.map(|chunk| Ok(chunk)).boxed())),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::AssetSource;
|
||||
use handlebars::{Handlebars, RenderError};
|
||||
use language::{BufferSnapshot, LanguageName};
|
||||
use language::BufferSnapshot;
|
||||
use parking_lot::Mutex;
|
||||
use serde::Serialize;
|
||||
use std::{ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
@@ -123,7 +123,7 @@ impl PromptBuilder {
|
||||
if params.fs.is_dir(parent_dir).await {
|
||||
let (mut changes, _watcher) = params.fs.watch(parent_dir, Duration::from_secs(1)).await;
|
||||
while let Some(changed_paths) = changes.next().await {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
let mut log_message = format!("Prompt template overrides directory detected at {}", templates_dir.display());
|
||||
if let Ok(target) = params.fs.read_link(&templates_dir).await {
|
||||
log_message.push_str(" -> ");
|
||||
@@ -162,18 +162,18 @@ impl PromptBuilder {
|
||||
let mut combined_changes = futures::stream::select(changes, parent_changes);
|
||||
|
||||
while let Some(changed_paths) = combined_changes.next().await {
|
||||
if changed_paths.iter().any(|p| &p.path == &templates_dir) {
|
||||
if changed_paths.iter().any(|p| p == &templates_dir) {
|
||||
if !params.fs.is_dir(&templates_dir).await {
|
||||
log::info!("Prompt template overrides directory removed. Restoring built-in prompt templates.");
|
||||
Self::register_built_in_templates(&mut handlebars.lock()).log_err();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for event in changed_paths {
|
||||
if event.path.starts_with(&templates_dir) && event.path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", event.path.display());
|
||||
if let Some(content) = params.fs.load(&event.path).await.log_err() {
|
||||
let file_name = event.path.file_stem().unwrap().to_string_lossy();
|
||||
for changed_path in changed_paths {
|
||||
if changed_path.starts_with(&templates_dir) && changed_path.extension().map_or(false, |ext| ext == "hbs") {
|
||||
log::info!("Reloading prompt template override: {}", changed_path.display());
|
||||
if let Some(content) = params.fs.load(&changed_path).await.log_err() {
|
||||
let file_name = changed_path.file_stem().unwrap().to_string_lossy();
|
||||
handlebars.lock().register_template_string(&file_name, content).log_err();
|
||||
}
|
||||
}
|
||||
@@ -204,11 +204,11 @@ impl PromptBuilder {
|
||||
pub fn generate_content_prompt(
|
||||
&self,
|
||||
user_prompt: String,
|
||||
language_name: Option<&LanguageName>,
|
||||
language_name: Option<&str>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
) -> Result<String, RenderError> {
|
||||
let content_type = match language_name.as_ref().map(|l| l.0.as_ref()) {
|
||||
let content_type = match language_name {
|
||||
None | Some("Markdown" | "Plain Text") => "text",
|
||||
Some(_) => "code",
|
||||
};
|
||||
|
||||
@@ -44,9 +44,10 @@ impl SlashCommand for ContextServerSlashCommand {
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
self.prompt.arguments.as_ref().map_or(false, |args| {
|
||||
args.iter().any(|arg| arg.required == Some(true))
|
||||
})
|
||||
self.prompt
|
||||
.arguments
|
||||
.as_ref()
|
||||
.map_or(false, |args| !args.is_empty())
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
@@ -178,8 +179,6 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
let mut map = HashMap::default();
|
||||
map.insert(args[0].name.clone(), arguments.join(" "));
|
||||
Ok(map)
|
||||
} else if arguments.is_empty() && args[0].required == Some(false) {
|
||||
Ok(HashMap::default())
|
||||
} else {
|
||||
Err(anyhow!("Prompt expects argument but none given"))
|
||||
}
|
||||
@@ -200,7 +199,7 @@ fn prompt_arguments(prompt: &PromptInfo, arguments: &[String]) -> Result<HashMap
|
||||
pub fn acceptable_prompt(prompt: &PromptInfo) -> bool {
|
||||
match &prompt.arguments {
|
||||
None => true,
|
||||
Some(args) if args.len() <= 1 => true,
|
||||
Some(args) if args.len() == 1 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,11 @@ impl SlashCommand for FileSlashCommand {
|
||||
Some(ArgumentCompletion {
|
||||
label,
|
||||
new_text: text,
|
||||
after_completion: AfterCompletion::Compose,
|
||||
after_completion: if path_match.is_dir {
|
||||
AfterCompletion::Compose
|
||||
} else {
|
||||
AfterCompletion::Run
|
||||
},
|
||||
replace_previous_arguments: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -253,7 +253,7 @@ fn tab_items_for_queries(
|
||||
.fold(HashMap::default(), |mut candidates, (id, path_string)| {
|
||||
candidates
|
||||
.entry(path_string)
|
||||
.or_insert_with(Vec::new)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(id);
|
||||
candidates
|
||||
});
|
||||
|
||||
@@ -465,8 +465,7 @@ impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let buttons = match status {
|
||||
let buttons = match &self.codegen.read(cx).status {
|
||||
CodegenStatus::Idle => {
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
@@ -517,8 +516,7 @@ impl Render for PromptEditor {
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)));
|
||||
|
||||
let has_error = matches!(status, CodegenStatus::Error(_));
|
||||
if has_error || self.edited_since_done {
|
||||
if self.edited_since_done {
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
@@ -990,7 +988,7 @@ impl TerminalTransaction {
|
||||
|
||||
pub fn push(&mut self, hunk: String, cx: &mut AppContext) {
|
||||
// Ensure that the assistant cannot accidentally execute commands that are streamed into the terminal
|
||||
let input = Self::sanitize_input(hunk);
|
||||
let input = hunk.replace(CARRIAGE_RETURN, " ");
|
||||
self.terminal
|
||||
.update(cx, |terminal, _| terminal.input(input));
|
||||
}
|
||||
@@ -1005,10 +1003,6 @@ impl TerminalTransaction {
|
||||
terminal.input(CARRIAGE_RETURN.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
fn sanitize_input(input: String) -> String {
|
||||
input.replace(['\r', '\n'], "")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Codegen {
|
||||
|
||||
@@ -116,30 +116,27 @@ impl Drop for MacOsUnmounter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether or not to automatically check for updates.
|
||||
#[derive(Clone, Copy, JsonSchema, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(transparent)]
|
||||
struct AutoUpdateSetting(bool);
|
||||
|
||||
impl Default for AutoUpdateSetting {
|
||||
fn default() -> Self {
|
||||
Self(true)
|
||||
}
|
||||
}
|
||||
/// Whether or not to automatically check for updates.
|
||||
///
|
||||
/// Default: true
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
struct AutoUpdateSettingContent(bool);
|
||||
|
||||
impl Settings for AutoUpdateSetting {
|
||||
const KEY: Option<&'static str> = Some("auto_update");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = Option<AutoUpdateSettingContent>;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
let auto_update = [sources.release_channel, sources.user]
|
||||
.into_iter()
|
||||
.find_map(|value| value.copied())
|
||||
.unwrap_or(*sources.default);
|
||||
.find_map(|value| value.copied().flatten())
|
||||
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
|
||||
|
||||
Ok(auto_update)
|
||||
Ok(Self(auto_update.0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -529,13 +529,14 @@ mod test {
|
||||
let (a, b) = cx.update(|cx| {
|
||||
(
|
||||
one_at_a_time.spawn(cx, |_| async {
|
||||
panic!("");
|
||||
assert!(false);
|
||||
Ok(2)
|
||||
}),
|
||||
one_at_a_time.spawn(cx, |_| async { Ok(3) }),
|
||||
)
|
||||
});
|
||||
|
||||
assert_eq!(a.await.unwrap(), None::<u32>);
|
||||
assert_eq!(a.await.unwrap(), None);
|
||||
assert_eq!(b.await.unwrap(), Some(3));
|
||||
|
||||
let promise = cx.update(|cx| one_at_a_time.spawn(cx, |_| async { Ok(4) }));
|
||||
|
||||
@@ -4,20 +4,30 @@ use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
/// Whether the microphone should be muted when joining a channel or a call.
|
||||
pub mute_on_join: bool,
|
||||
/// Whether your current project should be shared when joining an empty channel.
|
||||
pub share_on_join: bool,
|
||||
}
|
||||
|
||||
/// Configuration of voice calls in Zed.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct CallSettingsContent {
|
||||
/// Whether the microphone should be muted when joining a channel or a call.
|
||||
///
|
||||
/// Default: false
|
||||
pub mute_on_join: Option<bool>,
|
||||
|
||||
/// Whether your current project should be shared when joining an empty channel.
|
||||
///
|
||||
/// Default: true
|
||||
pub share_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for CallSettings {
|
||||
const KEY: Option<&'static str> = Some("calls");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = CallSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
|
||||
@@ -99,26 +99,20 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
|
||||
actions!(client, [SignIn, SignOut, Reconnect]);
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
pub struct ClientSettings {
|
||||
/// The server to connect to. If the environment variable
|
||||
/// ZED_SERVER_URL is set, it will override this setting.
|
||||
pub server_url: String,
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ClientSettingsContent {
|
||||
server_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ClientSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_url: "https://zed.dev".to_owned(),
|
||||
}
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
pub struct ClientSettings {
|
||||
pub server_url: String,
|
||||
}
|
||||
|
||||
impl Settings for ClientSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = ClientSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
let mut result = sources.json_merge::<Self>()?;
|
||||
@@ -130,37 +124,19 @@ impl Settings for ClientSettings {
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
pub struct ProxySettings {
|
||||
/// Set a proxy to use. The proxy protocol is specified by the URI scheme.
|
||||
///
|
||||
/// Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`,
|
||||
/// `socks5h`. `http` will be used when no scheme is specified.
|
||||
///
|
||||
/// By default no proxy will be used, or Zed will try get proxy settings from
|
||||
/// environment variables.
|
||||
///
|
||||
/// Examples:
|
||||
/// - "proxy": "socks5://localhost:10808"
|
||||
/// - "proxy": "http://127.0.0.1:10809"
|
||||
#[schemars(example = "Self::example_1")]
|
||||
#[schemars(example = "Self::example_2")]
|
||||
pub proxy: Option<String>,
|
||||
pub struct ProxySettingsContent {
|
||||
proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl ProxySettings {
|
||||
fn example_1() -> String {
|
||||
"http://127.0.0.1:10809".to_owned()
|
||||
}
|
||||
fn example_2() -> String {
|
||||
"socks5://localhost:10808".to_owned()
|
||||
}
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct ProxySettings {
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl Settings for ProxySettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = ProxySettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
Ok(Self {
|
||||
|
||||
@@ -30,7 +30,7 @@ struct CheckIsContributorParams {
|
||||
}
|
||||
|
||||
impl CheckIsContributorParams {
|
||||
fn into_contributor_selector(self) -> Result<ContributorSelector> {
|
||||
fn as_contributor_selector(self) -> Result<ContributorSelector> {
|
||||
if let Some(github_user_id) = self.github_user_id {
|
||||
return Ok(ContributorSelector::GitHubUserId { github_user_id });
|
||||
}
|
||||
@@ -54,7 +54,7 @@ async fn check_is_contributor(
|
||||
Extension(app): Extension<Arc<AppState>>,
|
||||
Query(params): Query<CheckIsContributorParams>,
|
||||
) -> Result<Json<CheckIsContributorResponse>> {
|
||||
let params = params.into_contributor_selector()?;
|
||||
let params = params.as_contributor_selector()?;
|
||||
|
||||
if RenovateBot::is_renovate_bot(¶ms) {
|
||||
return Ok(Json(CheckIsContributorResponse {
|
||||
|
||||
@@ -1326,7 +1326,9 @@ impl ActionEventRow {
|
||||
}
|
||||
|
||||
pub fn calculate_json_checksum(app: Arc<AppState>, json: &impl AsRef<[u8]>) -> Option<Vec<u8>> {
|
||||
let checksum_seed = app.config.zed_client_checksum_seed.as_ref()?;
|
||||
let Some(checksum_seed) = app.config.zed_client_checksum_seed.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut summer = Sha256::new();
|
||||
summer.update(checksum_seed);
|
||||
|
||||
@@ -52,7 +52,7 @@ async fn get_extensions(
|
||||
let extension_id = filter.to_lowercase();
|
||||
let mut exact_match = None;
|
||||
extensions.retain(|extension| {
|
||||
if extension.id.as_ref() == extension_id {
|
||||
if extension.id.as_ref() == &extension_id {
|
||||
exact_match = Some(extension.clone());
|
||||
false
|
||||
} else {
|
||||
|
||||
@@ -872,7 +872,7 @@ fn operation_from_storage(
|
||||
})
|
||||
}
|
||||
|
||||
fn version_to_storage(version: &[proto::VectorClockEntry]) -> Vec<storage::VectorClockEntry> {
|
||||
fn version_to_storage(version: &Vec<proto::VectorClockEntry>) -> Vec<storage::VectorClockEntry> {
|
||||
version
|
||||
.iter()
|
||||
.map(|entry| storage::VectorClockEntry {
|
||||
@@ -882,7 +882,7 @@ fn version_to_storage(version: &[proto::VectorClockEntry]) -> Vec<storage::Vecto
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn version_from_storage(version: &[storage::VectorClockEntry]) -> Vec<proto::VectorClockEntry> {
|
||||
fn version_from_storage(version: &Vec<storage::VectorClockEntry>) -> Vec<proto::VectorClockEntry> {
|
||||
version
|
||||
.iter()
|
||||
.map(|entry| proto::VectorClockEntry {
|
||||
|
||||
@@ -146,11 +146,11 @@ impl Database {
|
||||
pub async fn update_dev_server_project(
|
||||
&self,
|
||||
id: DevServerProjectId,
|
||||
paths: &[String],
|
||||
paths: &Vec<String>,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let paths = paths.to_owned();
|
||||
let paths = paths.clone();
|
||||
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
|
||||
.find_also_related(dev_server::Entity)
|
||||
.one(&*tx)
|
||||
|
||||
@@ -5,7 +5,7 @@ use super::*;
|
||||
impl Database {
|
||||
pub async fn get_hosted_projects(
|
||||
&self,
|
||||
channel_ids: &[ChannelId],
|
||||
channel_ids: &Vec<ChannelId>,
|
||||
roles: &HashMap<ChannelId, ChannelRole>,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<proto::HostedProject>> {
|
||||
|
||||
@@ -88,7 +88,7 @@ async fn main() -> Result<()> {
|
||||
.route("/healthz", get(handle_liveness_probe))
|
||||
.layer(Extension(mode));
|
||||
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", config.http_port))
|
||||
let listener = TcpListener::bind(&format!("0.0.0.0:{}", config.http_port))
|
||||
.expect("failed to bind TCP listener");
|
||||
|
||||
let mut on_shutdown = None;
|
||||
|
||||
@@ -2261,11 +2261,11 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
// Turn inline-blame-off by default so no state is transferred without us explicitly doing so
|
||||
let inline_blame_off_settings = InlineBlameSettings {
|
||||
let inline_blame_off_settings = Some(InlineBlameSettings {
|
||||
enabled: false,
|
||||
delay_ms: 0,
|
||||
min_column: 0,
|
||||
};
|
||||
delay_ms: None,
|
||||
min_column: None,
|
||||
});
|
||||
cx_a.update(|cx| {
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
store.update_user_settings::<ProjectSettings>(cx, |settings| {
|
||||
|
||||
@@ -1649,7 +1649,7 @@ async fn test_following_into_excluded_file(
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |settings| {
|
||||
settings.file_scan_exclusions = vec!["**/.git".to_string()];
|
||||
settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2328,11 +2328,11 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
.unwrap();
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.language().unwrap().name(), "Rust".into());
|
||||
assert_eq!(&*buffer.language().unwrap().name(), "Rust");
|
||||
});
|
||||
|
||||
buffer_c.read_with(cx_c, |buffer, _| {
|
||||
assert_eq!(buffer.language().unwrap().name(), "Rust".into());
|
||||
assert_eq!(&*buffer.language().unwrap().name(), "Rust");
|
||||
});
|
||||
buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
|
||||
buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
|
||||
@@ -2432,17 +2432,17 @@ async fn test_propagate_saves_and_fs_changes(
|
||||
|
||||
buffer_a.read_with(cx_a, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
|
||||
});
|
||||
|
||||
buffer_b.read_with(cx_b, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
|
||||
});
|
||||
|
||||
buffer_c.read_with(cx_c, |buffer, _| {
|
||||
assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
|
||||
assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
|
||||
assert_eq!(&*buffer.language().unwrap().name(), "JavaScript");
|
||||
});
|
||||
|
||||
let new_buffer_a = project_a
|
||||
@@ -3324,7 +3324,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
|
||||
@@ -3343,7 +3343,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("").into(), r#"{}"#.to_string()),
|
||||
@@ -3372,7 +3372,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[
|
||||
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||
@@ -3404,7 +3404,7 @@ async fn test_local_settings(
|
||||
let store = cx.global::<SettingsStore>();
|
||||
assert_eq!(
|
||||
store
|
||||
.local_settings(worktree_b.read(cx).id())
|
||||
.local_settings(worktree_b.entity_id().as_u64() as _)
|
||||
.collect::<Vec<_>>(),
|
||||
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
let file = buffer_b.read(cx).file();
|
||||
assert_eq!(
|
||||
all_language_settings(file, cx)
|
||||
.language(Some(&("Rust".into())))
|
||||
.language(Some("Rust"))
|
||||
.language_servers,
|
||||
["override-rust-analyzer".into()]
|
||||
)
|
||||
|
||||
@@ -1108,7 +1108,7 @@ impl Panel for ChatPanel {
|
||||
settings::update_settings_file::<ChatPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.dock = position,
|
||||
move |settings, _| settings.dock = Some(position),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ impl MessageEditor {
|
||||
editor.set_show_indent_guides(false, cx);
|
||||
editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
|
||||
editor.set_auto_replace_emoji_shortcode(
|
||||
MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
|
||||
MessageEditorSettings::get_global(cx)
|
||||
.auto_replace_emoji_shortcode
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -128,7 +130,9 @@ impl MessageEditor {
|
||||
cx.observe_global::<settings::SettingsStore>(|view, cx| {
|
||||
view.editor.update(cx, |editor, cx| {
|
||||
editor.set_auto_replace_emoji_shortcode(
|
||||
MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
|
||||
MessageEditorSettings::get_global(cx)
|
||||
.auto_replace_emoji_shortcode
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -342,7 +346,7 @@ impl MessageEditor {
|
||||
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let Some(query) = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == '@' {
|
||||
@@ -354,7 +358,9 @@ impl MessageEditor {
|
||||
query.push(ch);
|
||||
}
|
||||
None
|
||||
})?;
|
||||
}) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let start_offset = end_offset - query.len();
|
||||
let start_anchor = buffer.read(cx).anchor_before(start_offset);
|
||||
@@ -408,7 +414,7 @@ impl MessageEditor {
|
||||
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
let query = buffer.update(cx, |buffer, _| {
|
||||
let Some(query) = buffer.update(cx, |buffer, _| {
|
||||
let mut query = String::new();
|
||||
for ch in buffer.reversed_chars_at(end_offset).take(100) {
|
||||
if ch == ':' {
|
||||
@@ -444,7 +450,9 @@ impl MessageEditor {
|
||||
query.push(ch);
|
||||
}
|
||||
None
|
||||
})?;
|
||||
}) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let start_offset = end_offset - query.len() - 1;
|
||||
let start_anchor = buffer.read(cx).anchor_before(start_offset);
|
||||
|
||||
@@ -2813,7 +2813,7 @@ impl Panel for CollabPanel {
|
||||
settings::update_settings_file::<CollaborationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.dock = position,
|
||||
move |settings, _| settings.dock = Some(position),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -672,7 +672,7 @@ impl Panel for NotificationPanel {
|
||||
settings::update_settings_file::<NotificationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.dock = position,
|
||||
move |settings, _| settings.dock = Some(position),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,84 +2,58 @@ use gpui::Pixels;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use ui::px;
|
||||
use workspace::dock::DockPosition;
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CollaborationPanelSettings {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
pub button: bool,
|
||||
/// Where to dock the panel.
|
||||
pub dock: DockPosition,
|
||||
/// Default width of the panel in pixels.
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
impl Default for CollaborationPanelSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
button: true,
|
||||
dock: DockPosition::Left,
|
||||
default_width: px(240.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ChatPanelSettings {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
pub button: bool,
|
||||
/// Where to dock the panel.
|
||||
pub dock: DockPosition,
|
||||
/// Default width of the panel in pixels.
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
impl Default for ChatPanelSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
button: true,
|
||||
dock: DockPosition::Right,
|
||||
default_width: px(240.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NotificationPanelSettings {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
pub button: bool,
|
||||
/// Where to dock the panel.
|
||||
pub dock: DockPosition,
|
||||
/// Default width of the panel in pixels.
|
||||
pub default_width: Pixels,
|
||||
}
|
||||
|
||||
impl Default for NotificationPanelSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
button: true,
|
||||
dock: DockPosition::Right,
|
||||
default_width: px(380.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(default)]
|
||||
pub struct PanelSettingsContent {
|
||||
/// Whether to show the panel button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Where to dock the panel.
|
||||
///
|
||||
/// Default: left
|
||||
pub dock: Option<DockPosition>,
|
||||
/// Default width of the panel in pixels.
|
||||
///
|
||||
/// Default: 240
|
||||
pub default_width: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct MessageEditorSettings {
|
||||
/// Whether to automatically replace emoji shortcodes with emoji characters.
|
||||
/// For example: typing `:wave:` gets replaced with `👋`.
|
||||
pub auto_replace_emoji_shortcode: bool,
|
||||
///
|
||||
/// Default: false
|
||||
pub auto_replace_emoji_shortcode: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for CollaborationPanelSettings {
|
||||
const KEY: Option<&'static str> = Some("collaboration_panel");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = PanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
@@ -92,7 +66,7 @@ impl Settings for CollaborationPanelSettings {
|
||||
impl Settings for ChatPanelSettings {
|
||||
const KEY: Option<&'static str> = Some("chat_panel");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = PanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
@@ -105,7 +79,7 @@ impl Settings for ChatPanelSettings {
|
||||
impl Settings for NotificationPanelSettings {
|
||||
const KEY: Option<&'static str> = Some("notification_panel");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = PanelSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
@@ -118,7 +92,7 @@ impl Settings for NotificationPanelSettings {
|
||||
impl Settings for MessageEditorSettings {
|
||||
const KEY: Option<&'static str> = Some("message_editor");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = MessageEditorSettings;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
|
||||
@@ -1250,8 +1250,8 @@ mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn worktree_id(&self, _: &AppContext) -> settings::WorktreeId {
|
||||
settings::WorktreeId::from_usize(0)
|
||||
fn worktree_id(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn is_private(&self) -> bool {
|
||||
|
||||
@@ -4,25 +4,23 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
#[serde(default)]
|
||||
/// Diagnostics configuration.
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ProjectDiagnosticsSettings {
|
||||
/// Whether to show warnings or not by default.
|
||||
pub include_warnings: bool,
|
||||
}
|
||||
|
||||
impl Default for ProjectDiagnosticsSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
include_warnings: true,
|
||||
}
|
||||
}
|
||||
/// Diagnostics configuration.
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct ProjectDiagnosticsSettingsContent {
|
||||
/// Whether to show warnings or not by default.
|
||||
///
|
||||
/// Default: true
|
||||
include_warnings: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for ProjectDiagnosticsSettings {
|
||||
const KEY: Option<&'static str> = Some("diagnostics");
|
||||
type FileContent = Self;
|
||||
type FileContent = ProjectDiagnosticsSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
|
||||
@@ -9,7 +9,7 @@ use settings::Settings;
|
||||
use std::hash::Hash;
|
||||
use theme::ThemeSettings;
|
||||
use time::UtcOffset;
|
||||
use ui::{prelude::*, tooltip_container, Avatar, Divider, IconButtonShape};
|
||||
use ui::{prelude::*, tooltip_container, Avatar};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::git::blame::{CommitDetails, GitRemote};
|
||||
@@ -160,7 +160,6 @@ impl Render for BlameEntryTooltip {
|
||||
.gap_4()
|
||||
.child(
|
||||
h_flex()
|
||||
.pb_1p5()
|
||||
.gap_x_2()
|
||||
.overflow_x_hidden()
|
||||
.flex_wrap()
|
||||
@@ -174,7 +173,7 @@ impl Render for BlameEntryTooltip {
|
||||
)
|
||||
})
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant),
|
||||
.border_color(cx.theme().colors().border),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -190,13 +189,10 @@ impl Render for BlameEntryTooltip {
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.pt_1p5()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(absolute_timestamp)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.gap_2()
|
||||
.when_some(pull_request, |this, pr| {
|
||||
this.child(
|
||||
Button::new(
|
||||
@@ -207,20 +203,19 @@ impl Render for BlameEntryTooltip {
|
||||
.icon(IconName::PullRequest)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.on_click(move |_, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.open_url(pr.url.as_str())
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(Divider::vertical())
|
||||
.child(
|
||||
Button::new(
|
||||
"commit-sha-button",
|
||||
short_commit_id.clone(),
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.color(Color::Muted)
|
||||
.icon(IconName::FileGit)
|
||||
.icon_color(Color::Muted)
|
||||
@@ -244,8 +239,6 @@ impl Render for BlameEntryTooltip {
|
||||
)
|
||||
.child(
|
||||
IconButton::new("copy-sha-button", IconName::Copy)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, cx| {
|
||||
cx.stop_propagation();
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{element::register_action, Editor, SwitchSourceHeader};
|
||||
static CLANGD_SERVER_NAME: &str = "clangd";
|
||||
|
||||
fn is_c_language(language: &Language) -> bool {
|
||||
return language.name() == "C++".into() || language.name() == "C".into();
|
||||
return language.name().as_ref() == "C++" || language.name().as_ref() == "C";
|
||||
}
|
||||
|
||||
pub fn switch_source_header(
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyElement, IntoElement};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToPoint};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp::Ordering, ops::Range, sync::Arc};
|
||||
use sum_tree::{Bias, SeekTarget, SumTree};
|
||||
use text::Point;
|
||||
use ui::{IconName, SharedString, WindowContext};
|
||||
use ui::WindowContext;
|
||||
|
||||
use crate::FoldPlaceholder;
|
||||
|
||||
@@ -50,31 +49,6 @@ impl CreaseSnapshot {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn creases_in_range<'a>(
|
||||
&'a self,
|
||||
range: Range<MultiBufferRow>,
|
||||
snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl '_ + Iterator<Item = &'a Crease> {
|
||||
let start = snapshot.anchor_before(Point::new(range.start.0, 0));
|
||||
let mut cursor = self.creases.cursor::<ItemSummary>();
|
||||
cursor.seek(&start, Bias::Left, snapshot);
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
while let Some(item) = cursor.item() {
|
||||
cursor.next(snapshot);
|
||||
let crease_start = item.crease.range.start.to_point(snapshot);
|
||||
let crease_end = item.crease.range.end.to_point(snapshot);
|
||||
if crease_end.row > range.end.0 {
|
||||
continue;
|
||||
}
|
||||
if crease_start.row >= range.start.0 && crease_end.row < range.end.0 {
|
||||
return Some(&item.crease);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn crease_items_with_offsets(
|
||||
&self,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
@@ -113,14 +87,6 @@ pub struct Crease {
|
||||
pub placeholder: FoldPlaceholder,
|
||||
pub render_toggle: RenderToggleFn,
|
||||
pub render_trailer: RenderTrailerFn,
|
||||
pub metadata: Option<CreaseMetadata>,
|
||||
}
|
||||
|
||||
/// Metadata about a [`Crease`], that is used for serialization.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CreaseMetadata {
|
||||
pub icon: IconName,
|
||||
pub label: SharedString,
|
||||
}
|
||||
|
||||
impl Crease {
|
||||
@@ -158,14 +124,8 @@ impl Crease {
|
||||
render_trailer: Arc::new(move |row, folded, cx| {
|
||||
render_trailer(row, folded, cx).into_any_element()
|
||||
}),
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_metadata(mut self, metadata: CreaseMetadata) -> Self {
|
||||
self.metadata = Some(metadata);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Crease {
|
||||
@@ -344,54 +304,4 @@ mod test {
|
||||
.query_row(MultiBufferRow(3), &snapshot)
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_creases_in_range(cx: &mut AppContext) {
|
||||
let text = "line1\nline2\nline3\nline4\nline5\nline6\nline7";
|
||||
let buffer = MultiBuffer::build_simple(text, cx);
|
||||
let snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
let mut crease_map = CreaseMap::default();
|
||||
|
||||
let creases = [
|
||||
Crease::new(
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(1, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Crease::new(
|
||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_after(Point::new(3, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
Crease::new(
|
||||
snapshot.anchor_before(Point::new(5, 0))..snapshot.anchor_after(Point::new(5, 5)),
|
||||
FoldPlaceholder::test(),
|
||||
|_row, _folded, _toggle, _cx| div(),
|
||||
|_row, _folded, _cx| div(),
|
||||
),
|
||||
];
|
||||
crease_map.insert(creases, &snapshot);
|
||||
|
||||
let crease_snapshot = crease_map.snapshot();
|
||||
|
||||
let range = MultiBufferRow(0)..MultiBufferRow(7);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 3);
|
||||
|
||||
let range = MultiBufferRow(2)..MultiBufferRow(5);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 1);
|
||||
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 3);
|
||||
|
||||
let range = MultiBufferRow(0)..MultiBufferRow(2);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 1);
|
||||
assert_eq!(creases[0].range.start.to_point(&snapshot).row, 1);
|
||||
|
||||
let range = MultiBufferRow(6)..MultiBufferRow(7);
|
||||
let creases: Vec<_> = crease_snapshot.creases_in_range(range, &snapshot).collect();
|
||||
assert_eq!(creases.len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,7 @@ use convert_case::{Case, Casing};
|
||||
use debounced_delay::DebouncedDelay;
|
||||
use display_map::*;
|
||||
pub use display_map::{DisplayPoint, FoldPlaceholder};
|
||||
pub use editor_settings::{
|
||||
CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings,
|
||||
};
|
||||
pub use editor_settings::{CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine};
|
||||
pub use editor_settings_controls::*;
|
||||
use element::LineWithInvisibles;
|
||||
pub use element::{
|
||||
@@ -118,14 +116,14 @@ use parking_lot::{Mutex, RwLock};
|
||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||
use project::{
|
||||
CodeAction, Completion, CompletionIntent, FormatTrigger, Item, Location, Project, ProjectPath,
|
||||
ProjectTransaction, TaskSourceKind,
|
||||
ProjectTransaction, TaskSourceKind, WorktreeId,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::{proto::*, ErrorExt};
|
||||
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
|
||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use smallvec::SmallVec;
|
||||
use snippet::Snippet;
|
||||
use std::{
|
||||
@@ -4975,10 +4973,9 @@ impl Editor {
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
let (buffer, cursor_buffer_position) =
|
||||
self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
|
||||
|
||||
if !user_requested
|
||||
&& (!self.enable_inline_completions
|
||||
|| !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx))
|
||||
&& self.enable_inline_completions
|
||||
&& !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx)
|
||||
{
|
||||
self.discard_inline_completion(false, cx);
|
||||
return None;
|
||||
@@ -8745,7 +8742,7 @@ impl Editor {
|
||||
let (worktree_id, file) = project
|
||||
.buffer_for_id(runnable.buffer, cx)
|
||||
.and_then(|buffer| buffer.read(cx).file())
|
||||
.map(|file| (file.worktree_id(cx), file.clone()))
|
||||
.map(|file| (WorktreeId::from_usize(file.worktree_id()), file.clone()))
|
||||
.unzip();
|
||||
|
||||
(project.task_inventory().clone(), worktree_id, file)
|
||||
@@ -10640,7 +10637,7 @@ impl Editor {
|
||||
let fs = workspace.read(cx).app_state().fs.clone();
|
||||
let current_show = TabBarSettings::get_global(cx).show;
|
||||
update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
|
||||
setting.show = !current_show;
|
||||
setting.show = Some(!current_show);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11424,14 +11421,7 @@ impl Editor {
|
||||
.redacted_ranges(search_range, |file| {
|
||||
if let Some(file) = file {
|
||||
file.is_private()
|
||||
&& EditorSettings::get(
|
||||
Some(SettingsLocation {
|
||||
worktree_id: file.worktree_id(cx),
|
||||
path: file.path().as_ref(),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.redact_private_values
|
||||
&& EditorSettings::get(Some(file.as_ref().into()), cx).redact_private_values
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -12466,7 +12456,7 @@ fn inlay_hint_settings(
|
||||
let language = snapshot.language_at(location);
|
||||
let settings = all_language_settings(file, cx);
|
||||
settings
|
||||
.language(language.map(|l| l.name()).as_ref())
|
||||
.language(language.map(|l| l.name()).as_deref())
|
||||
.inlay_hints
|
||||
}
|
||||
|
||||
@@ -12563,7 +12553,7 @@ impl EditorSnapshot {
|
||||
let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
GitGutterSetting::TrackedFiles
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||
|
||||
@@ -3,105 +3,36 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct EditorSettings {
|
||||
/// Whether the cursor blinks in the editor.
|
||||
pub cursor_blink: bool,
|
||||
/// How to highlight the current line in the editor.
|
||||
pub current_line_highlight: CurrentLineHighlight,
|
||||
/// Whether to show the informational hover box when moving the mouse
|
||||
/// over symbols in the editor.
|
||||
pub hover_popover_enabled: bool,
|
||||
/// Whether to pop the completions menu while typing in an editor without
|
||||
/// explicitly requesting it.
|
||||
pub show_completions_on_input: bool,
|
||||
/// Whether to display inline and alongside documentation for items in the
|
||||
/// completions menu.
|
||||
pub show_completion_documentation: bool,
|
||||
/// The debounce delay before re-querying the language server for completion
|
||||
/// documentation when not included in original completion list.
|
||||
pub completion_documentation_secondary_query_debounce: u64,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
pub use_on_type_format: bool,
|
||||
/// Toolbar related settings
|
||||
pub toolbar: Toolbar,
|
||||
/// Scrollbar related settings
|
||||
pub scrollbar: Scrollbar,
|
||||
/// Gutter related settings
|
||||
pub gutter: Gutter,
|
||||
/// Whether the editor will scroll beyond the last line.
|
||||
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||
/// The number of lines to keep above/below the cursor when auto-scrolling.
|
||||
pub vertical_scroll_margin: f32,
|
||||
/// Scroll sensitivity multiplier. This multiplier is applied
|
||||
/// to both the horizontal and vertical delta values while scrolling.
|
||||
pub scroll_sensitivity: f32,
|
||||
/// Whether the line numbers on editors gutter are relative or not.
|
||||
pub relative_line_numbers: bool,
|
||||
/// When to populate a new search's query based on the text under the cursor.
|
||||
pub seed_search_query_from_cursor: SeedQuerySetting,
|
||||
pub use_smartcase_search: bool,
|
||||
/// The key to use for adding multiple cursors
|
||||
pub multi_cursor_modifier: MultiCursorModifier,
|
||||
/// Hide the values of variables in `private` files, as defined by the
|
||||
/// private_files setting. This only changes the visual representation,
|
||||
/// the values are still present in the file and can be selected / copied / pasted
|
||||
pub redact_private_values: bool,
|
||||
|
||||
/// How many lines to expand the multibuffer excerpts by default
|
||||
pub expand_excerpt_lines: u32,
|
||||
pub middle_click_paste: bool,
|
||||
/// What to do when multibuffer is double clicked in some of its excerpts
|
||||
/// (parts of singleton buffers).
|
||||
#[serde(default)]
|
||||
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
|
||||
/// Whether the editor search results will loop
|
||||
pub search_wrap: bool,
|
||||
#[serde(default)]
|
||||
pub search: SearchSettings,
|
||||
/// Show method signatures in the editor, when inside parentheses.
|
||||
pub auto_signature_help: bool,
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
pub show_signature_help_after_edits: bool,
|
||||
/// Jupyter REPL settings.
|
||||
pub jupyter: Jupyter,
|
||||
}
|
||||
|
||||
impl Default for EditorSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cursor_blink: true,
|
||||
current_line_highlight: CurrentLineHighlight::All,
|
||||
hover_popover_enabled: true,
|
||||
show_completions_on_input: true,
|
||||
show_completion_documentation: true,
|
||||
completion_documentation_secondary_query_debounce: 300,
|
||||
use_on_type_format: true,
|
||||
toolbar: Default::default(),
|
||||
scrollbar: Default::default(),
|
||||
gutter: Default::default(),
|
||||
scroll_beyond_last_line: ScrollBeyondLastLine::OnePage,
|
||||
vertical_scroll_margin: 3.,
|
||||
scroll_sensitivity: 1.0,
|
||||
relative_line_numbers: false,
|
||||
seed_search_query_from_cursor: SeedQuerySetting::Always,
|
||||
multi_cursor_modifier: MultiCursorModifier::Alt,
|
||||
redact_private_values: false,
|
||||
expand_excerpt_lines: 3,
|
||||
double_click_in_multibuffer: DoubleClickInMultibuffer::Select,
|
||||
search_wrap: true,
|
||||
auto_signature_help: false,
|
||||
show_signature_help_after_edits: true,
|
||||
jupyter: Default::default(),
|
||||
use_smartcase_search: false,
|
||||
middle_click_paste: true,
|
||||
search: SearchSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CurrentLineHighlight {
|
||||
@@ -139,93 +70,48 @@ pub enum DoubleClickInMultibuffer {
|
||||
Open,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Jupyter {
|
||||
/// Whether the Jupyter feature is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for Jupyter {
|
||||
fn default() -> Self {
|
||||
Self { enabled: true }
|
||||
}
|
||||
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct JupyterContent {
|
||||
/// Whether the Jupyter feature is enabled.
|
||||
///
|
||||
/// Default: true
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct Toolbar {
|
||||
/// Whether to display breadcrumbs in the editor toolbar.
|
||||
pub breadcrumbs: bool,
|
||||
/// Whether to display quick action buttons in the editor toolbar.
|
||||
pub quick_actions: bool,
|
||||
/// Whether to show the selections menu in the editor toolbar
|
||||
pub selections_menu: bool,
|
||||
}
|
||||
|
||||
impl Default for Toolbar {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
breadcrumbs: true,
|
||||
quick_actions: true,
|
||||
selections_menu: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct Scrollbar {
|
||||
/// When to show the scrollbar in the editor.
|
||||
pub show: ShowScrollbar,
|
||||
/// Whether to show git diff indicators in the scrollbar.
|
||||
pub git_diff: bool,
|
||||
/// Whether to show buffer search result indicators in the scrollbar.
|
||||
pub selected_symbol: bool,
|
||||
/// Whether to show selected symbol occurrences in the scrollbar.
|
||||
pub search_results: bool,
|
||||
/// Whether to show diagnostic indicators in the scrollbar.
|
||||
pub diagnostics: bool,
|
||||
/// Whether to show cursor positions in the scrollbar.
|
||||
pub cursors: bool,
|
||||
}
|
||||
|
||||
impl Default for Scrollbar {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show: ShowScrollbar::Auto,
|
||||
git_diff: true,
|
||||
selected_symbol: true,
|
||||
search_results: true,
|
||||
diagnostics: true,
|
||||
cursors: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gutter-related settings.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct Gutter {
|
||||
/// Whether to show line numbers in the gutter.
|
||||
pub line_numbers: bool,
|
||||
/// Whether to show code action buttons in the gutter.
|
||||
pub code_actions: bool,
|
||||
/// Whether to show runnable buttons in the gutter.
|
||||
pub runnables: bool,
|
||||
/// Whether to show fold buttons in the gutter.
|
||||
pub folds: bool,
|
||||
}
|
||||
|
||||
impl Default for Gutter {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
line_numbers: true,
|
||||
code_actions: true,
|
||||
runnables: true,
|
||||
folds: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When to show the scrollbar in the editor.
|
||||
///
|
||||
/// Default: auto
|
||||
@@ -270,17 +156,181 @@ pub enum ScrollBeyondLastLine {
|
||||
VerticalScrollMargin,
|
||||
}
|
||||
|
||||
/// Default options for buffer and project search items.
|
||||
#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct SearchSettings {
|
||||
#[serde(default)]
|
||||
pub whole_word: bool,
|
||||
#[serde(default)]
|
||||
pub case_sensitive: bool,
|
||||
#[serde(default)]
|
||||
pub include_ignored: bool,
|
||||
#[serde(default)]
|
||||
pub regex: bool,
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditorSettingsContent {
|
||||
/// Whether the cursor blinks in the editor.
|
||||
///
|
||||
/// Default: true
|
||||
pub cursor_blink: Option<bool>,
|
||||
/// How to highlight the current line in the editor.
|
||||
///
|
||||
/// Default: all
|
||||
pub current_line_highlight: Option<CurrentLineHighlight>,
|
||||
/// Whether to show the informational hover box when moving the mouse
|
||||
/// over symbols in the editor.
|
||||
///
|
||||
/// Default: true
|
||||
pub hover_popover_enabled: Option<bool>,
|
||||
|
||||
/// Whether to pop the completions menu while typing in an editor without
|
||||
/// explicitly requesting it.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_completions_on_input: Option<bool>,
|
||||
/// Whether to display inline and alongside documentation for items in the
|
||||
/// completions menu.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_completion_documentation: Option<bool>,
|
||||
/// The debounce delay before re-querying the language server for completion
|
||||
/// documentation when not included in original completion list.
|
||||
///
|
||||
/// Default: 300 ms
|
||||
pub completion_documentation_secondary_query_debounce: Option<u64>,
|
||||
/// Whether to use additional LSP queries to format (and amend) the code after
|
||||
/// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
///
|
||||
/// Default: true
|
||||
pub use_on_type_format: Option<bool>,
|
||||
/// Toolbar related settings
|
||||
pub toolbar: Option<ToolbarContent>,
|
||||
/// Scrollbar related settings
|
||||
pub scrollbar: Option<ScrollbarContent>,
|
||||
/// Gutter related settings
|
||||
pub gutter: Option<GutterContent>,
|
||||
/// Whether the editor will scroll beyond the last line.
|
||||
///
|
||||
/// Default: one_page
|
||||
pub scroll_beyond_last_line: Option<ScrollBeyondLastLine>,
|
||||
/// The number of lines to keep above/below the cursor when auto-scrolling.
|
||||
///
|
||||
/// Default: 3.
|
||||
pub vertical_scroll_margin: Option<f32>,
|
||||
/// Scroll sensitivity multiplier. This multiplier is applied
|
||||
/// to both the horizontal and vertical delta values while scrolling.
|
||||
///
|
||||
/// Default: 1.0
|
||||
pub scroll_sensitivity: Option<f32>,
|
||||
/// Whether the line numbers on editors gutter are relative or not.
|
||||
///
|
||||
/// Default: false
|
||||
pub relative_line_numbers: Option<bool>,
|
||||
/// When to populate a new search's query based on the text under the cursor.
|
||||
///
|
||||
/// Default: always
|
||||
pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
|
||||
pub use_smartcase_search: Option<bool>,
|
||||
/// The key to use for adding multiple cursors
|
||||
///
|
||||
/// Default: alt
|
||||
pub multi_cursor_modifier: Option<MultiCursorModifier>,
|
||||
/// Hide the values of variables in `private` files, as defined by the
|
||||
/// private_files setting. This only changes the visual representation,
|
||||
/// the values are still present in the file and can be selected / copied / pasted
|
||||
///
|
||||
/// Default: false
|
||||
pub redact_private_values: Option<bool>,
|
||||
|
||||
/// How many lines to expand the multibuffer excerpts by default
|
||||
///
|
||||
/// Default: 3
|
||||
pub expand_excerpt_lines: Option<u32>,
|
||||
|
||||
/// Whether to enable middle-click paste on Linux
|
||||
///
|
||||
/// Default: true
|
||||
pub middle_click_paste: Option<bool>,
|
||||
|
||||
/// What to do when multibuffer is double clicked in some of its excerpts
|
||||
/// (parts of singleton buffers).
|
||||
///
|
||||
/// Default: select
|
||||
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
|
||||
/// Whether the editor search results will loop
|
||||
///
|
||||
/// Default: true
|
||||
pub search_wrap: Option<bool>,
|
||||
|
||||
/// Whether to automatically show a signature help pop-up or not.
|
||||
///
|
||||
/// Default: false
|
||||
pub auto_signature_help: Option<bool>,
|
||||
|
||||
/// Whether to show the signature help pop-up after completions or bracket pairs inserted.
|
||||
///
|
||||
/// Default: true
|
||||
pub show_signature_help_after_edits: Option<bool>,
|
||||
|
||||
/// Jupyter REPL settings.
|
||||
pub jupyter: Option<JupyterContent>,
|
||||
}
|
||||
|
||||
// Toolbar related settings
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ToolbarContent {
|
||||
/// Whether to display breadcrumbs in the editor toolbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub breadcrumbs: Option<bool>,
|
||||
/// Whether to display quick action buttons in the editor toolbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub quick_actions: Option<bool>,
|
||||
|
||||
/// Whether to show the selections menu in the editor toolbar
|
||||
///
|
||||
/// Default: true
|
||||
pub selections_menu: Option<bool>,
|
||||
}
|
||||
|
||||
/// Scrollbar related settings
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
pub struct ScrollbarContent {
|
||||
/// When to show the scrollbar in the editor.
|
||||
///
|
||||
/// Default: auto
|
||||
pub show: Option<ShowScrollbar>,
|
||||
/// Whether to show git diff indicators in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub git_diff: Option<bool>,
|
||||
/// Whether to show buffer search result indicators in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub search_results: Option<bool>,
|
||||
/// Whether to show selected symbol occurrences in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub selected_symbol: Option<bool>,
|
||||
/// Whether to show diagnostic indicators in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub diagnostics: Option<bool>,
|
||||
/// Whether to show cursor positions in the scrollbar.
|
||||
///
|
||||
/// Default: true
|
||||
pub cursors: Option<bool>,
|
||||
}
|
||||
|
||||
/// Gutter related settings
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct GutterContent {
|
||||
/// Whether to show line numbers in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub line_numbers: Option<bool>,
|
||||
/// Whether to show code action buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub code_actions: Option<bool>,
|
||||
/// Whether to show runnable buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub runnables: Option<bool>,
|
||||
/// Whether to show fold buttons in the gutter.
|
||||
///
|
||||
/// Default: true
|
||||
pub folds: Option<bool>,
|
||||
}
|
||||
|
||||
impl EditorSettings {
|
||||
@@ -292,7 +342,7 @@ impl EditorSettings {
|
||||
impl Settings for EditorSettings {
|
||||
const KEY: Option<&'static str> = None;
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = EditorSettingsContent;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{AppContext, FontFeatures, FontWeight};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use project::project_settings::{InlineBlameSettings, ProjectSettings};
|
||||
use settings::{EditableSettingControl, Settings};
|
||||
use theme::{FontFamilyCache, ThemeSettings};
|
||||
use ui::{
|
||||
@@ -296,7 +296,14 @@ impl EditableSettingControl for InlineGitBlameControl {
|
||||
value: Self::Value,
|
||||
_cx: &AppContext,
|
||||
) {
|
||||
settings.git.inline_blame.enabled = value;
|
||||
if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
|
||||
inline_blame.enabled = value;
|
||||
} else {
|
||||
settings.git.inline_blame = Some(InlineBlameSettings {
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +349,14 @@ impl EditableSettingControl for LineNumbersControl {
|
||||
value: Self::Value,
|
||||
_cx: &AppContext,
|
||||
) {
|
||||
settings.gutter.line_numbers = value;
|
||||
if let Some(gutter) = settings.gutter.as_mut() {
|
||||
gutter.line_numbers = Some(value);
|
||||
} else {
|
||||
settings.gutter = Some(crate::editor_settings::GutterContent {
|
||||
line_numbers: Some(value),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,7 +402,7 @@ impl EditableSettingControl for RelativeLineNumbersControl {
|
||||
value: Self::Value,
|
||||
_cx: &AppContext,
|
||||
) {
|
||||
settings.relative_line_numbers = value;
|
||||
settings.relative_line_numbers = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ use language::{
|
||||
},
|
||||
BracketPairConfig,
|
||||
Capability::ReadWrite,
|
||||
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
|
||||
LanguageName, Override, ParsedMarkdown, Point,
|
||||
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
|
||||
ParsedMarkdown, Point,
|
||||
};
|
||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||
use multi_buffer::MultiBufferIndentGuide;
|
||||
@@ -6964,7 +6964,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.auto_signature_help = true;
|
||||
settings.auto_signature_help = Some(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7105,8 +7105,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.auto_signature_help = false;
|
||||
settings.show_signature_help_after_edits = false;
|
||||
settings.auto_signature_help = Some(false);
|
||||
settings.show_signature_help_after_edits = Some(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7232,8 +7232,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.auto_signature_help = false;
|
||||
settings.show_signature_help_after_edits = true;
|
||||
settings.auto_signature_help = Some(false);
|
||||
settings.show_signature_help_after_edits = Some(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7274,8 +7274,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.auto_signature_help = true;
|
||||
settings.show_signature_help_after_edits = false;
|
||||
settings.auto_signature_help = Some(true);
|
||||
settings.show_signature_help_after_edits = Some(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7318,7 +7318,7 @@ async fn test_signature_help(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.auto_signature_help = true;
|
||||
settings.auto_signature_help = Some(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7759,7 +7759,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|settings, cx| {
|
||||
settings.update_user_settings::<EditorSettings>(cx, |settings| {
|
||||
settings.show_completions_on_input = false;
|
||||
settings.show_completions_on_input = Some(false);
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -9587,12 +9587,12 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
let server_restarts = Arc::new(AtomicUsize::new(0));
|
||||
let closure_restarts = Arc::clone(&server_restarts);
|
||||
let language_server_name = "test language server";
|
||||
let language_name: LanguageName = "Rust".into();
|
||||
let language_name: Arc<str> = "Rust".into();
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
language_registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: language_name.clone(),
|
||||
name: Arc::clone(&language_name),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
@@ -9629,7 +9629,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
|
||||
let _fake_server = fake_servers.next().await.unwrap();
|
||||
update_test_language_settings(cx, |language_settings| {
|
||||
language_settings.languages.insert(
|
||||
language_name.clone(),
|
||||
Arc::clone(&language_name),
|
||||
LanguageSettingsContent {
|
||||
tab_size: NonZeroU32::new(8),
|
||||
..Default::default()
|
||||
|
||||
@@ -1283,7 +1283,10 @@ impl EditorElement {
|
||||
.row,
|
||||
);
|
||||
|
||||
let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter;
|
||||
let git_gutter_setting = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.git_gutter
|
||||
.unwrap_or_default();
|
||||
let display_hunks = buffer_snapshot
|
||||
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
|
||||
.map(|hunk| diff_hunk_to_display(&hunk, snapshot))
|
||||
@@ -1363,10 +1366,12 @@ impl EditorElement {
|
||||
};
|
||||
let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
|
||||
|
||||
let min_column_in_pixels = self.column_pixels(
|
||||
ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
|
||||
cx,
|
||||
);
|
||||
let min_column_in_pixels = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.inline_blame
|
||||
.and_then(|settings| settings.min_column)
|
||||
.map(|col| self.column_pixels(col as usize, cx))
|
||||
.unwrap_or(px(0.));
|
||||
let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
|
||||
|
||||
cmp::max(padded_line_end, min_start)
|
||||
@@ -2727,7 +2732,7 @@ impl EditorElement {
|
||||
.position(position)
|
||||
.child(context_menu)
|
||||
.anchor(AnchorCorner::TopLeft)
|
||||
.snap_to_window_with_margin(px(8.)),
|
||||
.snap_to_window(),
|
||||
)
|
||||
.with_priority(1)
|
||||
.into_any(),
|
||||
@@ -3326,7 +3331,7 @@ impl EditorElement {
|
||||
.unwrap_or_else(|| {
|
||||
matches!(
|
||||
ProjectSettings::get_global(cx).git.git_gutter,
|
||||
GitGutterSetting::TrackedFiles
|
||||
Some(GitGutterSetting::TrackedFiles)
|
||||
)
|
||||
});
|
||||
if show_git_gutter {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use gpui::ViewContext;
|
||||
use language::CursorShape;
|
||||
|
||||
use crate::{Editor, RangeToAnchorExt};
|
||||
|
||||
@@ -14,18 +13,11 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
|
||||
return;
|
||||
}
|
||||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let head = newest_selection.head();
|
||||
let mut tail = head;
|
||||
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
|
||||
&& head < snapshot.buffer_snapshot.len()
|
||||
{
|
||||
tail += 1;
|
||||
}
|
||||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
if let Some((opening_range, closing_range)) = snapshot
|
||||
.buffer_snapshot
|
||||
.innermost_enclosing_bracket_ranges(head..tail, None)
|
||||
.innermost_enclosing_bracket_ranges(head..head, None)
|
||||
{
|
||||
editor.highlight_background::<MatchingBracketHighlight>(
|
||||
&[
|
||||
|
||||
@@ -1054,12 +1054,28 @@ impl SerializableItem for Editor {
|
||||
let buffer = buffer_task.await?;
|
||||
|
||||
pane.update(&mut cx, |_, cx| {
|
||||
cx.new_view(|cx| {
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
|
||||
|
||||
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
|
||||
editor
|
||||
})
|
||||
});
|
||||
|
||||
// let weak = editor.model.downgrade();
|
||||
// cx.spawn(|_, cx| async move {
|
||||
// for i in 0..5 {
|
||||
// println!("{i}/5: going to sleep for 5 seconds");
|
||||
// cx.background_executor()
|
||||
// .timer(std::time::Duration::from_secs(5))
|
||||
// .await;
|
||||
// }
|
||||
|
||||
// println!("done sleeping");
|
||||
// weak.assert_released();
|
||||
// })
|
||||
// .detach();
|
||||
|
||||
editor
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1705,8 +1721,8 @@ mod tests {
|
||||
|
||||
let buffer = editor.buffer().read(cx).as_singleton().unwrap().read(cx);
|
||||
assert_eq!(
|
||||
buffer.language().map(|lang| lang.name()),
|
||||
Some("Rust".into())
|
||||
buffer.language().map(|lang| lang.name()).as_deref(),
|
||||
Some("Rust")
|
||||
); // Language should be set to Rust
|
||||
assert!(buffer.file().is_none()); // The buffer should not have an associated file
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
static RUST_ANALYZER_NAME: &str = "rust-analyzer";
|
||||
|
||||
fn is_rust_language(language: &Language) -> bool {
|
||||
language.name() == "Rust".into()
|
||||
language.name().as_ref() == "Rust"
|
||||
}
|
||||
|
||||
pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
|
||||
|
||||
@@ -58,7 +58,7 @@ impl EditorLspTestContext {
|
||||
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||
let mut fake_servers = language_registry.register_fake_lsp_adapter(
|
||||
language.name(),
|
||||
language.name().as_ref(),
|
||||
FakeLspAdapter {
|
||||
capabilities,
|
||||
..Default::default()
|
||||
|
||||
@@ -38,6 +38,7 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Language>,
|
||||
_: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use fs::Fs;
|
||||
use language::{LanguageName, LanguageServerName};
|
||||
use language::LanguageServerName;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -106,10 +106,10 @@ pub struct GrammarManifestEntry {
|
||||
pub struct LanguageServerManifestEntry {
|
||||
/// Deprecated in favor of `languages`.
|
||||
#[serde(default)]
|
||||
language: Option<LanguageName>,
|
||||
language: Option<Arc<str>>,
|
||||
/// The list of languages this language server should work with.
|
||||
#[serde(default)]
|
||||
languages: Vec<LanguageName>,
|
||||
languages: Vec<Arc<str>>,
|
||||
#[serde(default)]
|
||||
pub language_ids: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
@@ -124,7 +124,7 @@ impl LanguageServerManifestEntry {
|
||||
///
|
||||
/// We can replace this with just field access for the `languages` field once
|
||||
/// we have removed `language`.
|
||||
pub fn languages(&self) -> impl IntoIterator<Item = LanguageName> + '_ {
|
||||
pub fn languages(&self) -> impl IntoIterator<Item = Arc<str>> + '_ {
|
||||
let language = if self.languages.is_empty() {
|
||||
self.language.clone()
|
||||
} else {
|
||||
|
||||
@@ -6,25 +6,18 @@ use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
|
||||
#[serde(default)]
|
||||
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
|
||||
pub struct ExtensionSettings {
|
||||
/// The extensions that should be automatically installed by Zed.
|
||||
///
|
||||
/// This is used to make functionality provided by extensions (e.g., language support)
|
||||
/// available out-of-the-box.
|
||||
#[serde(default)]
|
||||
pub auto_install_extensions: HashMap<Arc<str>, bool>,
|
||||
#[serde(default)]
|
||||
pub auto_update_extensions: HashMap<Arc<str>, bool>,
|
||||
}
|
||||
|
||||
impl Default for ExtensionSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
auto_install_extensions: HashMap::from_iter([("html".into(), true)]),
|
||||
auto_update_extensions: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ExtensionSettings {
|
||||
/// Returns whether the given extension should be auto-installed.
|
||||
pub fn should_auto_install(&self, extension_id: &str) -> bool {
|
||||
|
||||
@@ -36,8 +36,7 @@ use gpui::{
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
@@ -149,7 +148,7 @@ impl Global for GlobalExtensionStore {}
|
||||
pub struct ExtensionIndex {
|
||||
pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
|
||||
pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
|
||||
pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
|
||||
pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
|
||||
@@ -370,9 +369,9 @@ impl ExtensionStore {
|
||||
let installed_dir = this.installed_dir.clone();
|
||||
async move {
|
||||
let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
|
||||
while let Some(events) = paths.next().await {
|
||||
for event in events {
|
||||
let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {
|
||||
while let Some(paths) = paths.next().await {
|
||||
for path in paths {
|
||||
let Ok(event_path) = path.strip_prefix(&installed_dir) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
||||
@@ -609,7 +609,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut fake_servers = language_registry.fake_language_servers("Gleam".into());
|
||||
let mut fake_servers = language_registry.fake_language_servers("Gleam");
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use ::http_client::AsyncBody;
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use ::settings::Settings;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
@@ -9,7 +9,6 @@ use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use isahc::config::{Configurable, RedirectPolicy};
|
||||
use language::LanguageName;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -77,7 +76,7 @@ impl HostWorktree for WasmState {
|
||||
delegate: Resource<Arc<dyn LspAdapterDelegate>>,
|
||||
) -> wasmtime::Result<u64> {
|
||||
let delegate = self.table.get(&delegate)?;
|
||||
Ok(delegate.worktree_id().to_proto())
|
||||
Ok(delegate.worktree_id())
|
||||
}
|
||||
|
||||
async fn root_path(
|
||||
@@ -394,15 +393,14 @@ impl ExtensionImports for WasmState {
|
||||
let location = location
|
||||
.as_ref()
|
||||
.map(|location| ::settings::SettingsLocation {
|
||||
worktree_id: WorktreeId::from_proto(location.worktree_id),
|
||||
worktree_id: location.worktree_id as usize,
|
||||
path: Path::new(&location.path),
|
||||
});
|
||||
|
||||
cx.update(|cx| match category.as_str() {
|
||||
"language" => {
|
||||
let key = key.map(|k| LanguageName::new(&k));
|
||||
let settings =
|
||||
AllLanguageSettings::get(location, cx).language(key.as_ref());
|
||||
AllLanguageSettings::get(location, cx).language(key.as_deref());
|
||||
Ok(serde_json::to_string(&settings::LanguageSettings {
|
||||
tab_size: settings.tab_size,
|
||||
})?)
|
||||
|
||||
@@ -1000,7 +1000,7 @@ impl ExtensionsPage {
|
||||
this.update_settings::<VimModeSetting>(
|
||||
selection,
|
||||
cx,
|
||||
|setting, value| *setting = VimModeSetting(value),
|
||||
|setting, value| *setting = Some(value),
|
||||
);
|
||||
}),
|
||||
)),
|
||||
|
||||
@@ -448,7 +448,7 @@ fn history_file_exists(abs_path: &PathBuf) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn history_file_exists(abs_path: &Path) -> bool {
|
||||
fn history_file_exists(abs_path: &PathBuf) -> bool {
|
||||
!abs_path.ends_with("nonexistent.rs")
|
||||
}
|
||||
|
||||
|
||||
@@ -2012,7 +2012,7 @@ fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
|
||||
}
|
||||
|
||||
fn test_path_position(test_str: &str) -> FileSearchQuery {
|
||||
let path_position = PathWithPosition::parse_str(test_str);
|
||||
let path_position = PathWithPosition::parse_str(&test_str);
|
||||
|
||||
FileSearchQuery {
|
||||
raw_query: test_str.to_owned(),
|
||||
|
||||
@@ -181,7 +181,7 @@ impl PickerDelegate for OpenPathDelegate {
|
||||
}
|
||||
|
||||
let matches = fuzzy::match_strings(
|
||||
match_candidates.as_slice(),
|
||||
&match_candidates.as_slice(),
|
||||
&suffix,
|
||||
false,
|
||||
100,
|
||||
|
||||
@@ -45,25 +45,6 @@ pub trait Watcher: Send + Sync {
|
||||
fn remove(&self, path: &Path) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum PathEventKind {
|
||||
Removed,
|
||||
Created,
|
||||
Changed,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PathEvent {
|
||||
pub path: PathBuf,
|
||||
pub kind: Option<PathEventKind>,
|
||||
}
|
||||
|
||||
impl From<PathEvent> for PathBuf {
|
||||
fn from(event: PathEvent) -> Self {
|
||||
event.path
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Fs: Send + Sync {
|
||||
async fn create_dir(&self, path: &Path) -> Result<()>;
|
||||
@@ -111,7 +92,7 @@ pub trait Fs: Send + Sync {
|
||||
path: &Path,
|
||||
latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
);
|
||||
|
||||
@@ -488,38 +469,17 @@ impl Fs for RealFs {
|
||||
path: &Path,
|
||||
latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use fsevent::{EventStream, StreamFlags};
|
||||
use fsevent::EventStream;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let (stream, handle) = EventStream::new(&[path], latency);
|
||||
std::thread::spawn(move || {
|
||||
stream.run(move |events| {
|
||||
smol::block_on(
|
||||
tx.send(
|
||||
events
|
||||
.into_iter()
|
||||
.map(|event| {
|
||||
let kind = if event.flags.contains(StreamFlags::ITEM_REMOVED) {
|
||||
Some(PathEventKind::Removed)
|
||||
} else if event.flags.contains(StreamFlags::ITEM_CREATED) {
|
||||
Some(PathEventKind::Created)
|
||||
} else if event.flags.contains(StreamFlags::ITEM_MODIFIED) {
|
||||
Some(PathEventKind::Changed)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
PathEvent {
|
||||
path: event.path,
|
||||
kind,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
)
|
||||
.is_ok()
|
||||
smol::block_on(tx.send(events.into_iter().map(|event| event.path).collect()))
|
||||
.is_ok()
|
||||
});
|
||||
});
|
||||
|
||||
@@ -538,46 +498,32 @@ impl Fs for RealFs {
|
||||
path: &Path,
|
||||
latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use notify::EventKind;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let pending_paths: Arc<Mutex<Vec<PathEvent>>> = Default::default();
|
||||
let pending_paths: Arc<Mutex<Vec<PathBuf>>> = Default::default();
|
||||
let root_path = path.to_path_buf();
|
||||
|
||||
watcher::global(|g| {
|
||||
let tx = tx.clone();
|
||||
let pending_paths = pending_paths.clone();
|
||||
g.add(move |event: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut paths = event
|
||||
.paths
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
path.starts_with(&root_path).then(|| PathEvent {
|
||||
path: path.clone(),
|
||||
kind,
|
||||
})
|
||||
})
|
||||
.filter(|path| path.starts_with(&root_path))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !paths.is_empty() {
|
||||
paths.sort();
|
||||
let mut pending_paths = pending_paths.lock();
|
||||
if pending_paths.is_empty() {
|
||||
tx.try_send(()).ok();
|
||||
}
|
||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, |a, b| {
|
||||
a.path.cmp(&b.path)
|
||||
});
|
||||
util::extend_sorted(&mut *pending_paths, paths, usize::MAX, PathBuf::cmp);
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -615,10 +561,10 @@ impl Fs for RealFs {
|
||||
path: &Path,
|
||||
_latency: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
use notify::{EventKind, Watcher};
|
||||
use notify::Watcher;
|
||||
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
|
||||
@@ -626,21 +572,7 @@ impl Fs for RealFs {
|
||||
let tx = tx.clone();
|
||||
move |event: Result<notify::Event, _>| {
|
||||
if let Some(event) = event.log_err() {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
tx.try_send(
|
||||
event
|
||||
.paths
|
||||
.into_iter()
|
||||
.map(|path| PathEvent { path, kind })
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.ok();
|
||||
tx.try_send(event.paths).ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -750,9 +682,9 @@ struct FakeFsState {
|
||||
root: Arc<Mutex<FakeFsEntry>>,
|
||||
next_inode: u64,
|
||||
next_mtime: SystemTime,
|
||||
event_txs: Vec<smol::channel::Sender<Vec<PathEvent>>>,
|
||||
event_txs: Vec<smol::channel::Sender<Vec<PathBuf>>>,
|
||||
events_paused: bool,
|
||||
buffered_events: Vec<PathEvent>,
|
||||
buffered_events: Vec<PathBuf>,
|
||||
metadata_call_count: usize,
|
||||
read_dir_call_count: usize,
|
||||
}
|
||||
@@ -861,14 +793,11 @@ impl FakeFsState {
|
||||
|
||||
fn emit_event<I, T>(&mut self, paths: I)
|
||||
where
|
||||
I: IntoIterator<Item = (T, Option<PathEventKind>)>,
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<PathBuf>,
|
||||
{
|
||||
self.buffered_events
|
||||
.extend(paths.into_iter().map(|(path, kind)| PathEvent {
|
||||
path: path.into(),
|
||||
kind,
|
||||
}));
|
||||
.extend(paths.into_iter().map(Into::into));
|
||||
|
||||
if !self.events_paused {
|
||||
self.flush_events(self.buffered_events.len());
|
||||
@@ -943,7 +872,7 @@ impl FakeFs {
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
state.emit_event([(path.to_path_buf(), None)]);
|
||||
state.emit_event([path.to_path_buf()]);
|
||||
}
|
||||
|
||||
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
|
||||
@@ -966,7 +895,7 @@ impl FakeFs {
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
state.emit_event([(path, None)]);
|
||||
state.emit_event([path]);
|
||||
}
|
||||
|
||||
fn write_file_internal(&self, path: impl AsRef<Path>, content: Vec<u8>) -> Result<()> {
|
||||
@@ -981,24 +910,18 @@ impl FakeFs {
|
||||
mtime,
|
||||
content,
|
||||
}));
|
||||
let mut kind = None;
|
||||
state.write_path(path, {
|
||||
let kind = &mut kind;
|
||||
move |entry| {
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
*kind = Some(PathEventKind::Created);
|
||||
e.insert(file);
|
||||
}
|
||||
btree_map::Entry::Occupied(mut e) => {
|
||||
*kind = Some(PathEventKind::Changed);
|
||||
*e.get_mut() = file;
|
||||
}
|
||||
state.write_path(path, move |entry| {
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
e.insert(file);
|
||||
}
|
||||
btree_map::Entry::Occupied(mut e) => {
|
||||
*e.get_mut() = file;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
state.emit_event([(path, kind)]);
|
||||
state.emit_event([path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1107,7 +1030,7 @@ impl FakeFs {
|
||||
f(&mut repo_state);
|
||||
|
||||
if emit_git_event {
|
||||
state.emit_event([(dot_git, None)]);
|
||||
state.emit_event([dot_git]);
|
||||
}
|
||||
} else {
|
||||
panic!("not a directory");
|
||||
@@ -1158,7 +1081,7 @@ impl FakeFs {
|
||||
self.state.lock().emit_event(
|
||||
statuses
|
||||
.iter()
|
||||
.map(|(path, _)| (dot_git.parent().unwrap().join(path), None)),
|
||||
.map(|(path, _)| dot_git.parent().unwrap().join(path)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1328,7 +1251,7 @@ impl Fs for FakeFs {
|
||||
state.next_inode += 1;
|
||||
state.write_path(&cur_path, |entry| {
|
||||
entry.or_insert_with(|| {
|
||||
created_dirs.push((cur_path.clone(), Some(PathEventKind::Created)));
|
||||
created_dirs.push(cur_path.clone());
|
||||
Arc::new(Mutex::new(FakeFsEntry::Dir {
|
||||
inode,
|
||||
mtime,
|
||||
@@ -1340,7 +1263,7 @@ impl Fs for FakeFs {
|
||||
})?
|
||||
}
|
||||
|
||||
self.state.lock().emit_event(created_dirs);
|
||||
self.state.lock().emit_event(&created_dirs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1356,12 +1279,10 @@ impl Fs for FakeFs {
|
||||
mtime,
|
||||
content: Vec::new(),
|
||||
}));
|
||||
let mut kind = Some(PathEventKind::Created);
|
||||
state.write_path(path, |entry| {
|
||||
match entry {
|
||||
btree_map::Entry::Occupied(mut e) => {
|
||||
if options.overwrite {
|
||||
kind = Some(PathEventKind::Changed);
|
||||
*e.get_mut() = file;
|
||||
} else if !options.ignore_if_exists {
|
||||
return Err(anyhow!("path already exists: {}", path.display()));
|
||||
@@ -1373,7 +1294,7 @@ impl Fs for FakeFs {
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
state.emit_event([(path, kind)]);
|
||||
state.emit_event([path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1392,8 +1313,7 @@ impl Fs for FakeFs {
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
state.emit_event([(path, None)]);
|
||||
|
||||
state.emit_event([path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1468,10 +1388,7 @@ impl Fs for FakeFs {
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
state.emit_event([
|
||||
(old_path, Some(PathEventKind::Removed)),
|
||||
(new_path, Some(PathEventKind::Created)),
|
||||
]);
|
||||
state.emit_event(&[old_path, new_path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1486,11 +1403,9 @@ impl Fs for FakeFs {
|
||||
state.next_mtime += Duration::from_nanos(1);
|
||||
let source_entry = state.read_path(&source)?;
|
||||
let content = source_entry.lock().file_content(&source)?.clone();
|
||||
let mut kind = Some(PathEventKind::Created);
|
||||
let entry = state.write_path(&target, |e| match e {
|
||||
btree_map::Entry::Occupied(e) => {
|
||||
if options.overwrite {
|
||||
kind = Some(PathEventKind::Changed);
|
||||
Ok(Some(e.get().clone()))
|
||||
} else if !options.ignore_if_exists {
|
||||
return Err(anyhow!("{target:?} already exists"));
|
||||
@@ -1510,7 +1425,7 @@ impl Fs for FakeFs {
|
||||
if let Some(entry) = entry {
|
||||
entry.lock().set_file_content(&target, content)?;
|
||||
}
|
||||
state.emit_event([(target, kind)]);
|
||||
state.emit_event(&[target]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1547,7 +1462,7 @@ impl Fs for FakeFs {
|
||||
e.remove();
|
||||
}
|
||||
}
|
||||
state.emit_event([(path, Some(PathEventKind::Removed))]);
|
||||
state.emit_event(&[path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1576,7 +1491,7 @@ impl Fs for FakeFs {
|
||||
e.remove();
|
||||
}
|
||||
}
|
||||
state.emit_event([(path, Some(PathEventKind::Removed))]);
|
||||
state.emit_event(&[path]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1717,7 +1632,7 @@ impl Fs for FakeFs {
|
||||
path: &Path,
|
||||
_: Duration,
|
||||
) -> (
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathEvent>>>>,
|
||||
Pin<Box<dyn Send + Stream<Item = Vec<PathBuf>>>>,
|
||||
Arc<dyn Watcher>,
|
||||
) {
|
||||
self.simulate_random_delay().await;
|
||||
@@ -1727,9 +1642,7 @@ impl Fs for FakeFs {
|
||||
let executor = self.executor.clone();
|
||||
(
|
||||
Box::pin(futures::StreamExt::filter(rx, move |events| {
|
||||
let result = events
|
||||
.iter()
|
||||
.any(|evt_path| evt_path.path.starts_with(&path));
|
||||
let result = events.iter().any(|evt_path| evt_path.starts_with(&path));
|
||||
let executor = executor.clone();
|
||||
async move {
|
||||
executor.simulate_random_delay().await;
|
||||
|
||||
@@ -180,10 +180,18 @@ pub(crate) enum LineIndicatorFormat {
|
||||
Long,
|
||||
}
|
||||
|
||||
/// Whether or not to automatically check for updates.
|
||||
///
|
||||
/// Values: short, long
|
||||
/// Default: short
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
|
||||
|
||||
impl Settings for LineIndicatorFormat {
|
||||
const KEY: Option<&'static str> = Some("line_indicator_format");
|
||||
|
||||
type FileContent = Self;
|
||||
type FileContent = Option<LineIndicatorFormatContent>;
|
||||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
@@ -191,9 +199,9 @@ impl Settings for LineIndicatorFormat {
|
||||
) -> anyhow::Result<Self> {
|
||||
let format = [sources.release_channel, sources.user]
|
||||
.into_iter()
|
||||
.find_map(|value| value.copied())
|
||||
.unwrap_or(*sources.default);
|
||||
.find_map(|value| value.copied().flatten())
|
||||
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
|
||||
|
||||
Ok(format)
|
||||
Ok(format.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,12 +304,7 @@ pub enum Model {
|
||||
#[serde(rename = "gemini-1.5-flash")]
|
||||
Gemini15Flash,
|
||||
#[serde(rename = "custom")]
|
||||
Custom {
|
||||
name: String,
|
||||
/// The name displayed in the UI, such as in the assistant panel model dropdown menu.
|
||||
display_name: Option<String>,
|
||||
max_tokens: usize,
|
||||
},
|
||||
Custom { name: String, max_tokens: usize },
|
||||
}
|
||||
|
||||
impl Model {
|
||||
@@ -325,9 +320,7 @@ impl Model {
|
||||
match self {
|
||||
Model::Gemini15Pro => "Gemini 1.5 Pro",
|
||||
Model::Gemini15Flash => "Gemini 1.5 Flash",
|
||||
Self::Custom {
|
||||
name, display_name, ..
|
||||
} => display_name.as_ref().unwrap_or(name),
|
||||
Model::Custom { name, .. } => name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ workspace = true
|
||||
[features]
|
||||
default = []
|
||||
test-support = [
|
||||
"backtrace",
|
||||
"collections/test-support",
|
||||
"rand",
|
||||
"util/test-support",
|
||||
@@ -29,7 +28,7 @@ doctest = false
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-task = "4.7"
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
backtrace = { version = "0.3" }
|
||||
blade-graphics = { workspace = true, optional = true }
|
||||
blade-macros = { workspace = true, optional = true }
|
||||
blade-util = { workspace = true, optional = true }
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -142,6 +142,12 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a start time for tracking time to first window draw.
|
||||
pub fn measure_time_to_first_window_draw(self, start: Instant) -> Self {
|
||||
self.0.borrow_mut().time_to_first_window_draw = Some(TimeToFirstWindowDraw::Pending(start));
|
||||
self
|
||||
}
|
||||
|
||||
/// Start the application. The provided callback will be called once the
|
||||
/// app is fully launched.
|
||||
pub fn run<F>(self, on_finish_launching: F)
|
||||
@@ -247,6 +253,7 @@ pub struct AppContext {
|
||||
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
|
||||
pub(crate) propagate_event: bool,
|
||||
pub(crate) prompt_builder: Option<PromptBuilder>,
|
||||
pub(crate) time_to_first_window_draw: Option<TimeToFirstWindowDraw>,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
@@ -300,6 +307,7 @@ impl AppContext {
|
||||
layout_id_buffer: Default::default(),
|
||||
propagate_event: true,
|
||||
prompt_builder: Some(PromptBuilder::Default),
|
||||
time_to_first_window_draw: None,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -354,6 +362,7 @@ impl AppContext {
|
||||
let result = update(self);
|
||||
if !self.flushing_effects && self.pending_updates == 1 {
|
||||
self.flushing_effects = true;
|
||||
println!("flushing effects");
|
||||
self.flush_effects();
|
||||
self.flushing_effects = false;
|
||||
}
|
||||
@@ -657,11 +666,6 @@ impl AppContext {
|
||||
self.platform.reveal_path(path)
|
||||
}
|
||||
|
||||
/// Opens the specified path with the system's default application.
|
||||
pub fn open_with_system(&self, path: &Path) {
|
||||
self.platform.open_with_system(path)
|
||||
}
|
||||
|
||||
/// Returns whether the user has configured scrollbars to auto-hide at the platform level.
|
||||
pub fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
self.platform.should_auto_hide_scrollbars()
|
||||
@@ -703,6 +707,7 @@ impl AppContext {
|
||||
};
|
||||
|
||||
self.pending_effects.push_back(effect);
|
||||
// If we're not in an update call; schedule one (somehow)
|
||||
}
|
||||
|
||||
/// Called at the end of [`AppContext::update`] to complete any side effects
|
||||
@@ -762,6 +767,7 @@ impl AppContext {
|
||||
/// reference count has become zero. We invoke any release observers before dropping
|
||||
/// each entity.
|
||||
fn release_dropped_entities(&mut self) {
|
||||
// println!("releasing dropped entities");
|
||||
loop {
|
||||
let dropped = self.entities.take_dropped();
|
||||
if dropped.is_empty() {
|
||||
@@ -1307,6 +1313,14 @@ impl AppContext {
|
||||
|
||||
(task, is_first)
|
||||
}
|
||||
|
||||
/// Returns the time to first window draw, if available.
|
||||
pub fn time_to_first_window_draw(&self) -> Option<Duration> {
|
||||
match self.time_to_first_window_draw {
|
||||
Some(TimeToFirstWindowDraw::Done(duration)) => Some(duration),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
@@ -1387,6 +1401,7 @@ impl Context for AppContext {
|
||||
if window.removed {
|
||||
cx.window_handles.remove(&handle.id);
|
||||
cx.windows.remove(handle.id);
|
||||
cx.flush_effects();
|
||||
} else {
|
||||
cx.windows
|
||||
.get_mut(handle.id)
|
||||
@@ -1470,6 +1485,15 @@ impl<G: Global> DerefMut for GlobalLease<G> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the initialization duration of the application.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TimeToFirstWindowDraw {
|
||||
/// The application is still initializing, and contains the start time.
|
||||
Pending(Instant),
|
||||
/// The application has finished initializing, and contains the total duration.
|
||||
Done(Duration),
|
||||
}
|
||||
|
||||
/// Contains state associated with an active drag operation, started by dragging an element
|
||||
/// within the window or by dragging into the app from the underlying platform.
|
||||
pub struct AnyDrag {
|
||||
@@ -1504,9 +1528,3 @@ pub struct KeystrokeEvent {
|
||||
/// The action that was resolved for the keystroke, if any
|
||||
pub action: Option<Box<dyn Action>>,
|
||||
}
|
||||
|
||||
impl Drop for AppContext {
|
||||
fn drop(&mut self) {
|
||||
println!("Dropping the App Context");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{
|
||||
thread::panicking,
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
use collections::HashMap;
|
||||
|
||||
slotmap::new_key_type! {
|
||||
@@ -57,7 +57,7 @@ pub(crate) struct EntityMap {
|
||||
struct EntityRefCounts {
|
||||
counts: SlotMap<EntityId, AtomicUsize>,
|
||||
dropped_entity_ids: Vec<EntityId>,
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
leak_detector: LeakDetector,
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl EntityMap {
|
||||
ref_counts: Arc::new(RwLock::new(EntityRefCounts {
|
||||
counts: SlotMap::with_key(),
|
||||
dropped_entity_ids: Vec::new(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
leak_detector: LeakDetector {
|
||||
next_handle_id: 0,
|
||||
entity_handles: HashMap::default(),
|
||||
@@ -193,7 +193,7 @@ pub struct AnyModel {
|
||||
pub(crate) entity_id: EntityId,
|
||||
pub(crate) entity_type: TypeId,
|
||||
entity_map: Weak<RwLock<EntityRefCounts>>,
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
handle_id: HandleId,
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ impl AnyModel {
|
||||
entity_id: id,
|
||||
entity_type,
|
||||
entity_map: entity_map.clone(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
handle_id: entity_map
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
@@ -262,7 +262,7 @@ impl Clone for AnyModel {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_map.clone(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
handle_id: self
|
||||
.entity_map
|
||||
.upgrade()
|
||||
@@ -291,8 +291,9 @@ impl Drop for AnyModel {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
if let Some(entity_map) = self.entity_map.upgrade() {
|
||||
// println!("dropped model");
|
||||
entity_map
|
||||
.write()
|
||||
.leak_detector
|
||||
@@ -504,7 +505,7 @@ impl AnyWeakModel {
|
||||
entity_id: self.entity_id,
|
||||
entity_type: self.entity_type,
|
||||
entity_map: self.entity_ref_counts.clone(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
handle_id: self
|
||||
.entity_ref_counts
|
||||
.upgrade()
|
||||
@@ -516,7 +517,7 @@ impl AnyWeakModel {
|
||||
}
|
||||
|
||||
/// Assert that model referenced by this weak handle has been released.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
pub fn assert_released(&self) {
|
||||
self.entity_ref_counts
|
||||
.upgrade()
|
||||
@@ -641,23 +642,23 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
static LEAK_BACKTRACE: std::sync::LazyLock<bool> =
|
||||
std::sync::LazyLock::new(|| std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()));
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct HandleId {
|
||||
id: u64, // id of the handle itself, not the pointed at object
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) struct LeakDetector {
|
||||
next_handle_id: u64,
|
||||
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
impl LeakDetector {
|
||||
#[track_caller]
|
||||
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
|
||||
@@ -666,7 +667,7 @@ impl LeakDetector {
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
handles.insert(
|
||||
handle_id,
|
||||
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
|
||||
LEAK_BACKTRACE.then(|| backtrace::Backtrace::new_unresolved()),
|
||||
);
|
||||
handle_id
|
||||
}
|
||||
@@ -679,7 +680,7 @@ impl LeakDetector {
|
||||
pub fn assert_released(&mut self, entity_id: EntityId) {
|
||||
let handles = self.entity_handles.entry(entity_id).or_default();
|
||||
if !handles.is_empty() {
|
||||
for backtrace in handles.values_mut() {
|
||||
for (_, backtrace) in handles {
|
||||
if let Some(mut backtrace) = backtrace.take() {
|
||||
backtrace.resolve();
|
||||
eprintln!("Leaked handle: {:#?}", backtrace);
|
||||
|
||||
@@ -2,8 +2,8 @@ use smallvec::SmallVec;
|
||||
use taffy::style::{Display, Position};
|
||||
|
||||
use crate::{
|
||||
point, AnyElement, Bounds, Edges, Element, GlobalElementId, IntoElement, LayoutId,
|
||||
ParentElement, Pixels, Point, Size, Style, WindowContext,
|
||||
point, AnyElement, Bounds, Element, GlobalElementId, IntoElement, LayoutId, ParentElement,
|
||||
Pixels, Point, Size, Style, WindowContext,
|
||||
};
|
||||
|
||||
/// The state that the anchored element element uses to track its children.
|
||||
@@ -60,12 +60,6 @@ impl Anchored {
|
||||
self.fit_mode = AnchoredFitMode::SnapToWindow;
|
||||
self
|
||||
}
|
||||
|
||||
/// Snap to window edge and leave some margins.
|
||||
pub fn snap_to_window_with_margin(mut self, edges: impl Into<Edges<Pixels>>) -> Self {
|
||||
self.fit_mode = AnchoredFitMode::SnapToWindowWithMargin(edges.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Anchored {
|
||||
@@ -159,27 +153,22 @@ impl Element for Anchored {
|
||||
}
|
||||
}
|
||||
|
||||
let edges = match self.fit_mode {
|
||||
AnchoredFitMode::SnapToWindowWithMargin(edges) => edges,
|
||||
_ => Edges::default(),
|
||||
};
|
||||
|
||||
// Snap the horizontal edges of the anchored element to the horizontal edges of the window if
|
||||
// its horizontal bounds overflow, aligning to the left if it is wider than the limits.
|
||||
if desired.right() > limits.right() {
|
||||
desired.origin.x -= desired.right() - limits.right() + edges.right;
|
||||
desired.origin.x -= desired.right() - limits.right();
|
||||
}
|
||||
if desired.left() < limits.left() {
|
||||
desired.origin.x = limits.origin.x + edges.left;
|
||||
desired.origin.x = limits.origin.x;
|
||||
}
|
||||
|
||||
// Snap the vertical edges of the anchored element to the vertical edges of the window if
|
||||
// its vertical bounds overflow, aligning to the top if it is taller than the limits.
|
||||
if desired.bottom() > limits.bottom() {
|
||||
desired.origin.y -= desired.bottom() - limits.bottom() + edges.bottom;
|
||||
desired.origin.y -= desired.bottom() - limits.bottom();
|
||||
}
|
||||
if desired.top() < limits.top() {
|
||||
desired.origin.y = limits.origin.y + edges.top;
|
||||
desired.origin.y = limits.origin.y;
|
||||
}
|
||||
|
||||
let offset = desired.origin - bounds.origin;
|
||||
@@ -222,20 +211,18 @@ enum Axis {
|
||||
/// Which algorithm to use when fitting the anchored element to be inside the window.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum AnchoredFitMode {
|
||||
/// Snap the anchored element to the window edge.
|
||||
/// Snap the anchored element to the window edge
|
||||
SnapToWindow,
|
||||
/// Snap to window edge and leave some margins.
|
||||
SnapToWindowWithMargin(Edges<Pixels>),
|
||||
/// Switch which corner anchor this anchored element is attached to.
|
||||
/// Switch which corner anchor this anchored element is attached to
|
||||
SwitchAnchor,
|
||||
}
|
||||
|
||||
/// Which algorithm to use when positioning the anchored element.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum AnchoredPositionMode {
|
||||
/// Position the anchored element relative to the window.
|
||||
/// Position the anchored element relative to the window
|
||||
Window,
|
||||
/// Position the anchored element relative to its parent.
|
||||
/// Position the anchored element relative to its parent
|
||||
Local,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
use core::fmt::Debug;
|
||||
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
|
||||
use refineable::Refineable;
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::{self, PartialOrd},
|
||||
@@ -1837,18 +1836,11 @@ impl Edges<Pixels> {
|
||||
|
||||
impl From<f32> for Edges<Pixels> {
|
||||
fn from(val: f32) -> Self {
|
||||
let val: Pixels = val.into();
|
||||
val.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for Edges<Pixels> {
|
||||
fn from(val: Pixels) -> Self {
|
||||
Edges {
|
||||
top: val,
|
||||
right: val,
|
||||
bottom: val,
|
||||
left: val,
|
||||
top: val.into(),
|
||||
right: val.into(),
|
||||
bottom: val.into(),
|
||||
left: val.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2202,7 +2194,6 @@ impl From<Percentage> for Radians {
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Pixels(pub f32);
|
||||
|
||||
@@ -16,6 +16,7 @@ mod blade;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
mod test;
|
||||
|
||||
mod fps;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
@@ -51,6 +52,7 @@ use strum::EnumIter;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use app_menu::*;
|
||||
pub use fps::*;
|
||||
pub use keystroke::*;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -149,7 +151,6 @@ pub(crate) trait Platform: 'static {
|
||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
||||
fn reveal_path(&self, path: &Path);
|
||||
fn open_with_system(&self, path: &Path);
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
||||
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
||||
@@ -355,7 +356,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
||||
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||
fn draw(&self, scene: &Scene);
|
||||
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>);
|
||||
fn completed_frame(&self) {}
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
@@ -380,6 +381,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
}
|
||||
fn set_client_inset(&self, _inset: Pixels) {}
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs>;
|
||||
fn fps(&self) -> Option<f32>;
|
||||
|
||||
fn update_ime_position(&self, _bounds: Bounds<Pixels>);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use collections::HashMap;
|
||||
use futures::channel::oneshot;
|
||||
#[cfg(target_os = "macos")]
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -336,17 +337,6 @@ impl BladePipelines {
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(&mut self, gpu: &gpu::Context) {
|
||||
gpu.destroy_render_pipeline(&mut self.quads);
|
||||
gpu.destroy_render_pipeline(&mut self.shadows);
|
||||
gpu.destroy_render_pipeline(&mut self.path_rasterization);
|
||||
gpu.destroy_render_pipeline(&mut self.paths);
|
||||
gpu.destroy_render_pipeline(&mut self.underlines);
|
||||
gpu.destroy_render_pipeline(&mut self.mono_sprites);
|
||||
gpu.destroy_render_pipeline(&mut self.poly_sprites);
|
||||
gpu.destroy_render_pipeline(&mut self.surfaces);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BladeSurfaceConfig {
|
||||
@@ -449,7 +439,6 @@ impl BladeRenderer {
|
||||
self.wait_for_gpu();
|
||||
self.surface_config.transparent = transparent;
|
||||
let surface_info = self.gpu.resize(self.surface_config);
|
||||
self.pipelines.destroy(&self.gpu);
|
||||
self.pipelines = BladePipelines::new(&self.gpu, surface_info);
|
||||
self.alpha_mode = surface_info.alpha;
|
||||
}
|
||||
@@ -550,13 +539,16 @@ impl BladeRenderer {
|
||||
pub fn destroy(&mut self) {
|
||||
self.wait_for_gpu();
|
||||
self.atlas.destroy();
|
||||
self.gpu.destroy_sampler(self.atlas_sampler);
|
||||
self.instance_belt.destroy(&self.gpu);
|
||||
self.gpu.destroy_command_encoder(&mut self.command_encoder);
|
||||
self.pipelines.destroy(&self.gpu);
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
scene: &Scene,
|
||||
// Required to compile on macOS, but not currently supported.
|
||||
_on_complete: Option<oneshot::Sender<()>>,
|
||||
) {
|
||||
self.command_encoder.start();
|
||||
self.atlas.before_frame(&mut self.command_encoder);
|
||||
self.rasterize_paths(scene.paths());
|
||||
@@ -785,4 +777,10 @@ impl BladeRenderer {
|
||||
self.wait_for_gpu();
|
||||
self.last_sync_point = Some(sync_point);
|
||||
}
|
||||
|
||||
/// Required to compile on macOS, but not currently supported.
|
||||
#[cfg_attr(any(target_os = "linux", target_os = "windows"), allow(dead_code))]
|
||||
pub fn fps(&self) -> f32 {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
94
crates/gpui/src/platform/fps.rs
Normal file
94
crates/gpui/src/platform/fps.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||
const WINDOW_SIZE: usize = 128;
|
||||
|
||||
/// Represents a rolling FPS (Frames Per Second) counter.
|
||||
///
|
||||
/// This struct provides a lock-free mechanism to measure and calculate FPS
|
||||
/// continuously, updating with every frame. It uses atomic operations to
|
||||
/// ensure thread-safety without the need for locks.
|
||||
pub struct FpsCounter {
|
||||
frame_times: [AtomicU64; WINDOW_SIZE],
|
||||
head: AtomicUsize,
|
||||
tail: AtomicUsize,
|
||||
}
|
||||
|
||||
impl FpsCounter {
|
||||
/// Creates a new `Fps` counter.
|
||||
///
|
||||
/// Returns an `Arc<Fps>` for safe sharing across threads.
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
frame_times: std::array::from_fn(|_| AtomicU64::new(0)),
|
||||
head: AtomicUsize::new(0),
|
||||
tail: AtomicUsize::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
/// Increments the FPS counter with a new frame timestamp.
|
||||
///
|
||||
/// This method updates the internal state to maintain a rolling window
|
||||
/// of frame data for the last second. It uses atomic operations to
|
||||
/// ensure thread-safety.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `timestamp_ns` - The timestamp of the new frame in nanoseconds.
|
||||
pub fn increment(&self, timestamp_ns: u64) {
|
||||
let mut head = self.head.load(Ordering::Relaxed);
|
||||
let mut tail = self.tail.load(Ordering::Relaxed);
|
||||
|
||||
// Add new timestamp
|
||||
self.frame_times[head].store(timestamp_ns, Ordering::Relaxed);
|
||||
// Increment head and wrap around to 0 if it reaches WINDOW_SIZE
|
||||
head = (head + 1) % WINDOW_SIZE;
|
||||
self.head.store(head, Ordering::Relaxed);
|
||||
|
||||
// Remove old timestamps (older than 1 second)
|
||||
while tail != head {
|
||||
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
|
||||
if timestamp_ns.wrapping_sub(oldest) <= NANOS_PER_SEC {
|
||||
break;
|
||||
}
|
||||
// Increment tail and wrap around to 0 if it reaches WINDOW_SIZE
|
||||
tail = (tail + 1) % WINDOW_SIZE;
|
||||
self.tail.store(tail, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates and returns the current FPS.
|
||||
///
|
||||
/// This method computes the FPS based on the frames recorded in the last second.
|
||||
/// It uses atomic loads to ensure thread-safety.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The calculated FPS as a `f32`, or 0.0 if no frames have been recorded.
|
||||
pub fn fps(&self) -> f32 {
|
||||
let head = self.head.load(Ordering::Relaxed);
|
||||
let tail = self.tail.load(Ordering::Relaxed);
|
||||
|
||||
if head == tail {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let newest =
|
||||
self.frame_times[head.wrapping_sub(1) & (WINDOW_SIZE - 1)].load(Ordering::Relaxed);
|
||||
let oldest = self.frame_times[tail].load(Ordering::Relaxed);
|
||||
|
||||
let time_diff = newest.wrapping_sub(oldest) as f32;
|
||||
if time_diff == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let frame_count = if head > tail {
|
||||
head - tail
|
||||
} else {
|
||||
WINDOW_SIZE - tail + head
|
||||
};
|
||||
|
||||
(frame_count as f32 - 1.0) * NANOS_PER_SEC as f32 / time_diff
|
||||
}
|
||||
}
|
||||
@@ -351,19 +351,6 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||
self.reveal_path(path.to_owned());
|
||||
}
|
||||
|
||||
fn open_with_system(&self, path: &Path) {
|
||||
let executor = self.background_executor().clone();
|
||||
let path = path.to_owned();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let _ = std::process::Command::new("xdg-open")
|
||||
.arg(path)
|
||||
.spawn()
|
||||
.expect("Failed to open file with xdg-open");
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.with_common(|common| {
|
||||
common.callbacks.quit = Some(callback);
|
||||
|
||||
@@ -476,8 +476,7 @@ impl WaylandClient {
|
||||
.as_ref()
|
||||
.map(|primary_selection_manager| primary_selection_manager.get_device(&seat, &qh, ()));
|
||||
|
||||
// FIXME: Determine the scaling factor dynamically by the compositor
|
||||
let mut cursor = Cursor::new(&conn, &globals, 24, 2);
|
||||
let mut cursor = Cursor::new(&conn, &globals, 24);
|
||||
|
||||
handle
|
||||
.insert_source(XDPEventSource::new(&common.background_executor), {
|
||||
|
||||
@@ -11,7 +11,6 @@ pub(crate) struct Cursor {
|
||||
theme_name: Option<String>,
|
||||
surface: WlSurface,
|
||||
size: u32,
|
||||
scale: u32,
|
||||
shm: WlShm,
|
||||
connection: Connection,
|
||||
}
|
||||
@@ -24,7 +23,7 @@ impl Drop for Cursor {
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn new(connection: &Connection, globals: &Globals, size: u32, scale: u32) -> Self {
|
||||
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
|
||||
Self {
|
||||
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
|
||||
theme_name: None,
|
||||
@@ -32,7 +31,6 @@ impl Cursor {
|
||||
shm: globals.shm.clone(),
|
||||
connection: connection.clone(),
|
||||
size,
|
||||
scale,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +38,14 @@ impl Cursor {
|
||||
if let Some(size) = size {
|
||||
self.size = size;
|
||||
}
|
||||
if let Some(theme) = CursorTheme::load_from_name(
|
||||
&self.connection,
|
||||
self.shm.clone(),
|
||||
theme_name,
|
||||
self.size * self.scale,
|
||||
)
|
||||
.log_err()
|
||||
if let Some(theme) =
|
||||
CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
|
||||
.log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = Some(theme_name.to_string());
|
||||
} else if let Some(theme) =
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), self.size * self.scale).log_err()
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = None;
|
||||
@@ -97,22 +91,9 @@ impl Cursor {
|
||||
let (width, height) = buffer.dimensions();
|
||||
let (hot_x, hot_y) = buffer.hotspot();
|
||||
|
||||
let scaled_width = width / self.scale;
|
||||
let scaled_height = height / self.scale;
|
||||
let scaled_hot_x = hot_x / self.scale;
|
||||
let scaled_hot_y = hot_y / self.scale;
|
||||
|
||||
self.surface.set_buffer_scale(self.scale as i32);
|
||||
|
||||
wl_pointer.set_cursor(
|
||||
serial_id,
|
||||
Some(&self.surface),
|
||||
scaled_hot_x as i32,
|
||||
scaled_hot_y as i32,
|
||||
);
|
||||
wl_pointer.set_cursor(serial_id, Some(&self.surface), hot_x as i32, hot_y as i32);
|
||||
self.surface.attach(Some(&buffer), 0, 0);
|
||||
self.surface
|
||||
.damage(0, 0, scaled_width as i32, scaled_height as i32);
|
||||
self.surface.damage(0, 0, width as i32, height as i32);
|
||||
self.surface.commit();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use blade_graphics as gpu;
|
||||
use collections::HashMap;
|
||||
use futures::channel::oneshot::Receiver;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use raw_window_handle as rwh;
|
||||
use wayland_backend::client::ObjectId;
|
||||
@@ -831,7 +831,7 @@ impl PlatformWindow for WaylandWindow {
|
||||
_msg: &str,
|
||||
_detail: Option<&str>,
|
||||
_answers: &[&str],
|
||||
) -> Option<Receiver<usize>> {
|
||||
) -> Option<oneshot::Receiver<usize>> {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -938,9 +938,9 @@ impl PlatformWindow for WaylandWindow {
|
||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||
let mut state = self.borrow_mut();
|
||||
state.renderer.draw(scene);
|
||||
state.renderer.draw(scene, on_complete);
|
||||
}
|
||||
|
||||
fn completed_frame(&self) {
|
||||
@@ -1018,6 +1018,10 @@ impl PlatformWindow for WaylandWindow {
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
self.borrow().renderer.gpu_specs().into()
|
||||
}
|
||||
|
||||
fn fps(&self) -> Option<f32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn update_window(mut state: RefMut<WaylandWindowState>) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use core::str;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::ops::Deref;
|
||||
@@ -10,8 +9,6 @@ use calloop::generic::{FdWrapper, Generic};
|
||||
use calloop::{EventLoop, LoopHandle, RegistrationToken};
|
||||
|
||||
use collections::HashMap;
|
||||
use http_client::Url;
|
||||
use smallvec::SmallVec;
|
||||
use util::ResultExt;
|
||||
|
||||
use x11rb::connection::{Connection, RequestConnection};
|
||||
@@ -20,13 +17,9 @@ use x11rb::errors::ConnectionError;
|
||||
use x11rb::protocol::randr::ConnectionExt as _;
|
||||
use x11rb::protocol::xinput::ConnectionExt;
|
||||
use x11rb::protocol::xkb::ConnectionExt as _;
|
||||
use x11rb::protocol::xproto::{
|
||||
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent, ConnectionExt as _,
|
||||
EventMask, KeyPressEvent,
|
||||
};
|
||||
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
|
||||
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
|
||||
use x11rb::resource_manager::Database;
|
||||
use x11rb::wrapper::ConnectionExt as _;
|
||||
use x11rb::xcb_ffi::XCBConnection;
|
||||
use xim::{x11rb::X11rbClient, Client};
|
||||
use xim::{AttributeName, InputStyle};
|
||||
@@ -37,8 +30,8 @@ use crate::platform::linux::LinuxClient;
|
||||
use crate::platform::{LinuxCommon, PlatformWindow};
|
||||
use crate::{
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
|
||||
DisplayId, FileDropEvent, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform,
|
||||
PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
|
||||
PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
};
|
||||
|
||||
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
|
||||
@@ -108,14 +101,6 @@ struct XKBStateNotiy {
|
||||
locked_layout: LayoutIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Xdnd {
|
||||
other_window: xproto::Window,
|
||||
drag_type: u32,
|
||||
retrieved: bool,
|
||||
position: Point<Pixels>,
|
||||
}
|
||||
|
||||
pub struct X11ClientState {
|
||||
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
|
||||
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
|
||||
@@ -157,7 +142,6 @@ pub struct X11ClientState {
|
||||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: x11_clipboard::Clipboard,
|
||||
pub(crate) clipboard_item: Option<ClipboardItem>,
|
||||
pub(crate) xdnd_state: Xdnd,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -439,7 +423,6 @@ impl X11Client {
|
||||
|
||||
clipboard,
|
||||
clipboard_item: None,
|
||||
xdnd_state: Xdnd::default(),
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -628,7 +611,7 @@ impl X11Client {
|
||||
match event {
|
||||
Event::ClientMessage(event) => {
|
||||
let window = self.get_window(event.window)?;
|
||||
let [atom, arg1, arg2, arg3, arg4] = event.data.as_data32();
|
||||
let [atom, _arg1, arg2, arg3, _arg4] = event.data.as_data32();
|
||||
let mut state = self.0.borrow_mut();
|
||||
|
||||
if atom == state.atoms.WM_DELETE_WINDOW {
|
||||
@@ -644,106 +627,6 @@ impl X11Client {
|
||||
hi: arg3 as i32,
|
||||
})
|
||||
}
|
||||
|
||||
if event.type_ == state.atoms.XdndEnter {
|
||||
state.xdnd_state.other_window = atom;
|
||||
if (arg1 & 0x1) == 0x1 {
|
||||
state.xdnd_state.drag_type = xdnd_get_supported_atom(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
state.xdnd_state.other_window,
|
||||
);
|
||||
} else {
|
||||
if let Some(atom) = [arg2, arg3, arg4]
|
||||
.into_iter()
|
||||
.find(|atom| xdnd_is_atom_supported(*atom, &state.atoms))
|
||||
{
|
||||
state.xdnd_state.drag_type = atom;
|
||||
}
|
||||
}
|
||||
} else if event.type_ == state.atoms.XdndLeave {
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Exited {}));
|
||||
state.xdnd_state = Xdnd::default();
|
||||
} else if event.type_ == state.atoms.XdndPosition {
|
||||
if let Ok(pos) = state
|
||||
.xcb_connection
|
||||
.query_pointer(event.window)
|
||||
.unwrap()
|
||||
.reply()
|
||||
{
|
||||
state.xdnd_state.position =
|
||||
Point::new(Pixels(pos.win_x as f32), Pixels(pos.win_y as f32));
|
||||
}
|
||||
if !state.xdnd_state.retrieved {
|
||||
state
|
||||
.xcb_connection
|
||||
.convert_selection(
|
||||
event.window,
|
||||
state.atoms.XdndSelection,
|
||||
state.xdnd_state.drag_type,
|
||||
state.atoms.XDND_DATA,
|
||||
arg3,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
xdnd_send_status(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
event.window,
|
||||
state.xdnd_state.other_window,
|
||||
arg4,
|
||||
);
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Pending {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
} else if event.type_ == state.atoms.XdndDrop {
|
||||
xdnd_send_finished(
|
||||
&state.xcb_connection,
|
||||
&state.atoms,
|
||||
event.window,
|
||||
state.xdnd_state.other_window,
|
||||
);
|
||||
window.handle_input(PlatformInput::FileDrop(FileDropEvent::Submit {
|
||||
position: state.xdnd_state.position,
|
||||
}));
|
||||
state.xdnd_state = Xdnd::default();
|
||||
}
|
||||
}
|
||||
Event::SelectionNotify(event) => {
|
||||
let window = self.get_window(event.requestor)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
let property = state.xcb_connection.get_property(
|
||||
false,
|
||||
event.requestor,
|
||||
state.atoms.XDND_DATA,
|
||||
AtomEnum::ANY,
|
||||
0,
|
||||
1024,
|
||||
);
|
||||
if property.as_ref().log_err().is_none() {
|
||||
return Some(());
|
||||
}
|
||||
if let Ok(reply) = property.unwrap().reply() {
|
||||
match str::from_utf8(&reply.value) {
|
||||
Ok(file_list) => {
|
||||
let paths: SmallVec<[_; 2]> = file_list
|
||||
.lines()
|
||||
.filter_map(|path| Url::parse(path).log_err())
|
||||
.filter_map(|url| url.to_file_path().log_err())
|
||||
.collect();
|
||||
let input = PlatformInput::FileDrop(FileDropEvent::Entered {
|
||||
position: state.xdnd_state.position,
|
||||
paths: crate::ExternalPaths(paths),
|
||||
});
|
||||
window.handle_input(input);
|
||||
state.xdnd_state.retrieved = true;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::ConfigureNotify(event) => {
|
||||
let bounds = Bounds {
|
||||
@@ -1296,16 +1179,6 @@ impl LinuxClient for X11Client {
|
||||
state.scale_factor,
|
||||
state.common.appearance,
|
||||
)?;
|
||||
state
|
||||
.xcb_connection
|
||||
.change_property32(
|
||||
xproto::PropMode::REPLACE,
|
||||
x_window,
|
||||
state.atoms.XdndAware,
|
||||
state.atoms.XA_ATOM,
|
||||
&[5],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let screen_resources = state
|
||||
.xcb_connection
|
||||
@@ -1667,78 +1540,3 @@ fn check_gtk_frame_extents_supported(
|
||||
|
||||
supported_atoms.contains(&atoms._GTK_FRAME_EXTENTS)
|
||||
}
|
||||
|
||||
fn xdnd_is_atom_supported(atom: u32, atoms: &XcbAtoms) -> bool {
|
||||
return atom == atoms.TEXT
|
||||
|| atom == atoms.STRING
|
||||
|| atom == atoms.UTF8_STRING
|
||||
|| atom == atoms.TEXT_PLAIN
|
||||
|| atom == atoms.TEXT_PLAIN_UTF8
|
||||
|| atom == atoms.TextUriList;
|
||||
}
|
||||
|
||||
fn xdnd_get_supported_atom(
|
||||
xcb_connection: &XCBConnection,
|
||||
supported_atoms: &XcbAtoms,
|
||||
target: xproto::Window,
|
||||
) -> u32 {
|
||||
let property = xcb_connection
|
||||
.get_property(
|
||||
false,
|
||||
target,
|
||||
supported_atoms.XdndTypeList,
|
||||
AtomEnum::ANY,
|
||||
0,
|
||||
1024,
|
||||
)
|
||||
.unwrap();
|
||||
if let Ok(reply) = property.reply() {
|
||||
if let Some(atoms) = reply.value32() {
|
||||
for atom in atoms {
|
||||
if xdnd_is_atom_supported(atom, &supported_atoms) {
|
||||
return atom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn xdnd_send_finished(
|
||||
xcb_connection: &XCBConnection,
|
||||
atoms: &XcbAtoms,
|
||||
source: xproto::Window,
|
||||
target: xproto::Window,
|
||||
) {
|
||||
let message = ClientMessageEvent {
|
||||
format: 32,
|
||||
window: target,
|
||||
type_: atoms.XdndFinished,
|
||||
data: ClientMessageData::from([source, 1, atoms.XdndActionCopy, 0, 0]),
|
||||
sequence: 0,
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
};
|
||||
xcb_connection
|
||||
.send_event(false, target, EventMask::default(), message)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn xdnd_send_status(
|
||||
xcb_connection: &XCBConnection,
|
||||
atoms: &XcbAtoms,
|
||||
source: xproto::Window,
|
||||
target: xproto::Window,
|
||||
action: u32,
|
||||
) {
|
||||
let message = ClientMessageEvent {
|
||||
format: 32,
|
||||
window: target,
|
||||
type_: atoms.XdndStatus,
|
||||
data: ClientMessageData::from([source, 1, 0, 0, action]),
|
||||
sequence: 0,
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
};
|
||||
xcb_connection
|
||||
.send_event(false, target, EventMask::default(), message)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::{
|
||||
platform::blade::{BladeRenderer, BladeSurfaceConfig},
|
||||
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GPUSpecs,
|
||||
@@ -9,7 +7,9 @@ use crate::{
|
||||
X11ClientStatePtr,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use blade_graphics as gpu;
|
||||
use futures::channel::oneshot;
|
||||
use raw_window_handle as rwh;
|
||||
use util::{maybe, ResultExt};
|
||||
use x11rb::{
|
||||
@@ -32,24 +32,7 @@ use std::{
|
||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
x11rb::atom_manager! {
|
||||
pub XcbAtoms: AtomsCookie {
|
||||
XA_ATOM,
|
||||
XdndAware,
|
||||
XdndStatus,
|
||||
XdndEnter,
|
||||
XdndLeave,
|
||||
XdndPosition,
|
||||
XdndSelection,
|
||||
XdndDrop,
|
||||
XdndFinished,
|
||||
XdndTypeList,
|
||||
XdndActionCopy,
|
||||
TextUriList: b"text/uri-list",
|
||||
UTF8_STRING,
|
||||
TEXT,
|
||||
STRING,
|
||||
TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
|
||||
TEXT_PLAIN: b"text/plain",
|
||||
XDND_DATA,
|
||||
WM_PROTOCOLS,
|
||||
WM_DELETE_WINDOW,
|
||||
WM_CHANGE_STATE,
|
||||
@@ -1227,9 +1210,10 @@ impl PlatformWindow for X11Window {
|
||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
// TODO: on_complete not yet supported for X11 windows
|
||||
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||
let mut inner = self.0.state.borrow_mut();
|
||||
inner.renderer.draw(scene);
|
||||
inner.renderer.draw(scene, on_complete);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
@@ -1422,4 +1406,8 @@ impl PlatformWindow for X11Window {
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
self.0.state.borrow().renderer.gpu_specs().into()
|
||||
}
|
||||
|
||||
fn fps(&self) -> Option<f32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::metal_atlas::MetalAtlas;
|
||||
use crate::{
|
||||
point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
|
||||
Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
|
||||
FpsCounter, Hsla, MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite,
|
||||
PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
@@ -14,6 +14,7 @@ use cocoa::{
|
||||
use collections::HashMap;
|
||||
use core_foundation::base::TCFType;
|
||||
use foreign_types::ForeignType;
|
||||
use futures::channel::oneshot;
|
||||
use media::core_video::CVMetalTextureCache;
|
||||
use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use objc::{self, msg_send, sel, sel_impl};
|
||||
@@ -105,6 +106,7 @@ pub(crate) struct MetalRenderer {
|
||||
instance_buffer_pool: Arc<Mutex<InstanceBufferPool>>,
|
||||
sprite_atlas: Arc<MetalAtlas>,
|
||||
core_video_texture_cache: CVMetalTextureCache,
|
||||
fps_counter: Arc<FpsCounter>,
|
||||
}
|
||||
|
||||
impl MetalRenderer {
|
||||
@@ -250,6 +252,7 @@ impl MetalRenderer {
|
||||
instance_buffer_pool,
|
||||
sprite_atlas,
|
||||
core_video_texture_cache,
|
||||
fps_counter: FpsCounter::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +295,8 @@ impl MetalRenderer {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
pub fn draw(&mut self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||
let on_complete = Arc::new(Mutex::new(on_complete));
|
||||
let layer = self.layer.clone();
|
||||
let viewport_size = layer.drawable_size();
|
||||
let viewport_size: Size<DevicePixels> = size(
|
||||
@@ -319,13 +323,24 @@ impl MetalRenderer {
|
||||
Ok(command_buffer) => {
|
||||
let instance_buffer_pool = self.instance_buffer_pool.clone();
|
||||
let instance_buffer = Cell::new(Some(instance_buffer));
|
||||
let block = ConcreteBlock::new(move |_| {
|
||||
if let Some(instance_buffer) = instance_buffer.take() {
|
||||
instance_buffer_pool.lock().release(instance_buffer);
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
command_buffer.add_completed_handler(&block);
|
||||
let device = self.device.clone();
|
||||
let fps_counter = self.fps_counter.clone();
|
||||
let completed_handler =
|
||||
ConcreteBlock::new(move |_: &metal::CommandBufferRef| {
|
||||
let mut cpu_timestamp = 0;
|
||||
let mut gpu_timestamp = 0;
|
||||
device.sample_timestamps(&mut cpu_timestamp, &mut gpu_timestamp);
|
||||
|
||||
fps_counter.increment(gpu_timestamp);
|
||||
if let Some(on_complete) = on_complete.lock().take() {
|
||||
on_complete.send(()).ok();
|
||||
}
|
||||
if let Some(instance_buffer) = instance_buffer.take() {
|
||||
instance_buffer_pool.lock().release(instance_buffer);
|
||||
}
|
||||
});
|
||||
let completed_handler = completed_handler.copy();
|
||||
command_buffer.add_completed_handler(&completed_handler);
|
||||
|
||||
if self.presents_with_transaction {
|
||||
command_buffer.commit();
|
||||
@@ -1117,6 +1132,10 @@ impl MetalRenderer {
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn fps(&self) -> f32 {
|
||||
self.fps_counter.fps()
|
||||
}
|
||||
}
|
||||
|
||||
fn build_pipeline_state(
|
||||
|
||||
@@ -718,20 +718,6 @@ impl Platform for MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
fn open_with_system(&self, path: &Path) {
|
||||
let path = path.to_path_buf();
|
||||
self.0
|
||||
.lock()
|
||||
.background_executor
|
||||
.spawn(async move {
|
||||
std::process::Command::new("open")
|
||||
.arg(path)
|
||||
.spawn()
|
||||
.expect("Failed to open file");
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.0.lock().quit = Some(callback);
|
||||
}
|
||||
|
||||
@@ -783,14 +783,14 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.as_ref().lock().bounds()
|
||||
}
|
||||
|
||||
fn window_bounds(&self) -> WindowBounds {
|
||||
self.0.as_ref().lock().window_bounds()
|
||||
}
|
||||
|
||||
fn is_maximized(&self) -> bool {
|
||||
self.0.as_ref().lock().is_maximized()
|
||||
}
|
||||
|
||||
fn window_bounds(&self) -> WindowBounds {
|
||||
self.0.as_ref().lock().window_bounds()
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.0.as_ref().lock().content_size()
|
||||
}
|
||||
@@ -974,8 +974,6 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_app_id(&mut self, _app_id: &str) {}
|
||||
|
||||
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
|
||||
let mut this = self.0.as_ref().lock();
|
||||
this.renderer
|
||||
@@ -1006,30 +1004,6 @@ impl PlatformWindow for MacWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, edited: bool) {
|
||||
unsafe {
|
||||
let window = self.0.lock().native_window;
|
||||
msg_send![window, setDocumentEdited: edited as BOOL]
|
||||
}
|
||||
|
||||
// Changing the document edited state resets the traffic light position,
|
||||
// so we have to move it again.
|
||||
self.0.lock().move_traffic_light();
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let app = NSApplication::sharedApplication(nil);
|
||||
let _: () = msg_send![app, orderFrontCharacterPalette: window];
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
let window = self.0.lock().native_window;
|
||||
unsafe {
|
||||
@@ -1106,15 +1080,41 @@ impl PlatformWindow for MacWindow {
|
||||
self.0.lock().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &crate::Scene) {
|
||||
fn draw(&self, scene: &crate::Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||
let mut this = self.0.lock();
|
||||
this.renderer.draw(scene);
|
||||
this.renderer.draw(scene, on_complete);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
self.0.lock().renderer.sprite_atlas().clone()
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, edited: bool) {
|
||||
unsafe {
|
||||
let window = self.0.lock().native_window;
|
||||
msg_send![window, setDocumentEdited: edited as BOOL]
|
||||
}
|
||||
|
||||
// Changing the document edited state resets the traffic light position,
|
||||
// so we have to move it again.
|
||||
self.0.lock().move_traffic_light();
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
let this = self.0.lock();
|
||||
let window = this.native_window;
|
||||
this.executor
|
||||
.spawn(async move {
|
||||
unsafe {
|
||||
let app = NSApplication::sharedApplication(nil);
|
||||
let _: () = msg_send![app, orderFrontCharacterPalette: window];
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn set_app_id(&mut self, _app_id: &str) {}
|
||||
|
||||
fn gpu_specs(&self) -> Option<crate::GPUSpecs> {
|
||||
None
|
||||
}
|
||||
@@ -1125,6 +1125,10 @@ impl PlatformWindow for MacWindow {
|
||||
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
|
||||
}
|
||||
}
|
||||
|
||||
fn fps(&self) -> Option<f32> {
|
||||
Some(self.0.lock().renderer.fps())
|
||||
}
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for MacWindow {
|
||||
|
||||
@@ -318,10 +318,6 @@ impl Platform for TestPlatform {
|
||||
fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn open_with_system(&self, _path: &Path) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -251,7 +251,12 @@ impl PlatformWindow for TestWindow {
|
||||
|
||||
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
||||
|
||||
fn draw(&self, _scene: &crate::Scene) {}
|
||||
fn draw(
|
||||
&self,
|
||||
_scene: &crate::Scene,
|
||||
_on_complete: Option<futures::channel::oneshot::Sender<()>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
||||
self.0.lock().sprite_atlas.clone()
|
||||
@@ -279,6 +284,10 @@ impl PlatformWindow for TestWindow {
|
||||
fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
None
|
||||
}
|
||||
|
||||
fn fps(&self) -> Option<f32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestAtlasState {
|
||||
|
||||
@@ -400,19 +400,6 @@ impl Platform for WindowsPlatform {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn open_with_system(&self, path: &Path) {
|
||||
let executor = self.background_executor().clone();
|
||||
let path = path.to_owned();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let _ = std::process::Command::new("cmd")
|
||||
.args(&["/c", "start", "", path.to_str().expect("path to string")])
|
||||
.spawn()
|
||||
.expect("Failed to open file");
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn on_quit(&self, callback: Box<dyn FnMut()>) {
|
||||
self.state.borrow_mut().callbacks.quit = Some(callback);
|
||||
}
|
||||
|
||||
@@ -660,8 +660,8 @@ impl PlatformWindow for WindowsWindow {
|
||||
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
|
||||
}
|
||||
|
||||
fn draw(&self, scene: &Scene) {
|
||||
self.0.state.borrow_mut().renderer.draw(scene)
|
||||
fn draw(&self, scene: &Scene, on_complete: Option<oneshot::Sender<()>>) {
|
||||
self.0.state.borrow_mut().renderer.draw(scene, on_complete)
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
@@ -679,6 +679,10 @@ impl PlatformWindow for WindowsWindow {
|
||||
fn update_ime_position(&self, _bounds: Bounds<Pixels>) {
|
||||
// todo(windows)
|
||||
}
|
||||
|
||||
fn fps(&self) -> Option<f32> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[implement(IDropTarget)]
|
||||
|
||||
@@ -153,7 +153,7 @@ impl LineWrapper {
|
||||
matches!(c, '\u{0400}'..='\u{04FF}') ||
|
||||
// Some other known special characters that should be treated as word characters,
|
||||
// e.g. `a-b`, `var_name`, `I'm`, '@mention`, `#hashtag`, `100%`, `3.1415`, `2^3`, `a~b`, etc.
|
||||
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~' | ',') ||
|
||||
matches!(c, '-' | '_' | '.' | '\'' | '$' | '%' | '@' | '#' | '^' | '~') ||
|
||||
// Characters that used in URL, e.g. `https://github.com/zed-industries/zed?a=1&b=2` for better wrapping a long URL.
|
||||
matches!(c, '/' | ':' | '?' | '&' | '=') ||
|
||||
// `⋯` character is special used in Zed, to keep this at the end of the line.
|
||||
|
||||
@@ -11,9 +11,9 @@ use crate::{
|
||||
PromptLevel, Quad, Render, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams,
|
||||
Replay, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
|
||||
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
|
||||
TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
|
||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations,
|
||||
WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
|
||||
TimeToFirstWindowDraw, TransformationMatrix, Underline, UnderlineStyle, View, VisualContext,
|
||||
WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControls,
|
||||
WindowDecorations, WindowOptions, WindowParams, WindowTextSystem, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
@@ -545,6 +545,8 @@ pub struct Window {
|
||||
hovered: Rc<Cell<bool>>,
|
||||
pub(crate) dirty: Rc<Cell<bool>>,
|
||||
pub(crate) needs_present: Rc<Cell<bool>>,
|
||||
/// We assign this to be notified when the platform graphics backend fires the next completion callback for drawing the window.
|
||||
present_completed: RefCell<Option<oneshot::Sender<()>>>,
|
||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||
pub(crate) refreshing: bool,
|
||||
pub(crate) draw_phase: DrawPhase,
|
||||
@@ -822,6 +824,7 @@ impl Window {
|
||||
hovered,
|
||||
dirty,
|
||||
needs_present,
|
||||
present_completed: RefCell::default(),
|
||||
last_input_timestamp,
|
||||
refreshing: false,
|
||||
draw_phase: DrawPhase::None,
|
||||
@@ -923,7 +926,15 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
/// Close this window.
|
||||
pub fn remove_window(&mut self) {
|
||||
println!("remove window!");
|
||||
self.window.removed = true;
|
||||
|
||||
// self.spawn(|cx| {
|
||||
// for _ in 0..10 {
|
||||
// cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||
// AsyncAppContext::update(&mut cx, |cx| cx.update(|cx| {})).ok();
|
||||
// })
|
||||
// .detach()
|
||||
}
|
||||
|
||||
/// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus
|
||||
@@ -1491,13 +1502,29 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.refreshing = false;
|
||||
self.window.draw_phase = DrawPhase::None;
|
||||
self.window.needs_present.set(true);
|
||||
|
||||
if let Some(TimeToFirstWindowDraw::Pending(start)) = self.app.time_to_first_window_draw {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
*self.window.present_completed.borrow_mut() = Some(tx);
|
||||
self.spawn(|mut cx| async move {
|
||||
rx.await.ok();
|
||||
cx.update(|cx| {
|
||||
let duration = start.elapsed();
|
||||
cx.time_to_first_window_draw = Some(TimeToFirstWindowDraw::Done(duration));
|
||||
log::info!("time to first window draw: {:?}", duration);
|
||||
cx.push_effect(Effect::Refresh);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn present(&self) {
|
||||
let on_complete = self.window.present_completed.take();
|
||||
self.window
|
||||
.platform_window
|
||||
.draw(&self.window.rendered_frame.scene);
|
||||
.draw(&self.window.rendered_frame.scene, on_complete);
|
||||
self.window.needs_present.set(false);
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
@@ -3779,6 +3806,12 @@ impl<'a> WindowContext<'a> {
|
||||
pub fn gpu_specs(&self) -> Option<GPUSpecs> {
|
||||
self.window.platform_window.gpu_specs()
|
||||
}
|
||||
|
||||
/// Get the current FPS (frames per second) of the window.
|
||||
/// This is only supported on macOS currently.
|
||||
pub fn fps(&self) -> Option<f32> {
|
||||
self.window.platform_window.fps()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -27,7 +27,6 @@ use gpui::{
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::Value;
|
||||
use settings::WorktreeId;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smallvec::SmallVec;
|
||||
use smol::future::yield_now;
|
||||
@@ -362,7 +361,7 @@ pub trait File: Send + Sync {
|
||||
/// Returns the id of the worktree to which this file belongs.
|
||||
///
|
||||
/// This is needed for looking up project-specific settings.
|
||||
fn worktree_id(&self, cx: &AppContext) -> WorktreeId;
|
||||
fn worktree_id(&self) -> usize;
|
||||
|
||||
/// Returns whether the file has been deleted.
|
||||
fn is_deleted(&self) -> bool;
|
||||
@@ -4173,8 +4172,8 @@ impl File for TestFile {
|
||||
self.path().file_name().unwrap_or(self.root_name.as_ref())
|
||||
}
|
||||
|
||||
fn worktree_id(&self, _: &AppContext) -> WorktreeId {
|
||||
WorktreeId::from_usize(0)
|
||||
fn worktree_id(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
fn is_deleted(&self) -> bool {
|
||||
@@ -4302,7 +4301,7 @@ pub fn trailing_whitespace_ranges(rope: &Rope) -> Vec<Range<usize>> {
|
||||
let mut prev_line_trailing_whitespace_range = 0..0;
|
||||
for (i, line) in chunk.split('\n').enumerate() {
|
||||
let line_end_offset = offset + line.len();
|
||||
let trimmed_line_len = line.trim_end_matches([' ', '\t']).len();
|
||||
let trimmed_line_len = line.trim_end_matches(|c| matches!(c, ' ' | '\t')).len();
|
||||
let mut trailing_whitespace_range = (offset + trimmed_line_len)..line_end_offset;
|
||||
|
||||
if i == 0 && trimmed_line_len == 0 {
|
||||
|
||||
@@ -72,7 +72,7 @@ fn test_select_language(cx: &mut AppContext) {
|
||||
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: LanguageName::new("Rust"),
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
@@ -83,7 +83,7 @@ fn test_select_language(cx: &mut AppContext) {
|
||||
)));
|
||||
registry.add(Arc::new(Language::new(
|
||||
LanguageConfig {
|
||||
name: LanguageName::new("Make"),
|
||||
name: "Make".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
|
||||
..Default::default()
|
||||
@@ -97,13 +97,15 @@ fn test_select_language(cx: &mut AppContext) {
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("src/lib.rs"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Rust".into())
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("src/lib.mk"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Make".into())
|
||||
);
|
||||
|
||||
@@ -111,7 +113,8 @@ fn test_select_language(cx: &mut AppContext) {
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("src/Makefile"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
Some("Make".into())
|
||||
);
|
||||
|
||||
@@ -119,19 +122,22 @@ fn test_select_language(cx: &mut AppContext) {
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("zed/cars"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("zed/a.cars"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
registry
|
||||
.language_for_file(&file("zed/sumk"), None, cx)
|
||||
.map(|l| l.name()),
|
||||
.now_or_never()
|
||||
.and_then(|l| Some(l.ok()?.name())),
|
||||
None
|
||||
);
|
||||
}
|
||||
@@ -152,22 +158,23 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert!(cx
|
||||
.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
|
||||
.is_none());
|
||||
assert!(cx
|
||||
.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
|
||||
.is_none());
|
||||
|
||||
cx.read(|cx| languages.language_for_file(&file("the/script"), None, cx))
|
||||
.await
|
||||
.unwrap_err();
|
||||
cx.read(|cx| languages.language_for_file(&file("the/script"), Some(&"nothing".into()), cx))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
cx.read(|cx| languages.language_for_file(
|
||||
&file("the/script"),
|
||||
Some(&"#!/bin/env node".into()),
|
||||
cx
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
.name(),
|
||||
"JavaScript".into()
|
||||
.name()
|
||||
.as_ref(),
|
||||
"JavaScript"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -235,16 +242,19 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
|
||||
|
||||
let language = cx
|
||||
.read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(language.name(), "TypeScript".into());
|
||||
assert_eq!(language.name().as_ref(), "TypeScript");
|
||||
let language = cx
|
||||
.read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(language.name(), "C++".into());
|
||||
assert_eq!(language.name().as_ref(), "C++");
|
||||
let language = cx
|
||||
.read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(language.name(), "Dockerfile".into());
|
||||
assert_eq!(language.name().as_ref(), "Dockerfile");
|
||||
}
|
||||
|
||||
fn file(path: &str) -> Arc<dyn File> {
|
||||
@@ -2235,10 +2245,10 @@ fn test_language_at_with_hidden_languages(cx: &mut AppContext) {
|
||||
|
||||
for point in [Point::new(0, 4), Point::new(0, 16)] {
|
||||
let config = snapshot.language_scope_at(point).unwrap();
|
||||
assert_eq!(config.language_name(), "Markdown".into());
|
||||
assert_eq!(config.language_name().as_ref(), "Markdown");
|
||||
|
||||
let language = snapshot.language_at(point).unwrap();
|
||||
assert_eq!(language.name().0.as_ref(), "Markdown");
|
||||
assert_eq!(language.name().as_ref(), "Markdown");
|
||||
}
|
||||
|
||||
buffer
|
||||
@@ -2747,7 +2757,7 @@ fn ruby_lang() -> Language {
|
||||
fn html_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: LanguageName::new("HTML"),
|
||||
name: "HTML".into(),
|
||||
block_comment: Some(("<!--".into(), "-->".into())),
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
@@ -28,7 +28,6 @@ use futures::Future;
|
||||
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
|
||||
pub use highlight_map::HighlightMap;
|
||||
use http_client::HttpClient;
|
||||
pub use language_registry::LanguageName;
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
@@ -39,7 +38,6 @@ use schemars::{
|
||||
};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::Value;
|
||||
use settings::WorktreeId;
|
||||
use smol::future::FutureExt as _;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{
|
||||
@@ -68,8 +66,8 @@ pub use buffer::Operation;
|
||||
pub use buffer::*;
|
||||
pub use diagnostic_set::DiagnosticEntry;
|
||||
pub use language_registry::{
|
||||
AvailableLanguage, LanguageNotFound, LanguageQueries, LanguageRegistry,
|
||||
LanguageServerBinaryStatus, PendingLanguageServer, QUERY_FILENAME_PREFIXES,
|
||||
LanguageNotFound, LanguageQueries, LanguageRegistry, LanguageServerBinaryStatus,
|
||||
PendingLanguageServer, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
pub use lsp::LanguageServerId;
|
||||
pub use outline::*;
|
||||
@@ -141,12 +139,6 @@ pub trait ToLspPosition {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
|
||||
pub struct LanguageServerName(pub Arc<str>);
|
||||
|
||||
impl LanguageServerName {
|
||||
pub fn from_proto(s: String) -> Self {
|
||||
Self(Arc::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Location {
|
||||
pub buffer: Model<Buffer>,
|
||||
@@ -202,12 +194,9 @@ impl CachedLspAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Arc<str> {
|
||||
self.adapter.name().0.clone()
|
||||
}
|
||||
|
||||
pub async fn get_language_server_command(
|
||||
self: Arc<Self>,
|
||||
language: Arc<Language>,
|
||||
container_dir: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
@@ -215,10 +204,18 @@ impl CachedLspAdapter {
|
||||
let cached_binary = self.cached_binary.lock().await;
|
||||
self.adapter
|
||||
.clone()
|
||||
.get_language_server_command(container_dir, delegate, cached_binary, cx)
|
||||
.get_language_server_command(language, container_dir, delegate, cached_binary, cx)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn will_start_server(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
self.adapter.will_start_server(delegate, cx)
|
||||
}
|
||||
|
||||
pub fn can_be_reinstalled(&self) -> bool {
|
||||
self.adapter.can_be_reinstalled()
|
||||
}
|
||||
@@ -264,11 +261,11 @@ impl CachedLspAdapter {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn language_id(&self, language_name: &LanguageName) -> String {
|
||||
pub fn language_id(&self, language: &Language) -> String {
|
||||
self.language_ids
|
||||
.get(language_name.0.as_ref())
|
||||
.get(language.name().as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| language_name.lsp_id())
|
||||
.unwrap_or_else(|| language.lsp_id())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -283,7 +280,7 @@ impl CachedLspAdapter {
|
||||
pub trait LspAdapterDelegate: Send + Sync {
|
||||
fn show_notification(&self, message: &str, cx: &mut AppContext);
|
||||
fn http_client(&self) -> Arc<dyn HttpClient>;
|
||||
fn worktree_id(&self) -> WorktreeId;
|
||||
fn worktree_id(&self) -> u64;
|
||||
fn worktree_root_path(&self) -> &Path;
|
||||
fn update_status(&self, language: LanguageServerName, status: LanguageServerBinaryStatus);
|
||||
|
||||
@@ -298,6 +295,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
language: Arc<Language>,
|
||||
container_dir: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
mut cached_binary: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
@@ -318,7 +316,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await {
|
||||
log::info!(
|
||||
"found user-installed language server for {}. path: {:?}, arguments: {:?}",
|
||||
self.name().0,
|
||||
language.name(),
|
||||
binary.path,
|
||||
binary.arguments
|
||||
);
|
||||
@@ -388,6 +386,14 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
fn will_start_server(
|
||||
&self,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: &mut AsyncAppContext,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
@@ -555,7 +561,7 @@ pub struct CodeLabel {
|
||||
#[derive(Clone, Deserialize, JsonSchema)]
|
||||
pub struct LanguageConfig {
|
||||
/// Human-readable name of the language.
|
||||
pub name: LanguageName,
|
||||
pub name: Arc<str>,
|
||||
/// The name of this language for a Markdown code fence block
|
||||
pub code_fence_block_name: Option<Arc<str>>,
|
||||
// The name of the grammar in a WASM bundle (experimental).
|
||||
@@ -692,7 +698,7 @@ impl<T> Override<T> {
|
||||
impl Default for LanguageConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: LanguageName::new(""),
|
||||
name: Arc::default(),
|
||||
code_fence_block_name: None,
|
||||
grammar: None,
|
||||
matcher: LanguageMatcher::default(),
|
||||
@@ -1328,7 +1334,7 @@ impl Language {
|
||||
Arc::get_mut(self.grammar.as_mut()?)
|
||||
}
|
||||
|
||||
pub fn name(&self) -> LanguageName {
|
||||
pub fn name(&self) -> Arc<str> {
|
||||
self.config.name.clone()
|
||||
}
|
||||
|
||||
@@ -1336,7 +1342,7 @@ impl Language {
|
||||
self.config
|
||||
.code_fence_block_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.config.name.0.to_lowercase().into())
|
||||
.unwrap_or_else(|| self.config.name.to_lowercase().into())
|
||||
}
|
||||
|
||||
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
|
||||
@@ -1401,7 +1407,10 @@ impl Language {
|
||||
}
|
||||
|
||||
pub fn lsp_id(&self) -> String {
|
||||
self.config.name.lsp_id()
|
||||
match self.config.name.as_ref() {
|
||||
"Plain Text" => "plaintext".to_string(),
|
||||
language_name => language_name.to_lowercase(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prettier_parser_name(&self) -> Option<&str> {
|
||||
@@ -1410,7 +1419,7 @@ impl Language {
|
||||
}
|
||||
|
||||
impl LanguageScope {
|
||||
pub fn language_name(&self) -> LanguageName {
|
||||
pub fn language_name(&self) -> Arc<str> {
|
||||
self.language.config.name.clone()
|
||||
}
|
||||
|
||||
@@ -1651,16 +1660,9 @@ impl LspAdapter for FakeLspAdapter {
|
||||
LanguageServerName(self.name.into())
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
_: &AsyncAppContext,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(self.language_server_binary.clone())
|
||||
}
|
||||
|
||||
fn get_language_server_command<'a>(
|
||||
self: Arc<Self>,
|
||||
_: Arc<Language>,
|
||||
_: Arc<Path>,
|
||||
_: Arc<dyn LspAdapterDelegate>,
|
||||
_: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
|
||||
|
||||
@@ -6,9 +6,9 @@ use crate::{
|
||||
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
|
||||
LanguageServerName, LspAdapter, LspAdapterDelegate, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
|
||||
use futures::TryFutureExt;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
future::Shared,
|
||||
@@ -19,10 +19,8 @@ use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use lsp::LanguageServerId;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use postage::watch;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
borrow::Cow,
|
||||
ffi::OsStr,
|
||||
ops::Not,
|
||||
path::{Path, PathBuf},
|
||||
@@ -34,48 +32,6 @@ use theme::Theme;
|
||||
use unicase::UniCase;
|
||||
use util::{maybe, paths::PathExt, post_inc, ResultExt};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
|
||||
)]
|
||||
pub struct LanguageName(pub Arc<str>);
|
||||
|
||||
impl LanguageName {
|
||||
pub fn new(s: &str) -> Self {
|
||||
Self(Arc::from(s))
|
||||
}
|
||||
|
||||
pub fn from_proto(s: String) -> Self {
|
||||
Self(Arc::from(s))
|
||||
}
|
||||
pub fn to_proto(self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
pub fn lsp_id(&self) -> String {
|
||||
match self.0.as_ref() {
|
||||
"Plain Text" => "plaintext".to_string(),
|
||||
language_name => language_name.to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for LanguageName {
|
||||
fn borrow(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for LanguageName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for LanguageName {
|
||||
fn from(str: &'a str) -> LanguageName {
|
||||
LanguageName(str.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LanguageRegistry {
|
||||
state: RwLock<LanguageRegistryState>,
|
||||
language_server_download_dir: Option<Arc<Path>>,
|
||||
@@ -90,7 +46,7 @@ struct LanguageRegistryState {
|
||||
language_settings: AllLanguageSettingsContent,
|
||||
available_languages: Vec<AvailableLanguage>,
|
||||
grammars: HashMap<Arc<str>, AvailableGrammar>,
|
||||
lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
|
||||
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>>>>>,
|
||||
@@ -100,10 +56,8 @@ struct LanguageRegistryState {
|
||||
reload_count: usize,
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fake_server_txs: HashMap<
|
||||
LanguageName,
|
||||
Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>,
|
||||
>,
|
||||
fake_server_txs:
|
||||
HashMap<Arc<str>, Vec<futures::channel::mpsc::UnboundedSender<lsp::FakeLanguageServer>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -121,9 +75,9 @@ pub struct PendingLanguageServer {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AvailableLanguage {
|
||||
struct AvailableLanguage {
|
||||
id: LanguageId,
|
||||
name: LanguageName,
|
||||
name: Arc<str>,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<
|
||||
@@ -139,16 +93,6 @@ pub struct AvailableLanguage {
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
impl AvailableLanguage {
|
||||
pub fn name(&self) -> LanguageName {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn matcher(&self) -> &LanguageMatcher {
|
||||
&self.matcher
|
||||
}
|
||||
}
|
||||
|
||||
enum AvailableGrammar {
|
||||
Native(tree_sitter::Language),
|
||||
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
|
||||
@@ -252,7 +196,7 @@ impl LanguageRegistry {
|
||||
/// appended to the end.
|
||||
pub fn reorder_language_servers(
|
||||
&self,
|
||||
language: &LanguageName,
|
||||
language: &Arc<Language>,
|
||||
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
|
||||
) {
|
||||
self.state
|
||||
@@ -263,7 +207,7 @@ impl LanguageRegistry {
|
||||
/// Removes the specified languages and grammars from the registry.
|
||||
pub fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
languages_to_remove: &[Arc<str>],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.state
|
||||
@@ -271,7 +215,7 @@ impl LanguageRegistry {
|
||||
.remove_languages(languages_to_remove, grammars_to_remove)
|
||||
}
|
||||
|
||||
pub fn remove_lsp_adapter(&self, language_name: &LanguageName, name: &LanguageServerName) {
|
||||
pub fn remove_lsp_adapter(&self, language_name: &str, name: &LanguageServerName) {
|
||||
let mut state = self.state.write();
|
||||
if let Some(adapters) = state.lsp_adapters.get_mut(language_name) {
|
||||
adapters.retain(|adapter| &adapter.name != name)
|
||||
@@ -323,7 +267,7 @@ impl LanguageRegistry {
|
||||
Some(load_lsp_adapter())
|
||||
}
|
||||
|
||||
pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
|
||||
pub fn register_lsp_adapter(&self, language_name: Arc<str>, adapter: Arc<dyn LspAdapter>) {
|
||||
self.state
|
||||
.write()
|
||||
.lsp_adapters
|
||||
@@ -335,14 +279,13 @@ impl LanguageRegistry {
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn register_fake_lsp_adapter(
|
||||
&self,
|
||||
language_name: impl Into<LanguageName>,
|
||||
language_name: &str,
|
||||
adapter: crate::FakeLspAdapter,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let language_name = language_name.into();
|
||||
self.state
|
||||
.write()
|
||||
.lsp_adapters
|
||||
.entry(language_name.clone())
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(CachedLspAdapter::new(Arc::new(adapter)));
|
||||
self.fake_language_servers(language_name)
|
||||
@@ -351,13 +294,13 @@ impl LanguageRegistry {
|
||||
#[cfg(any(feature = "test-support", test))]
|
||||
pub fn fake_language_servers(
|
||||
&self,
|
||||
language_name: LanguageName,
|
||||
language_name: &str,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<lsp::FakeLanguageServer> {
|
||||
let (servers_tx, servers_rx) = futures::channel::mpsc::unbounded();
|
||||
self.state
|
||||
.write()
|
||||
.fake_server_txs
|
||||
.entry(language_name)
|
||||
.entry(language_name.into())
|
||||
.or_default()
|
||||
.push(servers_tx);
|
||||
servers_rx
|
||||
@@ -366,7 +309,7 @@ impl LanguageRegistry {
|
||||
/// Adds a language to the registry, which can be loaded if needed.
|
||||
pub fn register_language(
|
||||
&self,
|
||||
name: LanguageName,
|
||||
name: Arc<str>,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: impl Fn() -> Result<(
|
||||
@@ -502,7 +445,7 @@ impl LanguageRegistry {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let name = UniCase::new(name);
|
||||
let rx = self.get_or_load_language(|language_name, _| {
|
||||
if UniCase::new(&language_name.0) == name {
|
||||
if UniCase::new(language_name) == name {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -517,7 +460,7 @@ impl LanguageRegistry {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let string = UniCase::new(string);
|
||||
let rx = self.get_or_load_language(|name, config| {
|
||||
if UniCase::new(&name.0) == string
|
||||
if UniCase::new(name) == string
|
||||
|| config
|
||||
.path_suffixes
|
||||
.iter()
|
||||
@@ -531,26 +474,13 @@ impl LanguageRegistry {
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
pub fn available_language_for_name(
|
||||
self: &Arc<Self>,
|
||||
name: &LanguageName,
|
||||
) -> Option<AvailableLanguage> {
|
||||
let state = self.state.read();
|
||||
state
|
||||
.available_languages
|
||||
.iter()
|
||||
.find(|l| &l.name == name)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn language_for_file(
|
||||
self: &Arc<Self>,
|
||||
file: &Arc<dyn File>,
|
||||
content: Option<&Rope>,
|
||||
cx: &AppContext,
|
||||
) -> Option<AvailableLanguage> {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let user_file_types = all_language_settings(Some(file), cx);
|
||||
|
||||
self.language_for_file_internal(
|
||||
&file.full_path(cx),
|
||||
content,
|
||||
@@ -562,16 +492,8 @@ impl LanguageRegistry {
|
||||
self: &Arc<Self>,
|
||||
path: &'a Path,
|
||||
) -> impl Future<Output = Result<Arc<Language>>> + 'a {
|
||||
let available_language = self.language_for_file_internal(path, None, None);
|
||||
|
||||
let this = self.clone();
|
||||
async move {
|
||||
if let Some(language) = available_language {
|
||||
this.load_language(&language).await?
|
||||
} else {
|
||||
Err(anyhow!(LanguageNotFound))
|
||||
}
|
||||
}
|
||||
self.language_for_file_internal(path, None, None)
|
||||
.map_err(|error| error.context(format!("language for file path {}", path.display())))
|
||||
}
|
||||
|
||||
fn language_for_file_internal(
|
||||
@@ -579,19 +501,19 @@ impl LanguageRegistry {
|
||||
path: &Path,
|
||||
content: Option<&Rope>,
|
||||
user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
|
||||
) -> Option<AvailableLanguage> {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let filename = path.file_name().and_then(|name| name.to_str());
|
||||
let extension = path.extension_or_hidden_file_name();
|
||||
let path_suffixes = [extension, filename, path.to_str()];
|
||||
let empty = GlobSet::empty();
|
||||
|
||||
self.find_matching_language(move |language_name, config| {
|
||||
let rx = self.get_or_load_language(move |language_name, config| {
|
||||
let path_matches_default_suffix = config
|
||||
.path_suffixes
|
||||
.iter()
|
||||
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str())));
|
||||
let custom_suffixes = user_file_types
|
||||
.and_then(|types| types.get(&language_name.0))
|
||||
.and_then(|types| types.get(language_name))
|
||||
.unwrap_or(&empty);
|
||||
let path_matches_custom_suffix = path_suffixes
|
||||
.iter()
|
||||
@@ -613,15 +535,18 @@ impl LanguageRegistry {
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
});
|
||||
async move { rx.await? }
|
||||
}
|
||||
|
||||
fn find_matching_language(
|
||||
fn get_or_load_language(
|
||||
self: &Arc<Self>,
|
||||
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
|
||||
) -> Option<AvailableLanguage> {
|
||||
let state = self.state.read();
|
||||
let available_language = state
|
||||
callback: impl Fn(&str, &LanguageMatcher) -> usize,
|
||||
) -> oneshot::Receiver<Result<Arc<Language>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let mut state = self.state.write();
|
||||
let Some((language, _)) = state
|
||||
.available_languages
|
||||
.iter()
|
||||
.filter_map(|language| {
|
||||
@@ -634,23 +559,15 @@ impl LanguageRegistry {
|
||||
})
|
||||
.max_by_key(|e| e.1)
|
||||
.clone()
|
||||
.map(|(available_language, _)| available_language);
|
||||
drop(state);
|
||||
available_language
|
||||
}
|
||||
|
||||
pub fn load_language(
|
||||
self: &Arc<Self>,
|
||||
language: &AvailableLanguage,
|
||||
) -> oneshot::Receiver<Result<Arc<Language>>> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
let mut state = self.state.write();
|
||||
else {
|
||||
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
|
||||
return rx;
|
||||
};
|
||||
|
||||
// If the language is already loaded, resolve with it immediately.
|
||||
for loaded_language in state.languages.iter() {
|
||||
if loaded_language.id == language.id {
|
||||
tx.send(Ok(loaded_language.clone())).unwrap();
|
||||
let _ = tx.send(Ok(loaded_language.clone()));
|
||||
return rx;
|
||||
}
|
||||
}
|
||||
@@ -663,15 +580,12 @@ impl LanguageRegistry {
|
||||
// Otherwise, start loading the language.
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let this = self.clone();
|
||||
|
||||
let id = language.id;
|
||||
let name = language.name.clone();
|
||||
let language_load = language.load.clone();
|
||||
|
||||
self.executor
|
||||
.spawn(async move {
|
||||
let id = language.id;
|
||||
let name = language.name.clone();
|
||||
let language = async {
|
||||
let (config, queries, provider) = (language_load)()?;
|
||||
let (config, queries, provider) = (language.load)()?;
|
||||
|
||||
if let Some(grammar) = config.grammar.clone() {
|
||||
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
||||
@@ -715,28 +629,13 @@ impl LanguageRegistry {
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
entry.insert(vec![tx]);
|
||||
}
|
||||
}
|
||||
|
||||
drop(state);
|
||||
rx
|
||||
}
|
||||
|
||||
fn get_or_load_language(
|
||||
self: &Arc<Self>,
|
||||
callback: impl Fn(&LanguageName, &LanguageMatcher) -> usize,
|
||||
) -> oneshot::Receiver<Result<Arc<Language>>> {
|
||||
let Some(language) = self.find_matching_language(callback) else {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
|
||||
return rx;
|
||||
};
|
||||
|
||||
self.load_language(&language)
|
||||
}
|
||||
|
||||
fn get_or_load_grammar(
|
||||
self: &Arc<Self>,
|
||||
name: Arc<str>,
|
||||
@@ -803,11 +702,11 @@ impl LanguageRegistry {
|
||||
self.state.read().languages.to_vec()
|
||||
}
|
||||
|
||||
pub fn lsp_adapters(&self, language_name: &LanguageName) -> Vec<Arc<CachedLspAdapter>> {
|
||||
pub fn lsp_adapters(&self, language: &Arc<Language>) -> Vec<Arc<CachedLspAdapter>> {
|
||||
self.state
|
||||
.read()
|
||||
.lsp_adapters
|
||||
.get(language_name)
|
||||
.get(&language.config.name)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
@@ -824,7 +723,7 @@ impl LanguageRegistry {
|
||||
pub fn create_pending_language_server(
|
||||
self: &Arc<Self>,
|
||||
stderr_capture: Arc<Mutex<Option<String>>>,
|
||||
_language_name_for_tests: LanguageName,
|
||||
language: Arc<Language>,
|
||||
adapter: Arc<CachedLspAdapter>,
|
||||
root_path: Arc<Path>,
|
||||
delegate: Arc<dyn LspAdapterDelegate>,
|
||||
@@ -833,7 +732,7 @@ impl LanguageRegistry {
|
||||
) -> Option<PendingLanguageServer> {
|
||||
let server_id = self.state.write().next_language_server_id();
|
||||
log::info!(
|
||||
"attempting to start language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||
"starting language server {:?}, path: {root_path:?}, id: {server_id}",
|
||||
adapter.name.0
|
||||
);
|
||||
|
||||
@@ -842,6 +741,7 @@ impl LanguageRegistry {
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server"))
|
||||
.log_err()?;
|
||||
let language = language.clone();
|
||||
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
||||
let root_path = root_path.clone();
|
||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||
@@ -856,7 +756,12 @@ impl LanguageRegistry {
|
||||
|
||||
let binary_result = adapter
|
||||
.clone()
|
||||
.get_language_server_command(container_dir, delegate.clone(), &mut cx)
|
||||
.get_language_server_command(
|
||||
language.clone(),
|
||||
container_dir,
|
||||
delegate.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None);
|
||||
@@ -880,6 +785,10 @@ impl LanguageRegistry {
|
||||
.initialization_options(&delegate)
|
||||
.await?;
|
||||
|
||||
if let Some(task) = adapter.will_start_server(&delegate, &mut cx) {
|
||||
task.await?;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
if true {
|
||||
let capabilities = adapter
|
||||
@@ -916,7 +825,7 @@ impl LanguageRegistry {
|
||||
.state
|
||||
.write()
|
||||
.fake_server_txs
|
||||
.get_mut(&_language_name_for_tests)
|
||||
.get_mut(language.name().as_ref())
|
||||
{
|
||||
for tx in txs {
|
||||
tx.unbounded_send(fake_server.clone()).ok();
|
||||
@@ -1026,10 +935,10 @@ impl LanguageRegistryState {
|
||||
/// appended to the end.
|
||||
fn reorder_language_servers(
|
||||
&mut self,
|
||||
language_name: &LanguageName,
|
||||
language: &Arc<Language>,
|
||||
ordered_lsp_adapters: Vec<Arc<CachedLspAdapter>>,
|
||||
) {
|
||||
let Some(lsp_adapters) = self.lsp_adapters.get_mut(language_name) else {
|
||||
let Some(lsp_adapters) = self.lsp_adapters.get_mut(&language.config.name) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1050,7 +959,7 @@ impl LanguageRegistryState {
|
||||
|
||||
fn remove_languages(
|
||||
&mut self,
|
||||
languages_to_remove: &[LanguageName],
|
||||
languages_to_remove: &[Arc<str>],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
if languages_to_remove.is_empty() && grammars_to_remove.is_empty() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Provides `language`-related settings.
|
||||
|
||||
use crate::{File, Language, LanguageName, LanguageServerName};
|
||||
use crate::{File, Language, LanguageServerName};
|
||||
use anyhow::Result;
|
||||
use collections::{HashMap, HashSet};
|
||||
use core::slice;
|
||||
@@ -20,6 +20,15 @@ use settings::{add_references_to_properties, Settings, SettingsLocation, Setting
|
||||
use std::{num::NonZeroU32, path::Path, sync::Arc};
|
||||
use util::serde::default_true;
|
||||
|
||||
impl<'a> From<&'a dyn File> for SettingsLocation<'a> {
|
||||
fn from(val: &'a dyn File) -> Self {
|
||||
SettingsLocation {
|
||||
worktree_id: val.worktree_id(),
|
||||
path: val.path().as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the language settings.
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
AllLanguageSettings::register(cx);
|
||||
@@ -32,7 +41,7 @@ pub fn language_settings<'a>(
|
||||
cx: &'a AppContext,
|
||||
) -> &'a LanguageSettings {
|
||||
let language_name = language.map(|l| l.name());
|
||||
all_language_settings(file, cx).language(language_name.as_ref())
|
||||
all_language_settings(file, cx).language(language_name.as_deref())
|
||||
}
|
||||
|
||||
/// Returns the settings for all languages from the provided file.
|
||||
@@ -40,10 +49,7 @@ pub fn all_language_settings<'a>(
|
||||
file: Option<&Arc<dyn File>>,
|
||||
cx: &'a AppContext,
|
||||
) -> &'a AllLanguageSettings {
|
||||
let location = file.map(|f| SettingsLocation {
|
||||
worktree_id: f.worktree_id(cx),
|
||||
path: f.path().as_ref(),
|
||||
});
|
||||
let location = file.map(|f| f.as_ref().into());
|
||||
AllLanguageSettings::get(location, cx)
|
||||
}
|
||||
|
||||
@@ -53,7 +59,7 @@ pub struct AllLanguageSettings {
|
||||
/// The inline completion settings.
|
||||
pub inline_completions: InlineCompletionSettings,
|
||||
defaults: LanguageSettings,
|
||||
languages: HashMap<LanguageName, LanguageSettings>,
|
||||
languages: HashMap<Arc<str>, LanguageSettings>,
|
||||
pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
|
||||
}
|
||||
|
||||
@@ -70,10 +76,10 @@ pub struct LanguageSettings {
|
||||
/// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||
/// is enabled.
|
||||
pub preferred_line_length: u32,
|
||||
/// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
/// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
/// if softwrap is set to 'preferred_line_length', and will show any
|
||||
/// additional guides as specified by the 'wrap_guides' setting.
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
// if softwrap is set to 'preferred_line_length', and will show any
|
||||
// additional guides as specified by the 'wrap_guides' setting.
|
||||
pub show_wrap_guides: bool,
|
||||
/// Character counts at which to show wrap guides (vertical rulers) in the editor.
|
||||
pub wrap_guides: Vec<usize>,
|
||||
@@ -204,7 +210,7 @@ pub struct AllLanguageSettingsContent {
|
||||
pub defaults: LanguageSettingsContent,
|
||||
/// The settings for individual languages.
|
||||
#[serde(default)]
|
||||
pub languages: HashMap<LanguageName, LanguageSettingsContent>,
|
||||
pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
|
||||
/// Settings for associating file extensions and filenames
|
||||
/// with languages.
|
||||
#[serde(default)]
|
||||
@@ -791,7 +797,7 @@ impl InlayHintSettings {
|
||||
|
||||
impl AllLanguageSettings {
|
||||
/// Returns the [`LanguageSettings`] for the language with the specified name.
|
||||
pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
|
||||
pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
|
||||
if let Some(name) = language_name {
|
||||
if let Some(overrides) = self.languages.get(name) {
|
||||
return overrides;
|
||||
@@ -821,7 +827,7 @@ impl AllLanguageSettings {
|
||||
}
|
||||
}
|
||||
|
||||
self.language(language.map(|l| l.name()).as_ref())
|
||||
self.language(language.map(|l| l.name()).as_deref())
|
||||
.show_inline_completions
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,6 @@ pub async fn parse_markdown_block(
|
||||
let mut list_stack = Vec::new();
|
||||
|
||||
let mut options = pulldown_cmark::Options::all();
|
||||
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
|
||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
|
||||
for event in Parser::new_ext(markdown, options) {
|
||||
@@ -385,7 +384,6 @@ public: void format(const int &, const std::tm &, int &dest)
|
||||
"#;
|
||||
|
||||
let mut options = pulldown_cmark::Options::all();
|
||||
options.remove(pulldown_cmark::Options::ENABLE_DEFINITION_LIST);
|
||||
options.remove(pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS);
|
||||
|
||||
let parser = pulldown_cmark::Parser::new_ext(input, options);
|
||||
|
||||
@@ -695,7 +695,7 @@ impl Render for ConfigurationView {
|
||||
)
|
||||
.child(
|
||||
Label::new(
|
||||
format!("You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed."),
|
||||
"You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed.",
|
||||
)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
|
||||
@@ -254,13 +254,11 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
|
||||
}),
|
||||
AvailableProvider::OpenAi => CloudModel::OpenAi(open_ai::Model::Custom {
|
||||
name: model.name.clone(),
|
||||
display_name: model.display_name.clone(),
|
||||
max_tokens: model.max_tokens,
|
||||
max_output_tokens: model.max_output_tokens,
|
||||
}),
|
||||
AvailableProvider::Google => CloudModel::Google(google_ai::Model::Custom {
|
||||
name: model.name.clone(),
|
||||
display_name: model.display_name.clone(),
|
||||
max_tokens: model.max_tokens,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -37,7 +37,6 @@ pub struct GoogleSettings {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AvailableModel {
|
||||
name: String,
|
||||
display_name: Option<String>,
|
||||
max_tokens: usize,
|
||||
}
|
||||
|
||||
@@ -171,7 +170,6 @@ impl LanguageModelProvider for GoogleLanguageModelProvider {
|
||||
model.name.clone(),
|
||||
google_ai::Model::Custom {
|
||||
name: model.name.clone(),
|
||||
display_name: model.display_name.clone(),
|
||||
max_tokens: model.max_tokens,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -40,7 +40,6 @@ pub struct OpenAiSettings {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct AvailableModel {
|
||||
pub name: String,
|
||||
pub display_name: Option<String>,
|
||||
pub max_tokens: usize,
|
||||
pub max_output_tokens: Option<u32>,
|
||||
}
|
||||
@@ -172,7 +171,6 @@ impl LanguageModelProvider for OpenAiLanguageModelProvider {
|
||||
model.name.clone(),
|
||||
open_ai::Model::Custom {
|
||||
name: model.name.clone(),
|
||||
display_name: model.display_name.clone(),
|
||||
max_tokens: model.max_tokens,
|
||||
max_output_tokens: model.max_output_tokens,
|
||||
},
|
||||
@@ -491,7 +489,7 @@ impl ConfigurationView {
|
||||
|
||||
impl Render for ConfigurationView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
const OPENAI_CONSOLE_URL: &str = "https://platform.openai.com/api-keys";
|
||||
const OPENAI_CONSOLE_URL: &str = "https://console.anthropic.com/settings/keys";
|
||||
const INSTRUCTIONS: [&str; 6] = [
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI API key.",
|
||||
" - You can create an API key at: ",
|
||||
|
||||
@@ -399,7 +399,7 @@ impl LanguageModelRequest {
|
||||
tool_choice: None,
|
||||
metadata: None,
|
||||
stop_sequences: Vec::new(),
|
||||
temperature: Some(self.temperature),
|
||||
temperature: None,
|
||||
top_k: None,
|
||||
top_p: None,
|
||||
}
|
||||
|
||||
@@ -175,14 +175,12 @@ impl OpenAiSettingsContent {
|
||||
.filter_map(|model| match model {
|
||||
open_ai::Model::Custom {
|
||||
name,
|
||||
display_name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
} => Some(provider::open_ai::AvailableModel {
|
||||
name,
|
||||
max_tokens,
|
||||
max_output_tokens,
|
||||
display_name,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user