Compare commits

..

3 Commits

Author SHA1 Message Date
Thorsten Ball
4d41e6aa35 more debug 2024-09-06 18:32:43 +02:00
Thorsten Ball
5d0bf9904e debug project leak stuff 2024-09-06 17:50:39 +02:00
Thorsten Ball
1ca4cfe2f8 Fix Workspace references being leaked
We noticed that the `Workspace` was never released (along with the
`Project` and everything that comes along with that) when closing a
window.

After playing around with the LeakDetector and debugging with
`cx.on_release()` callbacks, we found two culprits: the inline assistant
and the outline panel.

Both held strong references to `View<Workspace>` after PR #16589 and
PR #16845.

This PR changes both references to `WeakView<Workspace>` which fixes the
leak but keeps the behaviour the same.

Co-authored-by: Bennet <bennet@zed.dev>
2024-09-06 15:21:54 +02:00
237 changed files with 3911 additions and 6061 deletions

View File

@@ -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
View File

@@ -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)",
]

View File

@@ -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"]

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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:
//

View File

@@ -1 +0,0 @@
allow-private-module-inception = true

View File

@@ -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>)
})
})
}

View File

@@ -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,
}),

View File

@@ -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(),

View File

@@ -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?;

View File

@@ -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,
)
});

View File

@@ -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",
};

View File

@@ -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,
}
}

View File

@@ -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,
})
})

View File

@@ -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
});

View File

@@ -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 {

View File

@@ -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))
}
}

View File

@@ -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) }));

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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(&params) {
return Ok(Json(CheckIsContributorResponse {

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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>> {

View File

@@ -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;

View File

@@ -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| {

View File

@@ -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()]);
});
});
});

View File

@@ -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()),]
)

View File

@@ -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()]
)

View File

@@ -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),
);
}

View File

@@ -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);

View File

@@ -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),
);
}

View File

@@ -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),
);
}

View File

@@ -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>,

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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();

View File

@@ -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(

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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>,

View File

@@ -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);
}
}

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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>(
&[

View File

@@ -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
});

View 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) {

View File

@@ -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()

View File

@@ -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>>,

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
};

View File

@@ -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| {

View File

@@ -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,
})?)

View File

@@ -1000,7 +1000,7 @@ impl ExtensionsPage {
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = VimModeSetting(value),
|setting, value| *setting = Some(value),
);
}),
)),

View File

@@ -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")
}

View File

@@ -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(),

View File

@@ -181,7 +181,7 @@ impl PickerDelegate for OpenPathDelegate {
}
let matches = fuzzy::match_strings(
match_candidates.as_slice(),
&match_candidates.as_slice(),
&suffix,
false,
100,

View File

@@ -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: &notify::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;

View File

@@ -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)
}
}

View File

@@ -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,
}
}

View File

@@ -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 }

View File

@@ -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");
}
}

View File

@@ -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);

View File

@@ -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,
}

View File

@@ -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);

View File

@@ -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>);

View File

@@ -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
}
}

View 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
}
}

View File

@@ -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);

View File

@@ -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), {

View File

@@ -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 {

View File

@@ -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>) {

View File

@@ -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();
}

View File

@@ -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
}
}

View File

@@ -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(

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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")]

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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)]

View File

@@ -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.

View File

@@ -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")]

View File

@@ -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 {

View File

@@ -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()
},

View File

@@ -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>>,

View File

@@ -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() {

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -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),
)

View File

@@ -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,
}),
};

View File

@@ -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,
},
);

View File

@@ -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: ",

View File

@@ -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,
}

View File

@@ -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