Compare commits

..

1 Commits

Author SHA1 Message Date
Smit Barmase
acac967c63 fix macos focus issue 2025-10-03 18:57:12 +05:30
303 changed files with 7499 additions and 14709 deletions

View File

@@ -24,7 +24,7 @@ workspace-members = [
third-party = [
{ name = "reqwest", version = "0.11.27" },
# build of remote_server should not include scap / its x11 dependency
{ name = "zed-scap", git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", version = "0.0.8-zed" },
{ name = "scap", git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" },
# build of remote_server should not need to include on libalsa through rodio
{ name = "rodio", git = "https://github.com/RustAudio/rodio" },
]

View File

@@ -20,4 +20,4 @@ runs:
- name: Run tests
shell: bash -euxo pipefail {0}
run: cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
run: cargo nextest run --workspace --no-fail-fast

View File

@@ -24,4 +24,4 @@ runs:
shell: powershell
working-directory: ${{ inputs.working-directory }}
run: |
cargo nextest run --workspace --no-fail-fast --failure-output immediate-final
cargo nextest run --workspace --no-fail-fast

View File

@@ -826,9 +826,8 @@ jobs:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-32vcpu-windows-2022]
if: |
( startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
@@ -871,7 +870,8 @@ jobs:
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
# Re-enable when we are ready to publish windows preview releases
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

1119
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -273,7 +273,7 @@ cloud_llm_client = { path = "crates/cloud_llm_client" }
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections", package = "zed-collections", version = "0.1.0" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
component = { path = "crates/component" }
@@ -289,7 +289,6 @@ debug_adapter_extension = { path = "crates/debug_adapter_extension" }
debugger_tools = { path = "crates/debugger_tools" }
debugger_ui = { path = "crates/debugger_ui" }
deepseek = { path = "crates/deepseek" }
derive_refineable = { path = "crates/refineable/derive_refineable", package = "zed-derive-refineable", version = "0.1.0" }
diagnostics = { path = "crates/diagnostics" }
editor = { path = "crates/editor" }
extension = { path = "crates/extension" }
@@ -308,10 +307,10 @@ git_ui = { path = "crates/git_ui" }
go_to_line = { path = "crates/go_to_line" }
google_ai = { path = "crates/google_ai" }
gpui = { path = "crates/gpui", default-features = false }
gpui_macros = { path = "crates/gpui_macros", package = "gpui-macros", version = "0.1.0" }
gpui_macros = { path = "crates/gpui_macros" }
gpui_tokio = { path = "crates/gpui_tokio" }
html_to_markdown = { path = "crates/html_to_markdown" }
http_client = { path = "crates/http_client", package = "zed-http-client", version = "0.1.0" }
http_client = { path = "crates/http_client" }
http_client_tls = { path = "crates/http_client_tls" }
icons = { path = "crates/icons" }
image_viewer = { path = "crates/image_viewer" }
@@ -340,7 +339,7 @@ lsp = { path = "crates/lsp" }
markdown = { path = "crates/markdown" }
markdown_preview = { path = "crates/markdown_preview" }
svg_preview = { path = "crates/svg_preview" }
media = { path = "crates/media", package = "zed-media", version = "0.1.0" }
media = { path = "crates/media" }
menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
@@ -357,7 +356,7 @@ outline = { path = "crates/outline" }
outline_panel = { path = "crates/outline_panel" }
panel = { path = "crates/panel" }
paths = { path = "crates/paths" }
perf = { path = "tooling/perf", package = "zed-perf", version = "0.1.0" }
perf = { path = "tooling/perf" }
picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
@@ -369,7 +368,7 @@ project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable", package = "zed-refineable", version = "0.1.0" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
scheduler = { path = "crates/scheduler" }
remote = { path = "crates/remote" }
@@ -382,7 +381,7 @@ rope = { path = "crates/rope" }
rpc = { path = "crates/rpc" }
rules_library = { path = "crates/rules_library" }
search = { path = "crates/search" }
semantic_version = { path = "crates/semantic_version", package = "zed-semantic-version", version = "0.1.0" }
semantic_version = { path = "crates/semantic_version" }
session = { path = "crates/session" }
settings = { path = "crates/settings" }
settings_macros = { path = "crates/settings_macros" }
@@ -395,7 +394,7 @@ sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree", package = "zed-sum-tree", version = "0.1.0" }
sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" }
system_specs = { path = "crates/system_specs" }
@@ -418,8 +417,8 @@ ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_macros = { path = "crates/ui_macros" }
ui_prompt = { path = "crates/ui_prompt" }
util = { path = "crates/util", package = "zed-util", version = "0.1.0" }
util_macros = { path = "crates/util_macros", package = "zed-util-macros", version = "0.1.0" }
util = { path = "crates/util" }
util_macros = { path = "crates/util_macros" }
vercel = { path = "crates/vercel" }
vim = { path = "crates/vim" }
vim_mode_setting = { path = "crates/vim_mode_setting" }
@@ -549,7 +548,6 @@ nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
num-traits = "0.2"
objc = "0.2"
objc2-foundation = { version = "0.3", default-features = false, features = [
"NSArray",
@@ -606,8 +604,7 @@ rand = "0.9"
rayon = "1.8"
ref-cast = "1.0.24"
regex = "1.5"
# WARNING: If you change this, you must also publish a new version of zed-reqwest to crates.io
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662463bda39148ba154100dd44d3fba5873a4", default-features = false, features = [
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c770a32f1998d6e999cef3e59e0013e6c4415", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
@@ -615,7 +612,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "c15662
"rustls-tls-native-roots",
"socks",
"stream",
], package = "zed-reqwest", version = "0.12.15-zed" }
] }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
@@ -624,8 +621,7 @@ rust-embed = { version = "8.4", features = ["include-exclude"] }
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
# WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
scap = { git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0"
serde = { version = "1.0.221", features = ["derive", "rc"] }
@@ -651,7 +647,7 @@ streaming-iterator = "0.1"
strsim = "0.11"
strum = { version = "0.27.0", features = ["derive"] }
subtle = "2.5.0"
syn = { version = "2.0.101", features = ["full", "extra-traits", "visit-mut"] }
syn = { version = "2.0.101", features = ["full", "extra-traits"] }
sys-locale = "0.3.1"
sysinfo = "0.31.0"
take-until = "0.2.0"
@@ -669,7 +665,6 @@ tiny_http = "0.8"
tokio = { version = "1" }
tokio-tungstenite = { version = "0.26", features = ["__rustls-tls"] }
toml = "0.8"
toml_edit = { version = "0.22", default-features = false, features = ["display", "parse", "serde"] }
tower-http = "0.4.4"
tree-sitter = { version = "0.25.10", features = ["wasm"] }
tree-sitter-bash = "0.25.0"
@@ -804,7 +799,7 @@ wasmtime = { opt-level = 3 }
activity_indicator = { codegen-units = 1 }
assets = { codegen-units = 1 }
breadcrumbs = { codegen-units = 1 }
zed-collections = { codegen-units = 1 }
collections = { codegen-units = 1 }
command_palette = { codegen-units = 1 }
command_palette_hooks = { codegen-units = 1 }
extension_cli = { codegen-units = 1 }
@@ -824,11 +819,11 @@ outline = { codegen-units = 1 }
paths = { codegen-units = 1 }
prettier = { codegen-units = 1 }
project_symbols = { codegen-units = 1 }
zed-refineable = { codegen-units = 1 }
refineable = { codegen-units = 1 }
release_channel = { codegen-units = 1 }
reqwest_client = { codegen-units = 1 }
rich_text = { codegen-units = 1 }
zed-semantic-version = { codegen-units = 1 }
semantic_version = { codegen-units = 1 }
session = { codegen-units = 1 }
snippet = { codegen-units = 1 }
snippets_ui = { codegen-units = 1 }

View File

@@ -30,8 +30,7 @@
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -370,15 +369,7 @@
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow"
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
}
},
{

View File

@@ -39,8 +39,7 @@
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
"cmd-,": "zed::OpenSettingsEditor",
"cmd-alt-,": "zed::OpenSettings",
"cmd-,": "zed::OpenSettings",
"cmd-q": "zed::Quit",
"cmd-h": "zed::Hide",
"alt-cmd-h": "zed::HideOthers",
@@ -431,13 +430,6 @@
"cmd-w": "workspace::CloseWindow"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"cmd-w": "workspace::CloseWindow"
}
},
{
"context": "BufferSearchBar",
"use_key_equivalents": true,

View File

@@ -29,8 +29,7 @@
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
"ctrl-,": "zed::OpenSettingsEditor",
"ctrl-alt-,": "zed::OpenSettings",
"ctrl-,": "zed::OpenSettings",
"ctrl-q": "zed::Quit",
"f4": "debugger::Start",
"shift-f5": "debugger::Stop",
@@ -379,15 +378,7 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow"
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
}
},
{

View File

@@ -240,7 +240,7 @@
"delete": "vim::DeleteRight",
"g shift-j": "vim::JoinLinesNoWhitespace",
"y": "vim::PushYank",
"shift-y": "vim::YankLine",
"shift-y": "vim::YankToEndOfLine",
"x": "vim::DeleteRight",
"shift-x": "vim::DeleteLeft",
"ctrl-a": "vim::Increment",
@@ -393,7 +393,7 @@
"escape": "editor::Cancel",
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"shift-y": "vim::YankLine",
"shift-y": "vim::YankToEndOfLine",
"shift-i": "vim::InsertFirstNonWhitespace",
"shift-a": "vim::InsertEndOfLine",
"o": "vim::InsertLineBelow",

View File

@@ -74,10 +74,8 @@
"ui_font_weight": 400,
// The default font size for text in the UI
"ui_font_size": 16,
// The default font size for agent responses in the agent panel. Falls back to the UI font size if unset.
"agent_ui_font_size": null,
// The default font size for user messages in the agent panel. Falls back to the buffer font size if unset.
"agent_buffer_font_size": 12,
// The default font size for text in the agent panel. Falls back to the UI font size if unset.
"agent_font_size": null,
// How much to fade out unused code.
"unnecessary_code_fade": 0.3,
// Active pane styling settings.
@@ -1231,10 +1229,6 @@
// 2. Hide the gutter
// "git_gutter": "hide"
"git_gutter": "tracked_files",
/// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
///
/// Default: null
"gutter_debounce": null,
// Control whether the git blame information is shown inline,
// in the currently focused line.
"inline_blame": {
@@ -1333,8 +1327,6 @@
},
// Status bar-related settings.
"status_bar": {
// Whether to show the status bar.
"experimental.show": true,
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
@@ -1570,14 +1562,6 @@
"auto_install_extensions": {
"html": true
},
// The capabilities granted to extensions.
//
// This list can be customized to restrict what extensions are able to do.
"granted_extension_capabilities": [
{ "kind": "process:exec", "command": "*", "args": ["**"] },
{ "kind": "download_file", "host": "*", "path": ["**"] },
{ "kind": "npm:install", "package": "*" }
],
// Controls how completions are processed for this language.
"completions": {
// Controls how words are completed.
@@ -1876,19 +1860,21 @@
// Allows to enable/disable formatting with Prettier
// and configure default Prettier, used when no project-level Prettier installation is found.
"prettier": {
// Enables or disables formatting with Prettier for any given language.
"allowed": false,
// Forces Prettier integration to use a specific parser name when formatting files with the language.
"plugins": [],
// Default Prettier options, in the format as in package.json section for Prettier.
// If project installs Prettier via its package.json, these options will be ignored.
// // Whether to consider prettier formatter or not when attempting to format a file.
"allowed": false
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// "plugins": [],
//
// // Use regular Prettier json configuration.
// // If Prettier is allowed, Zed will use this for its Prettier instance for any applicable file, if
// // the project has no other Prettier installed.
// "trailingComma": "es5",
// "tabWidth": 4,
// "semi": false,
// "singleQuote": true
// Forces Prettier integration to use a specific parser name when formatting files with the language
// when set to a non-empty string.
"parser": ""
},
// Settings for auto-closing of JSX tags.
"jsx_tag_auto_close": {
@@ -2038,7 +2024,7 @@
// Examples:
// "profiles": {
// "Presenting": {
// "agent_ui_font_size": 20.0,
// "agent_font_size": 20.0,
// "buffer_font_size": 20.0,
// "theme": "One Light",
// "ui_font_size": 20.0

View File

@@ -12,7 +12,7 @@ use language::language_settings::FormatOnSave;
pub use mention::*;
use project::lsp_store::{FormatTrigger, LspFormatTarget};
use serde::{Deserialize, Serialize};
use settings::{Settings as _, SettingsLocation};
use settings::Settings as _;
use task::{Shell, ShellBuilder};
pub use terminal::*;
@@ -35,7 +35,7 @@ use std::rc::Rc;
use std::time::{Duration, Instant};
use std::{fmt::Display, mem, path::PathBuf, sync::Arc};
use ui::App;
use util::{ResultExt, get_default_system_shell_preferring_bash};
use util::{ResultExt, get_default_system_shell};
use uuid::Uuid;
#[derive(Debug)]
@@ -788,8 +788,6 @@ pub struct AcpThread {
prompt_capabilities: acp::PromptCapabilities,
_observe_prompt_capabilities: Task<anyhow::Result<()>>,
terminals: HashMap<acp::TerminalId, Entity<Terminal>>,
pending_terminal_output: HashMap<acp::TerminalId, Vec<Vec<u8>>>,
pending_terminal_exit: HashMap<acp::TerminalId, acp::TerminalExitStatus>,
}
#[derive(Debug)]
@@ -812,126 +810,6 @@ pub enum AcpThreadEvent {
impl EventEmitter<AcpThreadEvent> for AcpThread {}
#[derive(Debug, Clone)]
pub enum TerminalProviderEvent {
Created {
terminal_id: acp::TerminalId,
label: String,
cwd: Option<PathBuf>,
output_byte_limit: Option<u64>,
terminal: Entity<::terminal::Terminal>,
},
Output {
terminal_id: acp::TerminalId,
data: Vec<u8>,
},
TitleChanged {
terminal_id: acp::TerminalId,
title: String,
},
Exit {
terminal_id: acp::TerminalId,
status: acp::TerminalExitStatus,
},
}
#[derive(Debug, Clone)]
pub enum TerminalProviderCommand {
WriteInput {
terminal_id: acp::TerminalId,
bytes: Vec<u8>,
},
Resize {
terminal_id: acp::TerminalId,
cols: u16,
rows: u16,
},
Close {
terminal_id: acp::TerminalId,
},
}
impl AcpThread {
pub fn on_terminal_provider_event(
&mut self,
event: TerminalProviderEvent,
cx: &mut Context<Self>,
) {
match event {
TerminalProviderEvent::Created {
terminal_id,
label,
cwd,
output_byte_limit,
terminal,
} => {
let entity = self.register_terminal_created(
terminal_id.clone(),
label,
cwd,
output_byte_limit,
terminal,
cx,
);
if let Some(mut chunks) = self.pending_terminal_output.remove(&terminal_id) {
for data in chunks.drain(..) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.write_output(&data, cx);
})
});
}
}
if let Some(_status) = self.pending_terminal_exit.remove(&terminal_id) {
entity.update(cx, |_term, cx| {
cx.notify();
});
}
cx.notify();
}
TerminalProviderEvent::Output { terminal_id, data } => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.write_output(&data, cx);
})
});
} else {
self.pending_terminal_output
.entry(terminal_id)
.or_default()
.push(data);
}
}
TerminalProviderEvent::TitleChanged { terminal_id, title } => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |term, cx| {
term.inner().update(cx, |inner, cx| {
inner.breadcrumb_text = title;
cx.emit(::terminal::Event::BreadcrumbsChanged);
})
});
}
}
TerminalProviderEvent::Exit {
terminal_id,
status,
} => {
if let Some(entity) = self.terminals.get(&terminal_id) {
entity.update(cx, |_term, cx| {
cx.notify();
});
} else {
self.pending_terminal_exit.insert(terminal_id, status);
}
}
}
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum ThreadStatus {
Idle,
@@ -1009,8 +887,6 @@ impl AcpThread {
prompt_capabilities,
_observe_prompt_capabilities: task,
terminals: HashMap::default(),
pending_terminal_output: HashMap::default(),
pending_terminal_exit: HashMap::default(),
}
}
@@ -2086,16 +1962,7 @@ impl AcpThread {
) -> Task<Result<Entity<Terminal>>> {
let env = match &cwd {
Some(dir) => self.project.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
let shell = TerminalSettings::get_global(cx).shell.clone();
project.directory_environment(&shell, dir.as_path().into(), cx)
}),
None => Task::ready(None).shared(),
@@ -2118,16 +1985,18 @@ impl AcpThread {
let terminal_id = terminal_id.clone();
async move |_this, cx| {
let env = env.await;
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
})?
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
let (task_command, task_args) = ShellBuilder::new(&Shell::Program(shell))
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
let (task_command, task_args) = ShellBuilder::new(
project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
})?
.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(command.clone()), &args);
let terminal = project
.update(cx, |project, cx| {
project.create_terminal_task(
@@ -2210,32 +2079,6 @@ impl AcpThread {
pub fn emit_load_error(&mut self, error: LoadError, cx: &mut Context<Self>) {
cx.emit(AcpThreadEvent::LoadError(error));
}
pub fn register_terminal_created(
&mut self,
terminal_id: acp::TerminalId,
command_label: String,
working_dir: Option<PathBuf>,
output_byte_limit: Option<u64>,
terminal: Entity<::terminal::Terminal>,
cx: &mut Context<Self>,
) -> Entity<Terminal> {
let language_registry = self.project.read(cx).languages().clone();
let entity = cx.new(|cx| {
Terminal::new(
terminal_id.clone(),
&command_label,
working_dir.clone(),
output_byte_limit.map(|l| l as usize),
terminal,
language_registry,
cx,
)
});
self.terminals.insert(terminal_id.clone(), entity.clone());
entity
}
}
fn markdown_for_raw_output(
@@ -2312,145 +2155,6 @@ mod tests {
});
}
#[gpui::test]
async fn test_terminal_output_buffered_before_created_renders(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
.await
.unwrap();
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
// Send Output BEFORE Created - should be buffered by acp_thread
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data: b"hello buffered".to_vec(),
},
cx,
);
});
// Create a display-only terminal and then send Created
let lower = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: "Buffered Test".to_string(),
cwd: None,
output_byte_limit: None,
terminal: lower.clone(),
},
cx,
);
});
// After Created, buffered Output should have been flushed into the renderer
let content = thread.read_with(cx, |thread, cx| {
let term = thread.terminal(terminal_id.clone()).unwrap();
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
});
assert!(
content.contains("hello buffered"),
"expected buffered output to render, got: {content}"
);
}
#[gpui::test]
async fn test_terminal_output_and_exit_buffered_before_created(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
let project = Project::test(fs, [], cx).await;
let connection = Rc::new(FakeAgentConnection::new());
let thread = cx
.update(|cx| connection.new_thread(project, std::path::Path::new(path!("/test")), cx))
.await
.unwrap();
let terminal_id = acp::TerminalId(uuid::Uuid::new_v4().to_string().into());
// Send Output BEFORE Created
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data: b"pre-exit data".to_vec(),
},
cx,
);
});
// Send Exit BEFORE Created
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id.clone(),
status: acp::TerminalExitStatus {
exit_code: Some(0),
signal: None,
meta: None,
},
},
cx,
);
});
// Now create a display-only lower-level terminal and send Created
let lower = cx.new(|cx| {
let builder = ::terminal::TerminalBuilder::new_display_only(
::terminal::terminal_settings::CursorShape::default(),
::terminal::terminal_settings::AlternateScroll::On,
None,
0,
)
.unwrap();
builder.subscribe(cx)
});
thread.update(cx, |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: "Buffered Exit Test".to_string(),
cwd: None,
output_byte_limit: None,
terminal: lower.clone(),
},
cx,
);
});
// Output should be present after Created (flushed from buffer)
let content = thread.read_with(cx, |thread, cx| {
let term = thread.terminal(terminal_id.clone()).unwrap();
term.read_with(cx, |t, cx| t.inner().read(cx).get_content())
});
assert!(
content.contains("pre-exit data"),
"expected pre-exit data to render, got: {content}"
);
}
#[gpui::test]
async fn test_push_user_content_block(cx: &mut gpui::TestAppContext) {
init_test(cx);

View File

@@ -3276,6 +3276,7 @@ mod tests {
use settings::{LanguageModelParameters, Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
@@ -5336,7 +5337,7 @@ fn main() {{
thread_store::init(fs.clone(), cx);
workspace::init_settings(cx);
language_model::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
ToolRegistry::default_global(cx);
assistant_tool::init(cx);

View File

@@ -38,7 +38,7 @@ use std::{
cell::{Ref, RefCell},
path::{Path, PathBuf},
rc::Rc,
sync::{Arc, LazyLock, Mutex},
sync::{Arc, Mutex},
};
use util::{ResultExt as _, rel_path::RelPath};
@@ -74,19 +74,17 @@ impl Column for DataType {
}
}
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
[
RelPath::unix(".rules").unwrap(),
RelPath::unix(".cursorrules").unwrap(),
RelPath::unix(".windsurfrules").unwrap(),
RelPath::unix(".clinerules").unwrap(),
RelPath::unix(".github/copilot-instructions.md").unwrap(),
RelPath::unix("CLAUDE.md").unwrap(),
RelPath::unix("AGENT.md").unwrap(),
RelPath::unix("AGENTS.md").unwrap(),
RelPath::unix("GEMINI.md").unwrap(),
]
});
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
".windsurfrules",
".clinerules",
".github/copilot-instructions.md",
"CLAUDE.md",
"AGENT.md",
"AGENTS.md",
"GEMINI.md",
];
pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
ThreadsDatabase::init(fs, cx);
@@ -234,10 +232,11 @@ impl ThreadStore {
self.enqueue_system_prompt_reload();
}
project::Event::WorktreeUpdatedEntries(_, items) => {
if items
.iter()
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
{
if items.iter().any(|(path, _, _)| {
RULES_FILE_NAMES
.iter()
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
}) {
self.enqueue_system_prompt_reload();
}
}
@@ -369,7 +368,7 @@ impl ThreadStore {
.into_iter()
.filter_map(|name| {
worktree
.entry_for_path(name)
.entry_for_path(RelPath::unix(name).unwrap())
.filter(|entry| entry.is_file())
.map(|entry| entry.path.clone())
})

View File

@@ -25,23 +25,21 @@ use std::any::Any;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::{Arc, LazyLock};
use std::sync::Arc;
use util::ResultExt;
use util::rel_path::RelPath;
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
[
RelPath::unix(".rules").unwrap(),
RelPath::unix(".cursorrules").unwrap(),
RelPath::unix(".windsurfrules").unwrap(),
RelPath::unix(".clinerules").unwrap(),
RelPath::unix(".github/copilot-instructions.md").unwrap(),
RelPath::unix("CLAUDE.md").unwrap(),
RelPath::unix("AGENT.md").unwrap(),
RelPath::unix("AGENTS.md").unwrap(),
RelPath::unix("GEMINI.md").unwrap(),
]
});
const RULES_FILE_NAMES: [&str; 9] = [
".rules",
".cursorrules",
".windsurfrules",
".clinerules",
".github/copilot-instructions.md",
"CLAUDE.md",
"AGENT.md",
"AGENTS.md",
"GEMINI.md",
];
pub struct RulesLoadingError {
pub message: SharedString,
@@ -477,7 +475,7 @@ impl NativeAgent {
.into_iter()
.filter_map(|name| {
worktree
.entry_for_path(name)
.entry_for_path(RelPath::unix(name).unwrap())
.filter(|entry| entry.is_file())
.map(|entry| entry.path.clone())
})
@@ -558,10 +556,11 @@ impl NativeAgent {
self.project_context_needs_refresh.send(()).ok();
}
project::Event::WorktreeUpdatedEntries(_, items) => {
if items
.iter()
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
{
if items.iter().any(|(path, _, _)| {
RULES_FILE_NAMES
.iter()
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
}) {
self.project_context_needs_refresh.send(()).ok();
}
}

View File

@@ -47,8 +47,6 @@ task.workspace = true
tempfile.workspace = true
thiserror.workspace = true
ui.workspace = true
terminal.workspace = true
uuid.workspace = true
util.workspace = true
watch.workspace = true
workspace-hack.workspace = true

View File

@@ -9,7 +9,6 @@ use futures::io::BufReader;
use project::Project;
use project::agent_server_store::AgentServerCommand;
use serde::Deserialize;
use task::Shell;
use util::ResultExt as _;
use std::path::PathBuf;
@@ -20,9 +19,7 @@ use thiserror::Error;
use anyhow::{Context as _, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, SharedString, Task, WeakEntity};
use acp_thread::{AcpThread, AuthRequired, LoadError, TerminalProviderEvent};
use terminal::TerminalBuilder;
use terminal::terminal_settings::{AlternateScroll, CursorShape};
use acp_thread::{AcpThread, AuthRequired, LoadError};
#[derive(Debug, Error)]
#[error("Unsupported version")]
@@ -82,7 +79,7 @@ impl AcpConnection {
is_remote: bool,
cx: &mut AsyncApp,
) -> Result<Self> {
let mut child = util::command::new_smol_command(&command.path);
let mut child = util::command::new_smol_command(command.path);
child
.args(command.args.iter().map(|arg| arg.as_str()))
.envs(command.env.iter().flatten())
@@ -97,11 +94,6 @@ impl AcpConnection {
let stdout = child.stdout.take().context("Failed to take stdout")?;
let stdin = child.stdin.take().context("Failed to take stdin")?;
let stderr = child.stderr.take().context("Failed to take stderr")?;
log::info!(
"Spawning external agent server: {:?}, {:?}",
command.path,
command.args
);
log::trace!("Spawned (pid: {})", child.id());
let sessions = Rc::new(RefCell::new(HashMap::default()));
@@ -708,100 +700,10 @@ impl acp::Client for ClientDelegate {
}
}
// Clone so we can inspect meta both before and after handing off to the thread
let update_clone = notification.update.clone();
// Pre-handle: if a ToolCall carries terminal_info, create/register a display-only terminal.
if let acp::SessionUpdate::ToolCall(tc) = &update_clone {
if let Some(meta) = &tc.meta {
if let Some(terminal_info) = meta.get("terminal_info") {
if let Some(id_str) = terminal_info.get("terminal_id").and_then(|v| v.as_str())
{
let terminal_id = acp::TerminalId(id_str.into());
let cwd = terminal_info
.get("cwd")
.and_then(|v| v.as_str().map(PathBuf::from));
// Create a minimal display-only lower-level terminal and register it.
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
let builder = TerminalBuilder::new_display_only(
CursorShape::default(),
AlternateScroll::On,
None,
0,
)?;
let lower = cx.new(|cx| builder.subscribe(cx));
thread.on_terminal_provider_event(
TerminalProviderEvent::Created {
terminal_id: terminal_id.clone(),
label: tc.title.clone(),
cwd,
output_byte_limit: None,
terminal: lower,
},
cx,
);
anyhow::Ok(())
});
}
}
}
}
// Forward the update to the acp_thread as usual.
session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.handle_session_update(notification.update.clone(), cx)
thread.handle_session_update(notification.update, cx)
})??;
// Post-handle: stream terminal output/exit if present on ToolCallUpdate meta.
if let acp::SessionUpdate::ToolCallUpdate(tcu) = &update_clone {
if let Some(meta) = &tcu.meta {
if let Some(term_out) = meta.get("terminal_output") {
if let Some(id_str) = term_out.get("terminal_id").and_then(|v| v.as_str()) {
let terminal_id = acp::TerminalId(id_str.into());
if let Some(s) = term_out.get("data").and_then(|v| v.as_str()) {
let data = s.as_bytes().to_vec();
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Output {
terminal_id: terminal_id.clone(),
data,
},
cx,
);
});
}
}
}
// terminal_exit
if let Some(term_exit) = meta.get("terminal_exit") {
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
let terminal_id = acp::TerminalId(id_str.into());
let status = acp::TerminalExitStatus {
exit_code: term_exit
.get("exit_code")
.and_then(|v| v.as_u64())
.map(|i| i as u32),
signal: term_exit
.get("signal")
.and_then(|v| v.as_str().map(|s| s.to_string())),
meta: None,
};
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
thread.on_terminal_provider_event(
TerminalProviderEvent::Exit {
terminal_id: terminal_id.clone(),
status,
},
cx,
);
});
}
}
}
}
Ok(())
}
@@ -809,68 +711,25 @@ impl acp::Client for ClientDelegate {
&self,
args: acp::CreateTerminalRequest,
) -> Result<acp::CreateTerminalResponse, acp::Error> {
let thread = self.session_thread(&args.session_id)?;
let project = thread.read_with(&self.cx, |thread, _cx| thread.project().clone())?;
let mut env = if let Some(dir) = &args.cwd {
project
.update(&mut self.cx.clone(), |project, cx| {
project.directory_environment(&task::Shell::System, dir.clone().into(), cx)
})?
.await
.unwrap_or_default()
} else {
Default::default()
};
for var in args.env {
env.insert(var.name, var.value);
}
// Use remote shell or default system shell, as appropriate
let shell = project
.update(&mut self.cx.clone(), |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
.map(Shell::Program)
})?
.unwrap_or(task::Shell::System);
let (task_command, task_args) = task::ShellBuilder::new(&shell)
.redirect_stdin_to_dev_null()
.build(Some(args.command.clone()), &args.args);
let terminal_entity = project
.update(&mut self.cx.clone(), |project, cx| {
project.create_terminal_task(
task::SpawnInTerminal {
command: Some(task_command),
args: task_args,
cwd: args.cwd.clone(),
env,
..Default::default()
},
let terminal = self
.session_thread(&args.session_id)?
.update(&mut self.cx.clone(), |thread, cx| {
thread.create_terminal(
args.command,
args.args,
args.env,
args.cwd,
args.output_byte_limit,
cx,
)
})?
.await?;
// Register with renderer
let terminal_entity = thread.update(&mut self.cx.clone(), |thread, cx| {
thread.register_terminal_created(
acp::TerminalId(uuid::Uuid::new_v4().to_string().into()),
format!("{} {}", args.command, args.args.join(" ")),
args.cwd.clone(),
args.output_byte_limit,
terminal_entity,
cx,
)
})?;
let terminal_id =
terminal_entity.read_with(&self.cx, |terminal, _| terminal.id().clone())?;
Ok(acp::CreateTerminalResponse {
terminal_id,
meta: None,
})
Ok(
terminal.read_with(&self.cx, |terminal, _| acp::CreateTerminalResponse {
terminal_id: terminal.id().clone(),
meta: None,
})?,
)
}
async fn kill_terminal_command(

View File

@@ -151,7 +151,7 @@ impl Default for AgentProfileId {
}
impl Settings for AgentSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let agent = content.agent.clone().unwrap();
Self {
enabled: agent.enabled.unwrap(),

View File

@@ -203,7 +203,7 @@ impl EntryViewState {
self.entries.drain(range);
}
pub fn agent_ui_font_size_changed(&mut self, cx: &mut App) {
pub fn agent_font_size_changed(&mut self, cx: &mut App) {
for entry in self.entries.iter() {
match entry {
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
@@ -387,7 +387,7 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
font_size: Some(
TextSize::Small
.rems(cx)
.to_pixels(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
.into(),
),
..Default::default()
@@ -414,6 +414,7 @@ mod tests {
use project::Project;
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use theme::ThemeSettings;
use util::path;
use workspace::Workspace;
@@ -543,7 +544,7 @@ mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
});

View File

@@ -290,18 +290,18 @@ impl MessageEditor {
let snapshot = self
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot().as_singleton() else {
let Some((excerpt_id, _, _)) = snapshot.buffer_snapshot.as_singleton() else {
return Task::ready(());
};
let Some(start_anchor) = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_in_excerpt(*excerpt_id, start)
else {
return Task::ready(());
};
let end_anchor = snapshot
.buffer_snapshot()
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot()) + content_len + 1);
.buffer_snapshot
.anchor_before(start_anchor.to_offset(&snapshot.buffer_snapshot) + content_len + 1);
let crease = if let MentionUri::File { abs_path } = &mention_uri
&& let Some(extension) = abs_path.extension()
@@ -718,7 +718,7 @@ impl MessageEditor {
continue;
};
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot());
let crease_range = crease.range().to_offset(&snapshot.buffer_snapshot);
if crease_range.start > ix {
//todo(): Custom slash command ContentBlock?
// let chunk = if prevent_slash_commands
@@ -865,11 +865,11 @@ impl MessageEditor {
self.editor.update(cx, |message_editor, cx| {
let snapshot = message_editor.snapshot(window, cx);
let (excerpt_id, _, buffer_snapshot) =
snapshot.buffer_snapshot().as_singleton().unwrap();
snapshot.buffer_snapshot.as_singleton().unwrap();
let text_anchor = buffer_snapshot.anchor_before(buffer_snapshot.len());
let multibuffer_anchor = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_in_excerpt(*excerpt_id, text_anchor);
message_editor.edit(
[(
@@ -1299,7 +1299,7 @@ impl Render for MessageEditor {
font_family: settings.buffer_font.family.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_features: settings.buffer_font.features.clone(),
font_size: settings.agent_buffer_font_size(cx).into(),
font_size: settings.buffer_font_size(cx).into(),
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
};
@@ -1550,7 +1550,7 @@ impl MentionSet {
fn remove_invalid(&mut self, snapshot: EditorSnapshot) {
for (crease_id, crease) in snapshot.crease_snapshot.creases() {
if !crease.range().start.is_valid(&snapshot.buffer_snapshot()) {
if !crease.range().start.is_valid(&snapshot.buffer_snapshot) {
self.mentions.remove(&crease_id);
}
}

View File

@@ -26,7 +26,7 @@ use gpui::{
CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length,
ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task,
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div,
ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between,
ease_in_out, linear_color_stop, linear_gradient, list, point, prelude::*, pulsating_between,
};
use language::Buffer;
@@ -289,9 +289,8 @@ pub struct AcpThreadView {
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
is_loading_contents: bool,
new_server_version_available: Option<SharedString>,
resume_thread_metadata: Option<DbThreadMetadata>,
_cancel_task: Option<Task<()>>,
_subscriptions: [Subscription; 5],
_subscriptions: [Subscription; 4],
}
enum ThreadState {
@@ -381,17 +380,11 @@ impl AcpThreadView {
)
});
let agent_server_store = project.read(cx).agent_server_store().clone();
let subscriptions = [
cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed),
cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed),
cx.observe_global_in::<SettingsStore>(window, Self::agent_font_size_changed),
cx.observe_global_in::<AgentFontSize>(window, Self::agent_font_size_changed),
cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event),
cx.subscribe_in(
&agent_server_store,
window,
Self::handle_agent_servers_updated,
),
];
Self {
@@ -399,14 +392,7 @@ impl AcpThreadView {
workspace: workspace.clone(),
project: project.clone(),
entry_view_state,
thread_state: Self::initial_state(
agent.clone(),
resume_thread.clone(),
workspace.clone(),
project.clone(),
window,
cx,
),
thread_state: Self::initial_state(agent, resume_thread, workspace, project, window, cx),
login: None,
message_editor,
model_selector: None,
@@ -435,14 +421,13 @@ impl AcpThreadView {
_cancel_task: None,
focus_handle: cx.focus_handle(),
new_server_version_available: None,
resume_thread_metadata: resume_thread,
}
}
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.thread_state = Self::initial_state(
self.agent.clone(),
self.resume_thread_metadata.clone(),
None,
self.workspace.clone(),
self.project.clone(),
window,
@@ -592,6 +577,31 @@ impl AcpThreadView {
AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
// Proactively surface Authentication Required if the agent advertises auth methods.
if let Some(acp_conn) = thread
.read(cx)
.connection()
.clone()
.downcast::<agent_servers::AcpConnection>()
{
let methods = acp_conn.auth_methods();
if !methods.is_empty() {
// Immediately transition to auth-required UI, but defer to avoid re-entrant update.
let err = AuthRequired {
description: None,
provider_id: None,
};
let this_weak = cx.weak_entity();
let agent = agent.clone();
let connection = thread.read(cx).connection().clone();
window.defer(cx, move |window, cx| {
Self::handle_auth_required(
this_weak, err, agent, connection, window, cx,
);
});
}
}
this.model_selector = thread
.read(cx)
.connection()
@@ -790,25 +800,6 @@ impl AcpThreadView {
cx.notify();
}
fn handle_agent_servers_updated(
&mut self,
_agent_server_store: &Entity<project::AgentServerStore>,
_event: &project::AgentServersUpdated,
window: &mut Window,
cx: &mut Context<Self>,
) {
// If we're in a LoadError state OR have a thread_error set (which can happen
// when agent.connect() fails during loading), retry loading the thread.
// This handles the case where a thread is restored before authentication completes.
let should_retry =
matches!(&self.thread_state, ThreadState::LoadError(_)) || self.thread_error.is_some();
if should_retry {
self.thread_error = None;
self.reset(window, cx);
}
}
pub fn workspace(&self) -> &WeakEntity<Workspace> {
&self.workspace
}
@@ -2750,7 +2741,7 @@ impl AcpThreadView {
let working_dir = working_dir
.as_ref()
.map(|path| path.display().to_string())
.map(|path| format!("{}", path.display()))
.unwrap_or_else(|| "current directory".to_string());
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
@@ -3388,12 +3379,6 @@ impl AcpThreadView {
.into_any_element()
}
fn activity_bar_bg(&self, cx: &Context<Self>) -> Hsla {
let editor_bg_color = cx.theme().colors().editor_background;
let active_color = cx.theme().colors().element_selected;
editor_bg_color.blend(active_color.opacity(0.3))
}
fn render_activity_bar(
&self,
thread_entity: &Entity<AcpThread>,
@@ -3409,6 +3394,10 @@ impl AcpThreadView {
return None;
}
let editor_bg_color = cx.theme().colors().editor_background;
let active_color = cx.theme().colors().element_selected;
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
// Temporarily always enable ACP edit controls. This is temporary, to lessen the
// impact of a nasty bug that causes them to sometimes be disabled when they shouldn't
// be, which blocks you from being able to accept or reject edits. This switches the
@@ -3419,7 +3408,7 @@ impl AcpThreadView {
v_flex()
.mt_1()
.mx_2()
.bg(self.activity_bar_bg(cx))
.bg(bg_edit_files_disclosure)
.border_1()
.border_b_0()
.border_color(cx.theme().colors().border)
@@ -3460,33 +3449,27 @@ impl AcpThreadView {
.into()
}
fn render_plan_summary(
&self,
plan: &Plan,
window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
fn render_plan_summary(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
let stats = plan.stats();
let title = if let Some(entry) = stats.in_progress_entry
&& !self.plan_expanded
{
h_flex()
.cursor_default()
.relative()
.w_full()
.cursor_default()
.gap_1()
.truncate()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.justify_between()
.child(
Label::new("Current:")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(
div()
.text_xs()
.text_color(cx.theme().colors().text_muted)
.line_clamp(1)
h_flex()
.gap_1()
.child(
Label::new("Current:")
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(MarkdownElement::new(
entry.content.clone(),
plan_label_markdown_style(&entry.status, window, cx),
@@ -3494,23 +3477,10 @@ impl AcpThreadView {
)
.when(stats.pending > 0, |this| {
this.child(
h_flex()
.absolute()
.top_0()
.right_0()
.h_full()
.child(div().min_w_8().h_full().bg(linear_gradient(
90.,
linear_color_stop(self.activity_bar_bg(cx), 1.),
linear_color_stop(self.activity_bar_bg(cx).opacity(0.2), 0.),
)))
.child(
div().pr_0p5().bg(self.activity_bar_bg(cx)).child(
Label::new(format!("{} left", stats.pending))
.size(LabelSize::Small)
.color(Color::Muted),
),
),
Label::new(format!("{} left", stats.pending))
.size(LabelSize::Small)
.color(Color::Muted)
.mr_1(),
)
})
} else {
@@ -3540,19 +3510,23 @@ impl AcpThreadView {
};
h_flex()
.id("plan_summary")
.p_1()
.w_full()
.gap_1()
.justify_between()
.when(self.plan_expanded, |this| {
this.border_b_1().border_color(cx.theme().colors().border)
})
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
.child(title)
.on_click(cx.listener(|this, _, _, cx| {
this.plan_expanded = !this.plan_expanded;
cx.notify();
}))
.child(
h_flex()
.id("plan_summary")
.w_full()
.gap_1()
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
.child(title)
.on_click(cx.listener(|this, _, _, cx| {
this.plan_expanded = !this.plan_expanded;
cx.notify();
})),
)
}
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
@@ -3801,7 +3775,7 @@ impl AcpThreadView {
.id(("file-name", index))
.pr_8()
.gap_1p5()
.w_full()
.max_w_full()
.overflow_x_scroll()
.child(file_icon)
.child(h_flex().gap_0p5().children(file_name).children(file_path))
@@ -4953,9 +4927,9 @@ impl AcpThreadView {
)
}
fn agent_ui_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
fn agent_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
self.entry_view_state.update(cx, |entry_view_state, cx| {
entry_view_state.agent_ui_font_size_changed(cx);
entry_view_state.agent_font_size_changed(cx);
});
}
@@ -5585,23 +5559,23 @@ fn default_markdown_style(
}),
code_block: StyleRefinement {
padding: EdgesRefinement {
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
},
margin: EdgesRefinement {
top: Some(Length::Definite(px(8.).into())),
left: Some(Length::Definite(px(0.).into())),
right: Some(Length::Definite(px(0.).into())),
bottom: Some(Length::Definite(px(12.).into())),
top: Some(Length::Definite(Pixels(8.).into())),
left: Some(Length::Definite(Pixels(0.).into())),
right: Some(Length::Definite(Pixels(0.).into())),
bottom: Some(Length::Definite(Pixels(12.).into())),
},
border_style: Some(BorderStyle::Solid),
border_widths: EdgesRefinement {
top: Some(AbsoluteLength::Pixels(px(1.))),
left: Some(AbsoluteLength::Pixels(px(1.))),
right: Some(AbsoluteLength::Pixels(px(1.))),
bottom: Some(AbsoluteLength::Pixels(px(1.))),
top: Some(AbsoluteLength::Pixels(Pixels(1.))),
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
},
border_color: Some(colors.border_variant),
background: Some(colors.editor_background.into()),
@@ -6086,7 +6060,7 @@ pub(crate) mod tests {
Project::init_settings(cx);
AgentSettings::register(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
release_channel::init(SemanticVersion::default(), cx);
EditorSettings::register(cx);
prompt_store::init(cx)

View File

@@ -562,6 +562,10 @@ impl Item for AgentDiffPane {
self.editor.for_each_project_item(cx, f)
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,
@@ -846,7 +850,7 @@ fn render_diff_hunk_controls(
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let position =
hunk_range.end.to_point(&snapshot.buffer_snapshot());
hunk_range.end.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
position,
@@ -882,7 +886,7 @@ fn render_diff_hunk_controls(
editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let point =
hunk_range.start.to_point(&snapshot.buffer_snapshot());
hunk_range.start.to_point(&snapshot.buffer_snapshot);
editor.go_to_hunk_before_or_after_position(
&snapshot,
point,
@@ -1814,6 +1818,7 @@ mod tests {
use serde_json::json;
use settings::{Settings, SettingsStore};
use std::{path::Path, rc::Rc};
use theme::ThemeSettings;
use util::path;
#[gpui::test]
@@ -1826,7 +1831,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
});
@@ -1978,7 +1983,7 @@ mod tests {
AgentSettings::register(cx);
prompt_store::init(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
workspace::register_project_item::<Editor>(cx);

View File

@@ -48,8 +48,8 @@ use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use fs::Fs;
use gpui::{
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, ReadGlobal as _, Subscription, Task,
UpdateGlobal, WeakEntity, prelude::*,
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
WeakEntity, prelude::*,
};
use language::LanguageRegistry;
use language_model::{ConfigurationError, LanguageModelRegistry};
@@ -519,14 +519,6 @@ impl AgentPanel {
cx,
)
});
if SettingsStore::global(cx)
.get::<DisableAiSettings>(None)
.disable_ai
{
return panel;
}
panel.as_mut(cx).loading = true;
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
@@ -1116,15 +1108,15 @@ impl AgentPanel {
WhichFontSize::AgentFont => {
if persist {
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
let agent_ui_font_size =
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
let agent_font_size =
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
let _ = settings
.theme
.agent_ui_font_size
.insert(theme::clamp_font_size(agent_ui_font_size).into());
.agent_font_size
.insert(theme::clamp_font_size(agent_font_size).into());
});
} else {
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
theme::adjust_agent_font_size(cx, |size| size + delta);
}
}
WhichFontSize::BufferFont => {
@@ -1144,10 +1136,10 @@ impl AgentPanel {
) {
if action.persist {
update_settings_file(self.fs.clone(), cx, move |settings, _| {
settings.theme.agent_ui_font_size = None;
settings.theme.agent_font_size = None;
});
} else {
theme::reset_agent_ui_font_size(cx);
theme::reset_agent_font_size(cx);
}
}
@@ -2578,7 +2570,7 @@ impl Render for AgentPanel {
match self.active_view.which_font_size_used() {
WhichFontSize::AgentFont => {
WithRemSize::new(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
.size_full()
.child(content)
.into_any()

View File

@@ -18,9 +18,7 @@ use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::RowExt;
use editor::SelectionEffects;
use editor::scroll::ScrollOffset;
use editor::{
Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
@@ -382,7 +380,7 @@ impl InlineAssistant {
if let Some(editor_assists) = self.assists_by_editor.get(&editor.downgrade()) {
for assist_id in &editor_assists.assist_ids {
let assist = &self.assists[assist_id];
let range = assist.range.to_point(&snapshot.buffer_snapshot());
let range = assist.range.to_point(&snapshot.buffer_snapshot);
if range.start.row <= newest_selection.start.row
&& newest_selection.end.row <= range.end.row
{
@@ -402,16 +400,16 @@ impl InlineAssistant {
selection.end.row -= 1;
}
selection.end.column = snapshot
.buffer_snapshot()
.buffer_snapshot
.line_len(MultiBufferRow(selection.end.row));
} else if let Some(fold) =
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
{
selection.start = fold.range().start;
selection.end = fold.range().end;
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot().max_row() {
if MultiBufferRow(selection.end.row) < snapshot.buffer_snapshot.max_row() {
let chars = snapshot
.buffer_snapshot()
.buffer_snapshot
.chars_at(Point::new(selection.end.row + 1, 0));
for c in chars {
@@ -427,7 +425,7 @@ impl InlineAssistant {
{
selection.end.row += 1;
selection.end.column = snapshot
.buffer_snapshot()
.buffer_snapshot
.line_len(MultiBufferRow(selection.end.row));
}
}
@@ -447,7 +445,7 @@ impl InlineAssistant {
}
selections.push(selection);
}
let snapshot = &snapshot.buffer_snapshot();
let snapshot = &snapshot.buffer_snapshot;
let newest_selection = newest_selection.unwrap();
let mut codegen_ranges = Vec::new();
@@ -746,7 +744,7 @@ impl InlineAssistant {
let scroll_bottom = scroll_top + editor.visible_line_count().unwrap_or(0.);
editor_assists.scroll_lock = editor
.row_for_block(decorations.prompt_block_id, cx)
.map(|row| row.as_f64())
.map(|row| row.0 as f32)
.filter(|prompt_row| (scroll_top..scroll_bottom).contains(&prompt_row))
.map(|prompt_row| InlineAssistScrollLock {
assist_id,
@@ -912,9 +910,7 @@ impl InlineAssistant {
editor.update(cx, |editor, cx| {
let scroll_position = editor.scroll_position(cx);
let target_scroll_top = editor
.row_for_block(decorations.prompt_block_id, cx)?
.as_f64()
let target_scroll_top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32
- scroll_lock.distance_from_top;
if target_scroll_top != scroll_position.y {
editor.set_scroll_position(point(scroll_position.x, target_scroll_top), window, cx);
@@ -963,9 +959,8 @@ impl InlineAssistant {
if let Some(decorations) = assist.decorations.as_ref() {
let distance_from_top = editor.update(cx, |editor, cx| {
let scroll_top = editor.scroll_position(cx).y;
let prompt_row = editor
.row_for_block(decorations.prompt_block_id, cx)?
.0 as ScrollOffset;
let prompt_row =
editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
Some(prompt_row - scroll_top)
});
@@ -1197,8 +1192,8 @@ impl InlineAssistant {
let mut scroll_target_range = None;
if let Some(decorations) = assist.decorations.as_ref() {
scroll_target_range = maybe!({
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f64;
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f64;
let top = editor.row_for_block(decorations.prompt_block_id, cx)?.0 as f32;
let bottom = editor.row_for_block(decorations.end_block_id, cx)?.0 as f32;
Some((top, bottom))
});
if scroll_target_range.is_none() {
@@ -1212,15 +1207,15 @@ impl InlineAssistant {
.start
.to_display_point(&snapshot.display_snapshot)
.row();
let top = start_row.0 as ScrollOffset;
let top = start_row.0 as f32;
let bottom = top + 1.0;
(top, bottom)
});
let mut scroll_target_top = scroll_target_range.0;
let mut scroll_target_bottom = scroll_target_range.1;
scroll_target_top -= editor.vertical_scroll_margin() as ScrollOffset;
scroll_target_bottom += editor.vertical_scroll_margin() as ScrollOffset;
scroll_target_top -= editor.vertical_scroll_margin() as f32;
scroll_target_bottom += editor.vertical_scroll_margin() as f32;
let height_in_lines = editor.visible_line_count().unwrap_or(0.);
let scroll_top = editor.scroll_position(cx).y;
@@ -1548,7 +1543,7 @@ struct EditorInlineAssists {
struct InlineAssistScrollLock {
assist_id: InlineAssistId,
distance_from_top: ScrollOffset,
distance_from_top: f32,
}
impl EditorInlineAssists {

View File

@@ -15,8 +15,7 @@ use std::{
sync::{Arc, atomic::AtomicBool},
};
use ui::{
DocumentationAside, DocumentationEdge, DocumentationSide, HighlightedLabel, LabelSize,
ListItem, ListItemSpacing, PopoverMenuHandle, TintColor, Tooltip, prelude::*,
HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle, TintColor, Tooltip, prelude::*,
};
/// Trait for types that can provide and manage agent profiles
@@ -87,8 +86,8 @@ impl ProfileSelector {
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.width(rems(18.))
.max_height(Some(rems(20.).into()))
.width(rems(20.))
.max_height(Some(rems(16.).into()))
});
self.picker = Some(picker);
@@ -537,10 +536,18 @@ impl PickerDelegate for ProfilePickerDelegate {
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(HighlightedLabel::new(
candidate.name.clone(),
entry.positions.clone(),
))
.child(
v_flex()
.child(HighlightedLabel::new(
candidate.name.clone(),
entry.positions.clone(),
))
.when_some(Self::documentation(candidate), |this, doc| {
this.child(
Label::new(doc).size(LabelSize::Small).color(Color::Muted),
)
}),
)
.when(is_active, |this| {
this.end_slot(
div()
@@ -554,36 +561,6 @@ impl PickerDelegate for ProfilePickerDelegate {
}
}
fn documentation_aside(
&self,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<DocumentationAside> {
use std::rc::Rc;
let entry = match self.filtered_entries.get(self.selected_index)? {
ProfilePickerEntry::Profile(entry) => entry,
ProfilePickerEntry::Header(_) => return None,
};
let candidate = self.candidates.get(entry.candidate_index)?;
let docs_aside = Self::documentation(candidate)?.to_string();
let settings = AgentSettings::get_global(cx);
let side = match settings.dock {
settings::DockPosition::Left => DocumentationSide::Right,
settings::DockPosition::Bottom | settings::DockPosition::Right => {
DocumentationSide::Left
}
};
Some(DocumentationAside {
side,
edge: DocumentationEdge::Top,
render: Rc::new(move |_| Label::new(docs_aside.clone()).into_any_element()),
})
}
fn render_footer(
&self,
_: &mut Window,

View File

@@ -17,7 +17,6 @@ use editor::{
BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
RenderBlock, ToDisplayPoint,
},
scroll::ScrollOffset,
};
use editor::{FoldPlaceholder, display_map::CreaseId};
use fs::Fs;
@@ -109,7 +108,7 @@ pub enum InsertDraggedFiles {
#[derive(Copy, Clone, Debug, PartialEq)]
struct ScrollPosition {
offset_before_cursor: gpui::Point<ScrollOffset>,
offset_before_cursor: gpui::Point<f32>,
cursor: Anchor,
}
@@ -632,7 +631,7 @@ impl TextThreadEditor {
let snapshot = editor.snapshot(window, cx);
let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
let scroll_top =
cursor_point.row().as_f64() - scroll_position.offset_before_cursor.y;
cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
editor.set_scroll_position(
point(scroll_position.offset_before_cursor.x, scroll_top),
window,
@@ -980,7 +979,7 @@ impl TextThreadEditor {
let cursor_row = cursor
.to_display_point(&snapshot.display_snapshot)
.row()
.as_f64();
.as_f32();
let scroll_position = editor
.scroll_manager
.anchor()

View File

@@ -16,8 +16,8 @@ anyhow.workspace = true
futures.workspace = true
gpui.workspace = true
net.workspace = true
proto.workspace = true
smol.workspace = true
log.workspace = true
tempfile.workspace = true
util.workspace = true
workspace-hack.workspace = true
@@ -25,6 +25,3 @@ zeroize.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
windows.workspace = true
[package.metadata.cargo-machete]
ignored = ["log"]

View File

@@ -1,16 +1,10 @@
mod encrypted_password;
pub use encrypted_password::{EncryptedPassword, IKnowWhatIAmDoingAndIHaveReadTheDocs};
pub use encrypted_password::{EncryptedPassword, ProcessExt};
use net::async_net::UnixListener;
use smol::lock::Mutex;
use util::fs::make_file_executable;
use std::ffi::OsStr;
use std::ops::ControlFlow;
use std::sync::Arc;
#[cfg(target_os = "windows")]
use std::sync::OnceLock;
use std::time::Duration;
use std::{ffi::OsStr, time::Duration};
use anyhow::{Context as _, Result};
use futures::channel::{mpsc, oneshot};
@@ -20,13 +14,9 @@ use futures::{
};
use gpui::{AsyncApp, BackgroundExecutor, Task};
use smol::fs;
use util::{ResultExt as _, debug_panic, maybe, paths::PathExt};
use util::ResultExt as _;
/// Path to the program used for askpass
///
/// On Unix and remote servers, this defaults to the current executable
/// On Windows, this is set to the CLI variant of zed
static ASKPASS_PROGRAM: OnceLock<std::path::PathBuf> = OnceLock::new();
use crate::encrypted_password::decrypt;
#[derive(PartialEq, Eq)]
pub enum AskPassResult {
@@ -36,7 +26,6 @@ pub enum AskPassResult {
pub struct AskPassDelegate {
tx: mpsc::UnboundedSender<(String, oneshot::Sender<EncryptedPassword>)>,
executor: BackgroundExecutor,
_task: Task<()>,
}
@@ -54,27 +43,24 @@ impl AskPassDelegate {
password_prompt(prompt, channel, cx);
}
});
Self {
tx,
_task: task,
executor: cx.background_executor().clone(),
}
Self { tx, _task: task }
}
pub fn ask_password(&mut self, prompt: String) -> Task<Option<EncryptedPassword>> {
let mut this_tx = self.tx.clone();
self.executor.spawn(async move {
let (tx, rx) = oneshot::channel();
this_tx.send((prompt, tx)).await.ok()?;
rx.await.ok()
})
pub async fn ask_password(&mut self, prompt: String) -> Result<EncryptedPassword> {
let (tx, rx) = oneshot::channel();
self.tx.send((prompt, tx)).await?;
Ok(rx.await?)
}
}
pub struct AskPassSession {
#[cfg(not(target_os = "windows"))]
script_path: std::path::PathBuf,
#[cfg(target_os = "windows")]
askpass_helper: String,
#[cfg(target_os = "windows")]
secret: std::sync::Arc<OnceLock<EncryptedPassword>>,
askpass_task: PasswordProxy,
_askpass_task: Task<()>,
askpass_opened_rx: Option<oneshot::Receiver<()>>,
askpass_kill_master_rx: Option<oneshot::Receiver<()>>,
}
@@ -89,57 +75,101 @@ impl AskPassSession {
/// You must retain this session until the master process exits.
#[must_use]
pub async fn new(executor: &BackgroundExecutor, mut delegate: AskPassDelegate) -> Result<Self> {
use net::async_net::UnixListener;
use util::fs::make_file_executable;
#[cfg(target_os = "windows")]
let secret = std::sync::Arc::new(OnceLock::new());
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
let askpass_socket = temp_dir.path().join("askpass.sock");
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
let askpass_opened_tx = Arc::new(Mutex::new(Some(askpass_opened_tx)));
let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
let zed_cli_path =
util::get_shell_safe_zed_cli_path().context("getting zed-cli path for askpass")?;
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
let kill_tx = Arc::new(Mutex::new(Some(askpass_kill_master_tx)));
let mut kill_tx = Some(askpass_kill_master_tx);
#[cfg(target_os = "windows")]
let askpass_secret = secret.clone();
let get_password = {
let executor = executor.clone();
let askpass_task = executor.spawn(async move {
let mut askpass_opened_tx = Some(askpass_opened_tx);
move |prompt| {
let prompt = delegate.ask_password(prompt);
let kill_tx = kill_tx.clone();
let askpass_opened_tx = askpass_opened_tx.clone();
#[cfg(target_os = "windows")]
let askpass_secret = askpass_secret.clone();
executor.spawn(async move {
if let Some(askpass_opened_tx) = askpass_opened_tx.lock().await.take() {
askpass_opened_tx.send(()).ok();
while let Ok((mut stream, _)) = listener.accept().await {
if let Some(askpass_opened_tx) = askpass_opened_tx.take() {
askpass_opened_tx.send(()).ok();
}
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
buffer.clear();
}
let prompt = String::from_utf8_lossy(&buffer);
if let Some(password) = delegate
.ask_password(prompt.to_string())
.await
.context("getting askpass password")
.log_err()
{
#[cfg(target_os = "windows")]
{
askpass_secret.get_or_init(|| password.clone());
}
if let Some(password) = prompt.await {
#[cfg(target_os = "windows")]
{
_ = askpass_secret.set(password.clone());
}
ControlFlow::Continue(Ok(password))
} else {
if let Some(kill_tx) = kill_tx.lock().await.take() {
kill_tx.send(()).log_err();
}
ControlFlow::Break(())
if let Ok(decrypted) = decrypt(password) {
stream.write_all(decrypted.as_bytes()).await.log_err();
}
})
} else {
if let Some(kill_tx) = kill_tx.take() {
kill_tx.send(()).log_err();
}
// note: we expect the caller to drop this task when it's done.
// We need to keep the stream open until the caller is done to avoid
// spurious errors from ssh.
std::future::pending::<()>().await;
drop(stream);
}
}
};
let askpass_task = PasswordProxy::new(get_password, executor.clone()).await?;
drop(temp_dir)
});
// Create an askpass script that communicates back to this process.
let askpass_script = generate_askpass_script(&zed_cli_path, &askpass_socket);
fs::write(&askpass_script_path, askpass_script)
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
make_file_executable(&askpass_script_path).await?;
#[cfg(target_os = "windows")]
let askpass_helper = format!(
"powershell.exe -ExecutionPolicy Bypass -File {}",
askpass_script_path.display()
);
Ok(Self {
#[cfg(not(target_os = "windows"))]
script_path: askpass_script_path,
#[cfg(target_os = "windows")]
secret,
#[cfg(target_os = "windows")]
askpass_helper,
askpass_task,
_askpass_task: askpass_task,
askpass_kill_master_rx: Some(askpass_kill_master_rx),
askpass_opened_rx: Some(askpass_opened_rx),
})
}
#[cfg(not(target_os = "windows"))]
pub fn script_path(&self) -> impl AsRef<OsStr> {
&self.script_path
}
#[cfg(target_os = "windows")]
pub fn script_path(&self) -> impl AsRef<OsStr> {
&self.askpass_helper
}
// This will run the askpass task forever, resolving as many authentication requests as needed.
// The caller is responsible for examining the result of their own commands and cancelling this
// future when this is no longer needed. Note that this can only be called once, but due to the
@@ -171,109 +201,8 @@ impl AskPassSession {
pub fn get_password(&self) -> Option<EncryptedPassword> {
self.secret.get().cloned()
}
pub fn script_path(&self) -> impl AsRef<OsStr> {
self.askpass_task.script_path()
}
}
pub struct PasswordProxy {
_task: Task<()>,
#[cfg(not(target_os = "windows"))]
askpass_script_path: std::path::PathBuf,
#[cfg(target_os = "windows")]
askpass_helper: String,
}
impl PasswordProxy {
pub async fn new(
mut get_password: impl FnMut(String) -> Task<ControlFlow<(), Result<EncryptedPassword>>>
+ 'static
+ Send
+ Sync,
executor: BackgroundExecutor,
) -> Result<Self> {
let temp_dir = tempfile::Builder::new().prefix("zed-askpass").tempdir()?;
let askpass_socket = temp_dir.path().join("askpass.sock");
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
let current_exec =
std::env::current_exe().context("Failed to determine current zed executable path.")?;
let askpass_program = ASKPASS_PROGRAM
.get_or_init(|| current_exec)
.try_shell_safe()
.context("Failed to shell-escape Askpass program path.")?
.to_string();
// Create an askpass script that communicates back to this process.
let askpass_script = generate_askpass_script(&askpass_program, &askpass_socket);
let _task = executor.spawn(async move {
maybe!(async move {
let listener =
UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
while let Ok((mut stream, _)) = listener.accept().await {
let mut buffer = Vec::new();
let mut reader = BufReader::new(&mut stream);
if reader.read_until(b'\0', &mut buffer).await.is_err() {
buffer.clear();
}
let prompt = String::from_utf8_lossy(&buffer).into_owned();
let password = get_password(prompt).await;
match password {
ControlFlow::Continue(password) => {
if let Ok(password) = password
&& let Ok(decrypted) =
password.decrypt(IKnowWhatIAmDoingAndIHaveReadTheDocs)
{
stream.write_all(decrypted.as_bytes()).await.log_err();
}
}
ControlFlow::Break(()) => {
// note: we expect the caller to drop this task when it's done.
// We need to keep the stream open until the caller is done to avoid
// spurious errors from ssh.
std::future::pending::<()>().await;
drop(stream);
}
}
}
drop(temp_dir);
Result::<_, anyhow::Error>::Ok(())
})
.await
.log_err();
});
fs::write(&askpass_script_path, askpass_script)
.await
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
make_file_executable(&askpass_script_path).await?;
#[cfg(target_os = "windows")]
let askpass_helper = format!(
"powershell.exe -ExecutionPolicy Bypass -File {}",
askpass_script_path.display()
);
Ok(Self {
_task,
#[cfg(not(target_os = "windows"))]
askpass_script_path,
#[cfg(target_os = "windows")]
askpass_helper,
})
}
pub fn script_path(&self) -> impl AsRef<OsStr> {
#[cfg(not(target_os = "windows"))]
{
&self.askpass_script_path
}
#[cfg(target_os = "windows")]
{
&self.askpass_helper
}
}
}
/// The main function for when Zed is running in netcat mode for use in askpass.
/// Called from both the remote server binary and the zed binary in their respective main functions.
pub fn main(socket: &str) {
@@ -320,17 +249,12 @@ pub fn main(socket: &str) {
}
}
pub fn set_askpass_program(path: std::path::PathBuf) {
if ASKPASS_PROGRAM.set(path).is_err() {
debug_panic!("askpass program has already been set");
}
}
#[inline]
#[cfg(not(target_os = "windows"))]
fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Path) -> String {
fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
format!(
"{shebang}\n{print_args} | {askpass_program} --askpass={askpass_socket} 2> /dev/null \n",
"{shebang}\n{print_args} | {zed_cli} --askpass={askpass_socket} 2> /dev/null \n",
zed_cli = zed_cli_path,
askpass_socket = askpass_socket.display(),
print_args = "printf '%s\\0' \"$@\"",
shebang = "#!/bin/sh",
@@ -339,12 +263,13 @@ fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Pa
#[inline]
#[cfg(target_os = "windows")]
fn generate_askpass_script(askpass_program: &str, askpass_socket: &std::path::Path) -> String {
fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
format!(
r#"
$ErrorActionPreference = 'Stop';
($args -join [char]0) | & "{askpass_program}" --askpass={askpass_socket} 2> $null
($args -join [char]0) | & "{zed_cli}" --askpass={askpass_socket} 2> $null
"#,
zed_cli = zed_cli_path,
askpass_socket = askpass_socket.display(),
)
}

View File

@@ -21,6 +21,27 @@ type LengthWithoutPadding = u32;
#[derive(Clone)]
pub struct EncryptedPassword(Vec<u8>, LengthWithoutPadding);
pub trait ProcessExt {
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self;
}
impl ProcessExt for smol::process::Command {
fn encrypted_env(&mut self, name: &str, value: EncryptedPassword) -> &mut Self {
if let Ok(password) = decrypt(value) {
self.env(name, password);
}
self
}
}
impl TryFrom<EncryptedPassword> for proto::AskPassResponse {
type Error = anyhow::Error;
fn try_from(pw: EncryptedPassword) -> Result<Self, Self::Error> {
let pw = decrypt(pw)?;
Ok(Self { response: pw })
}
}
impl Drop for EncryptedPassword {
fn drop(&mut self) {
self.0.zeroize();
@@ -58,45 +79,38 @@ impl TryFrom<&str> for EncryptedPassword {
}
}
/// Read the docs for [EncryptedPassword]; please take care of not storing the plaintext string in memory for extended
/// periods of time.
pub struct IKnowWhatIAmDoingAndIHaveReadTheDocs;
impl EncryptedPassword {
pub fn decrypt(mut self, _: IKnowWhatIAmDoingAndIHaveReadTheDocs) -> Result<String> {
#[cfg(windows)]
{
use anyhow::Context;
use windows::Win32::Security::Cryptography::{
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS,
CryptUnprotectMemory,
pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result<String> {
#[cfg(windows)]
{
use anyhow::Context;
use windows::Win32::Security::Cryptography::{
CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, CryptUnprotectMemory,
};
assert_eq!(
password.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
0,
"Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
password.0.len(),
CRYPTPROTECTMEMORY_BLOCK_SIZE
);
if password.1 != 0 {
unsafe {
CryptUnprotectMemory(
password.0.as_mut_ptr() as _,
password.0.len().try_into()?,
CRYPTPROTECTMEMORY_SAME_PROCESS,
)
.context("while decrypting a SSH password")?
};
assert_eq!(
self.0.len() % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize,
0,
"Violated pre-condition (buffer size <{}> must be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE <{}>) for CryptUnprotectMemory.",
self.0.len(),
CRYPTPROTECTMEMORY_BLOCK_SIZE
);
if self.1 != 0 {
unsafe {
CryptUnprotectMemory(
self.0.as_mut_ptr() as _,
self.0.len().try_into()?,
CRYPTPROTECTMEMORY_SAME_PROCESS,
)
.context("while decrypting a SSH password")?
};
{
// Remove padding
_ = self.0.drain(self.1 as usize..);
}
{
// Remove padding
_ = password.0.drain(password.1 as usize..);
}
Ok(String::from_utf8(std::mem::take(&mut self.0))?)
}
#[cfg(not(windows))]
Ok(String::from_utf8(std::mem::take(&mut self.0))?)
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
}
#[cfg(not(windows))]
Ok(String::from_utf8(std::mem::take(&mut password.0))?)
}

View File

@@ -17,7 +17,7 @@ use editor::{
use futures::StreamExt;
use gpui::{
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task,
TextStyleRefinement, WeakEntity, pulsating_between,
TextStyleRefinement, WeakEntity, pulsating_between, px,
};
use indoc::formatdoc;
use language::{
@@ -1003,7 +1003,7 @@ impl ToolCard for EditFileToolCard {
font_size: Some(
TextSize::Small
.rems(cx)
.to_pixels(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
.into(),
),
..TextStyleRefinement::default()
@@ -1102,7 +1102,7 @@ impl ToolCard for EditFileToolCard {
.relative()
.h_full()
.when(!self.full_height_expanded, |editor_container| {
editor_container.max_h(COLLAPSED_LINES as f32 * editor_line_height)
editor_container.max_h(px(COLLAPSED_LINES as f32 * editor_line_height.0))
})
.overflow_hidden()
.border_t_1()

View File

@@ -18,7 +18,7 @@ use portable_pty::{CommandBuilder, PtySize, native_pty_system};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation};
use settings::Settings;
use std::{
env,
path::{Path, PathBuf},
@@ -32,8 +32,8 @@ use terminal_view::TerminalView;
use theme::ThemeSettings;
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
use util::{
ResultExt, get_default_system_shell_preferring_bash, markdown::MarkdownInlineCode,
size::format_file_size, time::duration_alt_display,
ResultExt, get_default_system_shell, markdown::MarkdownInlineCode, size::format_file_size,
time::duration_alt_display,
};
use workspace::Workspace;
@@ -122,27 +122,16 @@ impl Tool for TerminalTool {
let cwd = working_dir.clone();
let env = match &cwd {
Some(dir) => project.update(cx, |project, cx| {
let worktree = project.find_worktree(dir.as_path(), cx);
let shell = TerminalSettings::get(
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
worktree_id: worktree.read(cx).id(),
path: &path,
}),
cx,
)
.shell
.clone();
let shell = TerminalSettings::get_global(cx).shell.clone();
project.directory_environment(&shell, dir.as_path().into(), cx)
}),
None => Task::ready(None).shared(),
};
let shell = project
.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
})
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
let remote_shell = project.update(cx, |project, cx| {
project
.remote_client()
.and_then(|r| r.read(cx).default_system_shell())
});
let env = cx.spawn(async move |_| {
let mut env = env.await.unwrap_or_default();
@@ -155,9 +144,12 @@ impl Tool for TerminalTool {
let build_cmd = {
let input_command = input.command.clone();
move || {
ShellBuilder::new(&Shell::Program(shell))
.redirect_stdin_to_dev_null()
.build(Some(input_command), &[])
ShellBuilder::new(
remote_shell.as_deref(),
&Shell::Program(get_default_system_shell()),
)
.redirect_stdin_to_dev_null()
.build(Some(input_command.clone()), &[])
}
};
@@ -486,7 +478,7 @@ impl ToolCard for TerminalToolCard {
.as_ref()
.cloned()
.or_else(|| env::current_dir().ok())
.map(|path| path.display().to_string())
.map(|path| format!("{}", path.display()))
.unwrap_or_else(|| "current directory".to_string());
let header = h_flex()
@@ -704,6 +696,7 @@ mod tests {
use serde_json::json;
use settings::{Settings, SettingsStore};
use terminal::terminal_settings::TerminalSettings;
use theme::ThemeSettings;
use util::{ResultExt as _, test::TempTree};
use super::*;
@@ -718,7 +711,7 @@ mod tests {
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
ThemeSettings::register(cx);
TerminalSettings::register(cx);
EditorSettings::register(cx);
});

View File

@@ -42,7 +42,7 @@ pub struct AudioSettings {
/// Configuration of audio in Zed
impl Settings for AudioSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let audio = &content.audio.as_ref().unwrap();
AudioSettings {
rodio_audio: audio.rodio_audio.unwrap(),

View File

@@ -127,7 +127,7 @@ struct AutoUpdateSetting(bool);
///
/// Default: true
impl Settings for AutoUpdateSetting {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self(content.auto_update.unwrap())
}
}

View File

@@ -1,4 +1,5 @@
use std::{
os::windows::process::CommandExt,
path::Path,
time::{Duration, Instant},
};
@@ -6,6 +7,7 @@ use std::{
use anyhow::{Context as _, Result};
use windows::Win32::{
Foundation::{HWND, LPARAM, WPARAM},
System::Threading::CREATE_NEW_PROCESS_GROUP,
UI::WindowsAndMessaging::PostMessageW,
};
@@ -205,7 +207,9 @@ pub(crate) fn perform_update(app_dir: &Path, hwnd: Option<isize>, launch: bool)
}
if launch {
#[allow(clippy::disallowed_methods, reason = "doesn't run in the main binary")]
let _ = std::process::Command::new(app_dir.join("Zed.exe")).spawn();
let _ = std::process::Command::new(app_dir.join("Zed.exe"))
.creation_flags(CREATE_NEW_PROCESS_GROUP.0)
.spawn();
}
log::info!("Update completed successfully");
Ok(())

View File

@@ -1,3 +1,4 @@
use gpui::App;
use settings::Settings;
#[derive(Debug)]
@@ -7,11 +8,17 @@ pub struct CallSettings {
}
impl Settings for CallSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let call = content.calls.clone().unwrap();
CallSettings {
mute_on_join: call.mute_on_join.unwrap(),
share_on_join: call.share_on_join.unwrap(),
}
}
fn import_from_vscode(
_vscode: &settings::VsCodeSettings,
_current: &mut settings::SettingsContent,
) {
}
}

View File

@@ -731,15 +731,15 @@ mod windows {
Storage::FileSystem::{
CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING, WriteFile,
},
System::Threading::CreateMutexW,
System::Threading::{CREATE_NEW_PROCESS_GROUP, CreateMutexW},
},
core::HSTRING,
};
use crate::{Detect, InstalledApp};
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::{io, os::windows::process::CommandExt};
fn check_single_instance() -> bool {
let mutex = unsafe {
@@ -778,6 +778,7 @@ mod windows {
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
if check_single_instance() {
std::process::Command::new(self.0.clone())
.creation_flags(CREATE_NEW_PROCESS_GROUP.0)
.arg(ipc_url)
.spawn()?;
} else {

View File

@@ -101,7 +101,7 @@ pub struct ClientSettings {
}
impl Settings for ClientSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
if let Some(server_url) = &*ZED_SERVER_URL {
return Self {
server_url: server_url.clone(),
@@ -133,7 +133,7 @@ impl ProxySettings {
}
impl Settings for ProxySettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self {
proxy: content.proxy.clone(),
}
@@ -519,7 +519,7 @@ pub struct TelemetrySettings {
}
impl settings::Settings for TelemetrySettings {
fn from_settings(content: &SettingsContent) -> Self {
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
Self {
diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),

View File

@@ -1,3 +0,0 @@
alter table billing_subscriptions
add column token_spend_in_cents integer,
add column token_spend_in_cents_updated_at timestamp without time zone;

View File

@@ -323,8 +323,8 @@ fn assert_remote_selections(
let CollaboratorId::PeerId(peer_id) = s.collaborator_id else {
panic!("unexpected collaborator id");
};
let start = s.selection.start.to_offset(snapshot.buffer_snapshot());
let end = s.selection.end.to_offset(snapshot.buffer_snapshot());
let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
let user_id = collaborators.get(&peer_id).unwrap().user_id;
let participant_index = hub.user_participant_indices(cx).get(&user_id).copied();
(participant_index, start..end)

View File

@@ -2041,10 +2041,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
});
}
// This test started hanging on seed 2 after the theme settings
// PR. The hypothesis is that it's been buggy for a while, but got lucky
// on seeds.
#[ignore]
#[gpui::test(iterations = 10)]
async fn test_inlay_hint_refresh_is_forwarded(
cx_a: &mut TestAppContext,

View File

@@ -84,11 +84,7 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
diff.update(cx_b, |diff, cx| {
assert_eq!(
diff.excerpt_paths(cx),
vec![
rel_path("changed.txt").into_arc(),
rel_path("deleted.txt").into_arc(),
rel_path("created.txt").into_arc()
]
vec!["changed.txt", "deleted.txt", "created.txt"]
);
});
@@ -125,11 +121,7 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext)
diff.update(cx_b, |diff, cx| {
assert_eq!(
diff.excerpt_paths(cx),
vec![
rel_path("deleted.txt").into_arc(),
rel_path("unchanged.txt").into_arc(),
rel_path("created.txt").into_arc()
]
vec!["deleted.txt", "unchanged.txt", "created.txt"]
);
});
}

View File

@@ -183,10 +183,9 @@ pub async fn run_randomized_test<T: RandomizedTest>(
for (client, cx) in clients {
cx.update(|cx| {
let settings = cx.remove_global::<SettingsStore>();
let store = cx.remove_global::<SettingsStore>();
cx.clear_globals();
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
cx.set_global(store);
drop(client);
});
}

View File

@@ -172,7 +172,6 @@ impl TestServer {
}
let settings = SettingsStore::test(cx);
cx.set_global(settings);
theme::init(theme::LoadThemes::JustBase, cx);
release_channel::init(SemanticVersion::default(), cx);
client::init_settings(cx);
});

View File

@@ -248,7 +248,7 @@ impl ChannelView {
.editor
.update(cx, |editor, cx| editor.snapshot(window, cx));
if let Some(outline) = snapshot.buffer_snapshot().outline(None)
if let Some(outline) = snapshot.buffer_snapshot.outline(None)
&& let Some(item) = outline
.items
.iter()
@@ -305,7 +305,7 @@ impl ChannelView {
let mut closest_heading = None;
if let Some(outline) = snapshot.buffer_snapshot().outline(None) {
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
for item in outline.items {
if item.range.start.to_display_point(&snapshot) > position {
break;
@@ -508,6 +508,10 @@ impl Item for ChannelView {
}))
}
fn is_singleton(&self, _cx: &App) -> bool {
false
}
fn navigate(
&mut self,
data: Box<dyn Any>,

View File

@@ -18,7 +18,7 @@ pub struct NotificationPanelSettings {
}
impl Settings for CollaborationPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let panel = content.collaboration_panel.as_ref().unwrap();
Self {
@@ -30,7 +30,7 @@ impl Settings for CollaborationPanelSettings {
}
impl Settings for NotificationPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let panel = content.notification_panel.as_ref().unwrap();
return Self {
button: panel.button.unwrap(),

View File

@@ -1,10 +1,9 @@
[package]
name = "zed-collections"
name = "collections"
version = "0.1.0"
edition.workspace = true
publish = true
publish.workspace = true
license = "Apache-2.0"
description = "Standard collection type re-exports used by Zed and GPUI"
[lints]
workspace = true

View File

@@ -1,4 +1,5 @@
use dap_types::SteppingGranularity;
use gpui::App;
use settings::{Settings, SettingsContent};
pub struct DebuggerSettings {
@@ -33,7 +34,7 @@ pub struct DebuggerSettings {
}
impl Settings for DebuggerSettings {
fn from_settings(content: &SettingsContent) -> Self {
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
let content = content.debugger.clone().unwrap();
Self {
stepping_granularity: dap_granularity_from_settings(

View File

@@ -41,8 +41,8 @@ use serde_json::Value;
use settings::Settings;
use stack_frame_list::StackFrameList;
use task::{
BuildTaskDefinition, DebugScenario, Shell, ShellBuilder, SpawnInTerminal, TaskContext,
ZedDebugConfig, substitute_variables_in_str,
BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskContext, ZedDebugConfig,
substitute_variables_in_str,
};
use terminal_view::TerminalView;
use ui::{
@@ -988,7 +988,7 @@ impl RunningState {
(task, None)
}
};
let Some(mut task) = task_template.resolve_task("debug-build-task", &task_context) else {
let Some(task) = task_template.resolve_task("debug-build-task", &task_context) else {
anyhow::bail!("Could not resolve task variables within a debug scenario");
};
@@ -1025,11 +1025,7 @@ impl RunningState {
None
};
if let Some(remote_shell) = remote_shell && task.resolved.shell == Shell::System {
task.resolved.shell = Shell::Program(remote_shell);
}
let builder = ShellBuilder::new(&task.resolved.shell);
let builder = ShellBuilder::new(remote_shell.as_deref(), &task.resolved.shell);
let command_label = builder.command_label(task.resolved.command.as_deref().unwrap_or(""));
let (command, args) =
builder.build(task.resolved.command.clone(), &task.resolved.args);
@@ -1232,6 +1228,7 @@ impl RunningState {
terminal.read_with(cx, |terminal, _| {
terminal
.pty_info
.pid()
.map(|pid| pid.as_u32())
.context("Terminal was spawned but PID was not available")

View File

@@ -1213,7 +1213,7 @@ impl VariableList {
let weak = cx.weak_entity();
let focus_handle = self.focus_handle.clone();
let watcher_len = (f32::from(self.list_handle.content_size().width / 12.0).floor()) - 3.0;
let watcher_len = (self.list_handle.content_size().width.0 / 12.0).floor() - 3.0;
let watcher_len = watcher_len as usize;
div()

View File

@@ -59,7 +59,7 @@ impl StackTraceView {
editor
.snapshot(window, cx)
.buffer_snapshot()
.buffer_snapshot
.excerpt_containing(position..position)
.map(|excerpt| excerpt.id())
});
@@ -259,7 +259,7 @@ impl StackTraceView {
let mut is_first = true;
for (_, highlight) in self.highlights.iter().skip(active_idx) {
let position = highlight.to_point(&snapshot.buffer_snapshot());
let position = highlight.to_point(&snapshot.buffer_snapshot);
let color = if is_first {
is_first = false;
first_color
@@ -268,11 +268,11 @@ impl StackTraceView {
};
let start = snapshot
.buffer_snapshot()
.buffer_snapshot
.clip_point(Point::new(position.row, 0), Bias::Left);
let end = start + Point::new(1, 0);
let start = snapshot.buffer_snapshot().anchor_before(start);
let end = snapshot.buffer_snapshot().anchor_before(end);
let start = snapshot.buffer_snapshot.anchor_before(start);
let end = snapshot.buffer_snapshot.anchor_before(end);
editor.highlight_rows::<DebugStackFrameLine>(
start..end,
color,
@@ -354,6 +354,10 @@ impl Item for StackTraceView {
self.editor.for_each_project_item(cx, f)
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,

View File

@@ -1604,7 +1604,7 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T
let point = editor
.snapshot(window, cx)
.buffer_snapshot()
.buffer_snapshot
.summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
assert_eq!(point.row, 1);
@@ -1679,7 +1679,7 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T
let point = editor
.snapshot(window, cx)
.buffer_snapshot()
.buffer_snapshot
.summary_for_anchor::<language::Point>(&active_debug_lines.first().unwrap().0.start);
assert_eq!(point.row, 2);

View File

@@ -341,8 +341,8 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
editor
.highlighted_rows::<editor::ActiveDebugLine>()
.map(|(range, _)| {
let start = range.start.to_point(&snapshot.buffer_snapshot());
let end = range.end.to_point(&snapshot.buffer_snapshot());
let start = range.start.to_point(&snapshot.buffer_snapshot);
let end = range.end.to_point(&snapshot.buffer_snapshot);
start.row..end.row
})
.collect::<Vec<_>>()
@@ -404,8 +404,8 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
editor
.highlighted_rows::<editor::ActiveDebugLine>()
.map(|(range, _)| {
let start = range.start.to_point(&snapshot.buffer_snapshot());
let end = range.end.to_point(&snapshot.buffer_snapshot());
let start = range.start.to_point(&snapshot.buffer_snapshot);
let end = range.end.to_point(&snapshot.buffer_snapshot);
start.row..end.row
})
.collect::<Vec<_>>()

View File

@@ -730,6 +730,10 @@ impl Item for BufferDiagnosticsEditor {
self.multibuffer.read(cx).is_dirty(cx)
}
fn is_singleton(&self, _cx: &App) -> bool {
false
}
fn navigate(
&mut self,
data: Box<dyn Any>,

View File

@@ -138,7 +138,7 @@ impl editor::DiagnosticRenderer for DiagnosticRenderer {
BlockProperties {
placement: BlockPlacement::Near(
snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_after(block.initial_range.start),
),
height: Some(1),
@@ -278,7 +278,7 @@ impl DiagnosticBlock {
}
} else if let Some(diagnostic) = editor
.snapshot(window, cx)
.buffer_snapshot()
.buffer_snapshot
.diagnostic_group(buffer_id, group_id)
.nth(ix)
{

View File

@@ -716,6 +716,10 @@ impl Item for ProjectDiagnosticsEditor {
self.editor.for_each_project_item(cx, f)
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,

View File

@@ -863,20 +863,20 @@ async fn test_random_diagnostics_with_inlays(cx: &mut TestAppContext, mut rng: S
21..=50 => mutated_diagnostics.update_in(cx, |diagnostics, window, cx| {
diagnostics.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
if !snapshot.buffer_snapshot().is_empty() {
let position = rng.random_range(0..snapshot.buffer_snapshot().len());
let position = snapshot.buffer_snapshot().clip_offset(position, Bias::Left);
if !snapshot.buffer_snapshot.is_empty() {
let position = rng.random_range(0..snapshot.buffer_snapshot.len());
let position = snapshot.buffer_snapshot.clip_offset(position, Bias::Left);
log::info!(
"adding inlay at {position}/{}: {:?}",
snapshot.buffer_snapshot().len(),
snapshot.buffer_snapshot().text(),
snapshot.buffer_snapshot.len(),
snapshot.buffer_snapshot.text(),
);
editor.splice_inlays(
&[],
vec![Inlay::edit_prediction(
post_inc(&mut next_inlay_id),
snapshot.buffer_snapshot().anchor_before(position),
snapshot.buffer_snapshot.anchor_before(position),
Rope::from_iter(["Test inlay ", "next_inlay_id"]),
)],
cx,

View File

@@ -1,10 +1,9 @@
use std::path::PathBuf;
use anyhow::Context as _;
use gpui::{App, Context, Entity, Window};
use language::Language;
use project::lsp_store::lsp_ext_command::SwitchSourceHeaderResult;
use rpc::proto;
use url::Url;
use util::paths::PathStyle;
use workspace::{OpenOptions, OpenVisible};
@@ -78,17 +77,16 @@ pub fn switch_source_header(
return Ok(());
}
let goto = switch_source_header
.0
.strip_prefix("file://")
.with_context(|| {
format!(
"Parsing file url \"{}\" returned from switch source/header failed",
switch_source_header.0
)
})?;
let goto = Url::parse(&switch_source_header.0).with_context(|| {
format!(
"Parsing URL \"{}\" returned from switch source/header failed",
switch_source_header.0
)
})?;
let path = PathBuf::from(goto);
let path = goto
.to_file_path()
.map_err(|()| anyhow::anyhow!("URL conversion to file path failed for \"{goto}\""))?;
workspace
.update_in(cx, |workspace, window, cx| {

View File

@@ -170,15 +170,20 @@ impl DisplayMap {
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot, edits, tab_size);
let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size);
let (wrap_snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
let block_snapshot = self.block_map.read(wrap_snapshot, edits).snapshot;
.update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx));
let block_snapshot = self.block_map.read(wrap_snapshot.clone(), edits).snapshot;
DisplaySnapshot {
buffer_snapshot: self.buffer.read(cx).snapshot(cx),
fold_snapshot,
inlay_snapshot,
tab_snapshot,
wrap_snapshot,
block_snapshot,
diagnostics_max_severity: self.diagnostics_max_severity,
crease_snapshot: self.crease_map.snapshot(),
@@ -193,10 +198,10 @@ impl DisplayMap {
pub fn set_state(&mut self, other: &DisplaySnapshot, cx: &mut Context<Self>) {
self.fold(
other
.folds_in_range(0..other.buffer_snapshot().len())
.folds_in_range(0..other.buffer_snapshot.len())
.map(|fold| {
Crease::simple(
fold.range.to_offset(other.buffer_snapshot()),
fold.range.to_offset(&other.buffer_snapshot),
fold.placeholder.clone(),
)
})
@@ -757,7 +762,12 @@ impl<'a> HighlightedChunk<'a> {
#[derive(Clone)]
pub struct DisplaySnapshot {
pub buffer_snapshot: MultiBufferSnapshot,
pub fold_snapshot: FoldSnapshot,
pub crease_snapshot: CreaseSnapshot,
inlay_snapshot: InlaySnapshot,
tab_snapshot: TabSnapshot,
wrap_snapshot: WrapSnapshot,
block_snapshot: BlockSnapshot,
text_highlights: TextHighlights,
inlay_highlights: InlayHighlights,
@@ -766,44 +776,15 @@ pub struct DisplaySnapshot {
diagnostics_max_severity: DiagnosticSeverity,
pub(crate) fold_placeholder: FoldPlaceholder,
}
impl DisplaySnapshot {
pub fn wrap_snapshot(&self) -> &WrapSnapshot {
&self.block_snapshot.wrap_snapshot
}
pub fn tab_snapshot(&self) -> &TabSnapshot {
&self.block_snapshot.wrap_snapshot.tab_snapshot
}
pub fn fold_snapshot(&self) -> &FoldSnapshot {
&self.block_snapshot.wrap_snapshot.tab_snapshot.fold_snapshot
}
pub fn inlay_snapshot(&self) -> &InlaySnapshot {
&self
.block_snapshot
.wrap_snapshot
.tab_snapshot
.fold_snapshot
.inlay_snapshot
}
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
&self
.block_snapshot
.wrap_snapshot
.tab_snapshot
.fold_snapshot
.inlay_snapshot
.buffer
}
#[cfg(test)]
pub fn fold_count(&self) -> usize {
self.fold_snapshot().fold_count()
self.fold_snapshot.fold_count()
}
pub fn is_empty(&self) -> bool {
self.buffer_snapshot().len() == 0
self.buffer_snapshot.len() == 0
}
pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator<Item = RowInfo> + '_ {
@@ -811,16 +792,16 @@ impl DisplaySnapshot {
}
pub fn widest_line_number(&self) -> u32 {
self.buffer_snapshot().widest_line_number()
self.buffer_snapshot.widest_line_number()
}
pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) {
loop {
let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Left);
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left);
fold_point.0.column = 0;
inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
point = self.inlay_snapshot().to_buffer_point(inlay_point);
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
point = self.inlay_snapshot.to_buffer_point(inlay_point);
let mut display_point = self.point_to_display_point(point, Bias::Left);
*display_point.column_mut() = 0;
@@ -838,11 +819,11 @@ impl DisplaySnapshot {
) -> (MultiBufferPoint, DisplayPoint) {
let original_point = point;
loop {
let mut inlay_point = self.inlay_snapshot().to_inlay_point(point);
let mut fold_point = self.fold_snapshot().to_fold_point(inlay_point, Bias::Right);
fold_point.0.column = self.fold_snapshot().line_len(fold_point.row());
inlay_point = fold_point.to_inlay_point(self.fold_snapshot());
point = self.inlay_snapshot().to_buffer_point(inlay_point);
let mut inlay_point = self.inlay_snapshot.to_inlay_point(point);
let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right);
fold_point.0.column = self.fold_snapshot.line_len(fold_point.row());
inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
point = self.inlay_snapshot.to_buffer_point(inlay_point);
let mut display_point = self.point_to_display_point(point, Bias::Right);
*display_point.column_mut() = self.line_len(display_point.row());
@@ -860,8 +841,7 @@ impl DisplaySnapshot {
let new_end = if range.end.column > 0 {
MultiBufferPoint::new(
range.end.row,
self.buffer_snapshot()
.line_len(MultiBufferRow(range.end.row)),
self.buffer_snapshot.line_len(MultiBufferRow(range.end.row)),
)
} else {
range.end
@@ -871,52 +851,52 @@ impl DisplaySnapshot {
}
pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint {
let inlay_point = self.inlay_snapshot().to_inlay_point(point);
let fold_point = self.fold_snapshot().to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot().to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
let inlay_point = self.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
let block_point = self.block_snapshot.to_block_point(wrap_point);
DisplayPoint(block_point)
}
pub fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point {
self.inlay_snapshot()
self.inlay_snapshot
.to_buffer_point(self.display_point_to_inlay_point(point, bias))
}
pub fn display_point_to_inlay_offset(&self, point: DisplayPoint, bias: Bias) -> InlayOffset {
self.inlay_snapshot()
self.inlay_snapshot
.to_offset(self.display_point_to_inlay_point(point, bias))
}
pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset {
self.inlay_snapshot()
.to_inlay_offset(anchor.to_offset(self.buffer_snapshot()))
self.inlay_snapshot
.to_inlay_offset(anchor.to_offset(&self.buffer_snapshot))
}
pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor {
self.buffer_snapshot()
self.buffer_snapshot
.anchor_at(point.to_offset(self, bias), bias)
}
fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
let fold_point = self.tab_snapshot().to_fold_point(tab_point, bias).0;
fold_point.to_inlay_point(self.fold_snapshot())
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0;
fold_point.to_inlay_point(&self.fold_snapshot)
}
pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint {
let block_point = point.0;
let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias);
let tab_point = self.wrap_snapshot().to_tab_point(wrap_point);
self.tab_snapshot().to_fold_point(tab_point, bias).0
let tab_point = self.wrap_snapshot.to_tab_point(wrap_point);
self.tab_snapshot.to_fold_point(tab_point, bias).0
}
pub fn fold_point_to_display_point(&self, fold_point: FoldPoint) -> DisplayPoint {
let tab_point = self.tab_snapshot().to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot().tab_point_to_wrap_point(tab_point);
let tab_point = self.tab_snapshot.to_tab_point(fold_point);
let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point);
let block_point = self.block_snapshot.to_block_point(wrap_point);
DisplayPoint(block_point)
}
@@ -1138,7 +1118,7 @@ impl DisplaySnapshot {
}
pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
self.buffer_snapshot().chars_at(offset).map(move |ch| {
self.buffer_snapshot.chars_at(offset).map(move |ch| {
let ret = (ch, offset);
offset += ch.len_utf8();
ret
@@ -1149,7 +1129,7 @@ impl DisplaySnapshot {
&self,
mut offset: usize,
) -> impl Iterator<Item = (char, usize)> + '_ {
self.buffer_snapshot()
self.buffer_snapshot
.reversed_chars_at(offset)
.map(move |ch| {
offset -= ch.len_utf8();
@@ -1172,11 +1152,11 @@ impl DisplaySnapshot {
pub fn clip_at_line_end(&self, display_point: DisplayPoint) -> DisplayPoint {
let mut point = self.display_point_to_point(display_point, Bias::Left);
if point.column != self.buffer_snapshot().line_len(MultiBufferRow(point.row)) {
if point.column != self.buffer_snapshot.line_len(MultiBufferRow(point.row)) {
return display_point;
}
point.column = point.column.saturating_sub(1);
point = self.buffer_snapshot().clip_point(point, Bias::Left);
point = self.buffer_snapshot.clip_point(point, Bias::Left);
self.point_to_display_point(point, Bias::Left)
}
@@ -1184,7 +1164,7 @@ impl DisplaySnapshot {
where
T: ToOffset,
{
self.fold_snapshot().folds_in_range(range)
self.fold_snapshot.folds_in_range(range)
}
pub fn blocks_in_range(
@@ -1196,7 +1176,7 @@ impl DisplaySnapshot {
.map(|(row, block)| (DisplayRow(row), block))
}
pub fn sticky_header_excerpt(&self, row: f64) -> Option<StickyHeaderExcerpt<'_>> {
pub fn sticky_header_excerpt(&self, row: f32) -> Option<StickyHeaderExcerpt<'_>> {
self.block_snapshot.sticky_header_excerpt(row)
}
@@ -1205,12 +1185,12 @@ impl DisplaySnapshot {
}
pub fn intersects_fold<T: ToOffset>(&self, offset: T) -> bool {
self.fold_snapshot().intersects_fold(offset)
self.fold_snapshot.intersects_fold(offset)
}
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
self.block_snapshot.is_line_replaced(buffer_row)
|| self.fold_snapshot().is_line_folded(buffer_row)
|| self.fold_snapshot.is_line_folded(buffer_row)
}
pub fn is_block_line(&self, display_row: DisplayRow) -> bool {
@@ -1227,7 +1207,7 @@ impl DisplaySnapshot {
.block_snapshot
.to_wrap_point(BlockPoint::new(display_row.0, 0), Bias::Left)
.row();
self.wrap_snapshot().soft_wrap_indent(wrap_row)
self.wrap_snapshot.soft_wrap_indent(wrap_row)
}
pub fn text(&self) -> String {
@@ -1248,7 +1228,7 @@ impl DisplaySnapshot {
}
pub fn line_indent_for_buffer_row(&self, buffer_row: MultiBufferRow) -> LineIndent {
self.buffer_snapshot().line_indent_for_row(buffer_row)
self.buffer_snapshot.line_indent_for_row(buffer_row)
}
pub fn line_len(&self, row: DisplayRow) -> u32 {
@@ -1266,7 +1246,7 @@ impl DisplaySnapshot {
}
pub fn starts_indent(&self, buffer_row: MultiBufferRow) -> bool {
let max_row = self.buffer_snapshot().max_row();
let max_row = self.buffer_snapshot.max_row();
if buffer_row >= max_row {
return false;
}
@@ -1291,11 +1271,10 @@ impl DisplaySnapshot {
}
pub fn crease_for_buffer_row(&self, buffer_row: MultiBufferRow) -> Option<Crease<Point>> {
let start =
MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot().line_len(buffer_row));
let start = MultiBufferPoint::new(buffer_row.0, self.buffer_snapshot.line_len(buffer_row));
if let Some(crease) = self
.crease_snapshot
.query_row(buffer_row, self.buffer_snapshot())
.query_row(buffer_row, &self.buffer_snapshot)
{
match crease {
Crease::Inline {
@@ -1305,7 +1284,7 @@ impl DisplaySnapshot {
render_trailer,
metadata,
} => Some(Crease::Inline {
range: range.to_point(self.buffer_snapshot()),
range: range.to_point(&self.buffer_snapshot),
placeholder: placeholder.clone(),
render_toggle: render_toggle.clone(),
render_trailer: render_trailer.clone(),
@@ -1319,7 +1298,7 @@ impl DisplaySnapshot {
block_priority,
render_toggle,
} => Some(Crease::Block {
range: range.to_point(self.buffer_snapshot()),
range: range.to_point(&self.buffer_snapshot),
block_height: *block_height,
block_style: *block_style,
render_block: render_block.clone(),
@@ -1331,7 +1310,7 @@ impl DisplaySnapshot {
&& !self.is_line_folded(MultiBufferRow(start.row))
{
let start_line_indent = self.line_indent_for_buffer_row(buffer_row);
let max_point = self.buffer_snapshot().max_point();
let max_point = self.buffer_snapshot.max_point();
let mut end = None;
for row in (buffer_row.0 + 1)..=max_point.row {
@@ -1342,7 +1321,7 @@ impl DisplaySnapshot {
let prev_row = row - 1;
end = Some(Point::new(
prev_row,
self.buffer_snapshot().line_len(MultiBufferRow(prev_row)),
self.buffer_snapshot.line_len(MultiBufferRow(prev_row)),
));
break;
}
@@ -1351,7 +1330,7 @@ impl DisplaySnapshot {
let mut row_before_line_breaks = end.unwrap_or(max_point);
while row_before_line_breaks.row > start.row
&& self
.buffer_snapshot()
.buffer_snapshot
.is_line_blank(MultiBufferRow(row_before_line_breaks.row))
{
row_before_line_breaks.row -= 1;
@@ -1359,7 +1338,7 @@ impl DisplaySnapshot {
row_before_line_breaks = Point::new(
row_before_line_breaks.row,
self.buffer_snapshot()
self.buffer_snapshot
.line_len(MultiBufferRow(row_before_line_breaks.row)),
);
@@ -1503,23 +1482,23 @@ impl DisplayPoint {
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias);
let tab_point = map.wrap_snapshot().to_tab_point(wrap_point);
let fold_point = map.tab_snapshot().to_fold_point(tab_point, bias).0;
let inlay_point = fold_point.to_inlay_point(map.fold_snapshot());
map.inlay_snapshot()
.to_buffer_offset(map.inlay_snapshot().to_offset(inlay_point))
let tab_point = map.wrap_snapshot.to_tab_point(wrap_point);
let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0;
let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot);
map.inlay_snapshot
.to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point))
}
}
impl ToDisplayPoint for usize {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
map.point_to_display_point(self.to_point(map.buffer_snapshot()), Bias::Left)
map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
}
}
impl ToDisplayPoint for OffsetUtf16 {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
self.to_offset(map.buffer_snapshot()).to_display_point(map)
self.to_offset(&map.buffer_snapshot).to_display_point(map)
}
}
@@ -1531,7 +1510,7 @@ impl ToDisplayPoint for Point {
impl ToDisplayPoint for Anchor {
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
self.to_point(map.buffer_snapshot()).to_display_point(map)
self.to_point(&map.buffer_snapshot).to_display_point(map)
}
}
@@ -1620,10 +1599,10 @@ pub mod tests {
let mut blocks = Vec::new();
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
log::info!("block text: {:?}", snapshot.block_snapshot.text());
log::info!("display text: {:?}", snapshot.text());
@@ -1655,8 +1634,7 @@ pub mod tests {
30..=44 => {
map.update(cx, |map, cx| {
if rng.random() || blocks.is_empty() {
let snapshot = map.snapshot(cx);
let buffer = snapshot.buffer_snapshot();
let buffer = map.snapshot(cx).buffer_snapshot;
let block_properties = (0..rng.random_range(1..=1))
.map(|_| {
let position = buffer.anchor_after(buffer.clip_offset(
@@ -1737,15 +1715,15 @@ pub mod tests {
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
fold_count = snapshot.fold_count();
log::info!("buffer text: {:?}", snapshot.buffer_snapshot().text());
log::info!("fold text: {:?}", snapshot.fold_snapshot().text());
log::info!("tab text: {:?}", snapshot.tab_snapshot().text());
log::info!("wrap text: {:?}", snapshot.wrap_snapshot().text());
log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
log::info!("tab text: {:?}", snapshot.tab_snapshot.text());
log::info!("wrap text: {:?}", snapshot.wrap_snapshot.text());
log::info!("block text: {:?}", snapshot.block_snapshot.text());
log::info!("display text: {:?}", snapshot.text());
// Line boundaries
let buffer = snapshot.buffer_snapshot();
let buffer = &snapshot.buffer_snapshot;
for _ in 0..5 {
let row = rng.random_range(0..=buffer.max_point().row);
let column = rng.random_range(0..=buffer.line_len(MultiBufferRow(row)));
@@ -1899,37 +1877,37 @@ pub mod tests {
),
(
DisplayPoint::new(DisplayRow(0), 7),
language::SelectionGoal::HorizontalPosition(f64::from(x))
language::SelectionGoal::HorizontalPosition(x.0)
)
);
assert_eq!(
movement::down(
&snapshot,
DisplayPoint::new(DisplayRow(0), 7),
language::SelectionGoal::HorizontalPosition(f64::from(x)),
language::SelectionGoal::HorizontalPosition(x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 10),
language::SelectionGoal::HorizontalPosition(f64::from(x))
language::SelectionGoal::HorizontalPosition(x.0)
)
);
assert_eq!(
movement::down(
&snapshot,
DisplayPoint::new(DisplayRow(1), 10),
language::SelectionGoal::HorizontalPosition(f64::from(x)),
language::SelectionGoal::HorizontalPosition(x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(2), 4),
language::SelectionGoal::HorizontalPosition(f64::from(x))
language::SelectionGoal::HorizontalPosition(x.0)
)
);
let ix = snapshot.buffer_snapshot().text().find("seven").unwrap();
let ix = snapshot.buffer_snapshot.text().find("seven").unwrap();
buffer.update(cx, |buffer, cx| {
buffer.edit([(ix..ix, "and ")], None, cx);
});
@@ -1942,7 +1920,7 @@ pub mod tests {
// Re-wrap on font size changes
map.update(cx, |map, cx| {
map.set_font(font("Helvetica"), font_size + Pixels::from(3.), cx)
map.set_font(font("Helvetica"), px(font_size.0 + 3.), cx)
});
let snapshot = map.update(cx, |map, cx| map.snapshot(cx));

View File

@@ -33,8 +33,8 @@ const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
///
/// See the [`display_map` module documentation](crate::display_map) for more information.
pub struct BlockMap {
pub(super) wrap_snapshot: RefCell<WrapSnapshot>,
next_block_id: AtomicUsize,
wrap_snapshot: RefCell<WrapSnapshot>,
custom_blocks: Vec<Arc<CustomBlock>>,
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
transforms: RefCell<SumTree<Transform>>,
@@ -53,7 +53,7 @@ pub struct BlockMapWriter<'a>(&'a mut BlockMap);
#[derive(Clone)]
pub struct BlockSnapshot {
pub(super) wrap_snapshot: WrapSnapshot,
wrap_snapshot: WrapSnapshot,
transforms: SumTree<Transform>,
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
pub(super) buffer_header_height: u32,
@@ -1395,7 +1395,7 @@ impl BlockSnapshot {
})
}
pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
let top_row = position as u32;
let mut cursor = self.transforms.cursor::<BlockRow>(());
cursor.seek(&BlockRow(top_row), Bias::Right);

View File

@@ -624,10 +624,10 @@ impl FoldMap {
#[derive(Clone)]
pub struct FoldSnapshot {
pub inlay_snapshot: InlaySnapshot,
transforms: SumTree<Transform>,
folds: SumTree<Fold>,
fold_metadata_by_id: TreeMap<FoldId, FoldMetadata>,
pub inlay_snapshot: InlaySnapshot,
pub version: usize,
}

View File

@@ -30,7 +30,7 @@ pub struct WrapMap {
#[derive(Clone)]
pub struct WrapSnapshot {
pub(super) tab_snapshot: TabSnapshot,
tab_snapshot: TabSnapshot,
transforms: SumTree<Transform>,
interpolated: bool,
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,12 +25,13 @@ pub struct EditorSettings {
pub lsp_highlight_debounce: u64,
pub hover_popover_enabled: bool,
pub hover_popover_delay: u64,
pub status_bar: StatusBar,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub minimap: Minimap,
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f64,
pub vertical_scroll_margin: f32,
pub autoscroll_on_clicks: bool,
pub horizontal_scroll_margin: f32,
pub scroll_sensitivity: f32,
@@ -66,6 +67,18 @@ pub struct Jupyter {
pub enabled: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatusBar {
/// Whether to display the active language button in the status bar.
///
/// Default: true
pub active_language_button: bool,
/// Whether to show the cursor position button in the status bar.
///
/// Default: true
pub cursor_position_button: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Toolbar {
pub breadcrumbs: bool,
@@ -176,12 +189,13 @@ impl ScrollbarVisibility for EditorSettings {
}
impl Settings for EditorSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let editor = content.editor.clone();
let scrollbar = editor.scrollbar.unwrap();
let minimap = editor.minimap.unwrap();
let gutter = editor.gutter.unwrap();
let axes = scrollbar.axes.unwrap();
let status_bar = editor.status_bar.unwrap();
let toolbar = editor.toolbar.unwrap();
let search = editor.search.unwrap();
let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
@@ -194,6 +208,10 @@ impl Settings for EditorSettings {
lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(),
hover_popover_enabled: editor.hover_popover_enabled.unwrap(),
hover_popover_delay: editor.hover_popover_delay.unwrap(),
status_bar: StatusBar {
active_language_button: status_bar.active_language_button.unwrap(),
cursor_position_button: status_bar.cursor_position_button.unwrap(),
},
toolbar: Toolbar {
breadcrumbs: toolbar.breadcrumbs.unwrap(),
quick_actions: toolbar.quick_actions.unwrap(),
@@ -230,7 +248,7 @@ impl Settings for EditorSettings {
folds: gutter.folds.unwrap(),
},
scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(),
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap() as f64,
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap(),
autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(),
horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(),
scroll_sensitivity: editor.scroll_sensitivity.unwrap(),

View File

@@ -0,0 +1,384 @@
use std::sync::Arc;
use gpui::{App, FontFeatures, FontWeight};
use project::project_settings::ProjectSettings;
use settings::{EditableSettingControl, Settings, SettingsContent};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
prelude::*,
};
use crate::EditorSettings;
#[derive(IntoElement)]
pub struct EditorSettingsControls {}
impl Default for EditorSettingsControls {
fn default() -> Self {
Self::new()
}
}
impl EditorSettingsControls {
pub fn new() -> Self {
Self {}
}
}
impl RenderOnce for EditorSettingsControls {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
SettingsContainer::new()
.child(
SettingsGroup::new("Font")
.child(
h_flex()
.gap_2()
.justify_between()
.child(BufferFontFamilyControl)
.child(BufferFontWeightControl),
)
.child(BufferFontSizeControl)
.child(BufferFontLigaturesControl),
)
.child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
.child(
SettingsGroup::new("Gutter").child(
h_flex()
.gap_2()
.justify_between()
.child(LineNumbersControl)
.child(RelativeLineNumbersControl),
),
)
}
}
#[derive(IntoElement)]
struct BufferFontFamilyControl;
impl EditableSettingControl for BufferFontFamilyControl {
type Value = SharedString;
fn name(&self) -> SharedString {
"Buffer Font Family".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings.buffer_font.family.clone()
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings.theme.buffer_font_family = Some(FontFamilyName(value.into()));
}
}
impl RenderOnce for BufferFontFamilyControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::Font))
.child(DropdownMenu::new(
"buffer-font-family",
value,
ContextMenu::build(window, cx, |mut menu, _, cx| {
let font_family_cache = FontFamilyCache::global(cx);
for font_name in font_family_cache.list_font_families(cx) {
menu = menu.custom_entry(
{
let font_name = font_name.clone();
move |_window, _cx| Label::new(font_name.clone()).into_any_element()
},
{
let font_name = font_name.clone();
move |_window, cx| {
Self::write(font_name.clone(), cx);
}
},
)
}
menu
}),
))
}
}
#[derive(IntoElement)]
struct BufferFontSizeControl;
impl EditableSettingControl for BufferFontSizeControl {
type Value = Pixels;
fn name(&self) -> SharedString {
"Buffer Font Size".into()
}
fn read(cx: &App) -> Self::Value {
ThemeSettings::get_global(cx).buffer_font_size(cx)
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings.theme.buffer_font_size = Some(value.into());
}
}
impl RenderOnce for BufferFontSizeControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::FontSize))
.child(NumericStepper::new(
"buffer-font-size",
value.to_string(),
move |_, _, cx| {
Self::write(value - px(1.), cx);
},
move |_, _, cx| {
Self::write(value + px(1.), cx);
},
))
}
}
#[derive(IntoElement)]
struct BufferFontWeightControl;
impl EditableSettingControl for BufferFontWeightControl {
type Value = FontWeight;
fn name(&self) -> SharedString {
"Buffer Font Weight".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings.buffer_font.weight
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings.theme.buffer_font_weight = Some(value.0);
}
}
impl RenderOnce for BufferFontWeightControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::FontWeight))
.child(DropdownMenu::new(
"buffer-font-weight",
value.0.to_string(),
ContextMenu::build(window, cx, |mut menu, _window, _cx| {
for weight in FontWeight::ALL {
menu = menu.custom_entry(
move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
{
move |_, cx| {
Self::write(weight, cx);
}
},
)
}
menu
}),
))
}
}
#[derive(IntoElement)]
struct BufferFontLigaturesControl;
impl EditableSettingControl for BufferFontLigaturesControl {
type Value = bool;
fn name(&self) -> SharedString {
"Buffer Font Ligatures".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings
.buffer_font
.features
.is_calt_enabled()
.unwrap_or(true)
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
let value = if value { 1 } else { 0 };
let mut features = settings
.theme
.buffer_font_features
.as_ref()
.map(|features| features.tag_value_list().to_vec())
.unwrap_or_default();
if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
features[calt_index].1 = value;
} else {
features.push(("calt".into(), value));
}
settings.theme.buffer_font_features = Some(FontFeatures(Arc::new(features)));
}
}
impl RenderOnce for BufferFontLigaturesControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"buffer-font-ligatures",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct InlineGitBlameControl;
impl EditableSettingControl for InlineGitBlameControl {
type Value = bool;
fn name(&self) -> SharedString {
"Inline Git Blame".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ProjectSettings::get_global(cx);
settings.git.inline_blame.enabled
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.enabled = Some(value)
}
}
impl RenderOnce for InlineGitBlameControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"inline-git-blame",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct LineNumbersControl;
impl EditableSettingControl for LineNumbersControl {
type Value = bool;
fn name(&self) -> SharedString {
"Line Numbers".into()
}
fn read(cx: &App) -> Self::Value {
let settings = EditorSettings::get_global(cx);
settings.gutter.line_numbers
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings.editor.gutter.get_or_insert_default().line_numbers = Some(value);
}
}
impl RenderOnce for LineNumbersControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"line-numbers",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct RelativeLineNumbersControl;
impl EditableSettingControl for RelativeLineNumbersControl {
type Value = bool;
fn name(&self) -> SharedString {
"Relative Line Numbers".into()
}
fn read(cx: &App) -> Self::Value {
let settings = EditorSettings::get_global(cx);
settings.relative_line_numbers
}
fn apply(settings: &mut SettingsContent, value: Self::Value, _cx: &App) {
settings.editor.relative_line_numbers = Some(value);
}
}
impl RenderOnce for RelativeLineNumbersControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
DropdownMenu::new(
"relative-line-numbers",
if value { "Relative" } else { "Ascending" },
ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.custom_entry(
|_window, _cx| Label::new("Ascending").into_any_element(),
move |_, cx| Self::write(false, cx),
)
.custom_entry(
|_window, _cx| Label::new("Relative").into_any_element(),
move |_, cx| Self::write(true, cx),
)
}),
)
}
}

View File

@@ -14,7 +14,7 @@ use crate::{
};
use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
use collections::HashMap;
use futures::{StreamExt, channel::oneshot};
use futures::StreamExt;
use gpui::{
BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
VisualTestContext, WindowBounds, WindowOptions, div,
@@ -782,12 +782,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
assert!(pop_history(&mut editor, cx).is_none());
// Set scroll position to check later
editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
let original_scroll_position = editor.scroll_manager.anchor();
// Jump to the end of the document and adjust scroll
editor.move_to_end(&MoveToEnd, window, cx);
editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
let nav_entry = pop_history(&mut editor, cx).unwrap();
@@ -817,7 +817,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
);
assert_eq!(
editor.scroll_position(cx),
gpui::Point::new(0., editor.max_point(cx).row().as_f64())
gpui::Point::new(0., editor.max_point(cx).row().as_f32())
);
editor
@@ -1256,63 +1256,6 @@ fn test_fold_at_level(cx: &mut TestAppContext) {
editor.display_text(cx),
editor.buffer.read(cx).read(cx).text()
);
let (_, positions) = marked_text_ranges(
&"
class Foo:
# Hello!
def a():
print(1)
def b():
p«riˇ»nt(2)
class Bar:
# World!
def a():
«ˇprint(1)
def b():
print(2)»
"
.unindent(),
true,
);
editor.change_selections(SelectionEffects::default(), window, cx, |s| {
s.select_ranges(positions)
});
editor.fold_at_level(&FoldAtLevel(2), window, cx);
assert_eq!(
editor.display_text(cx),
"
class Foo:
# Hello!
def a():⋯
def b():
print(2)
class Bar:
# World!
def a():
print(1)
def b():
print(2)
"
.unindent(),
);
});
}
@@ -4300,8 +4243,8 @@ fn test_delete_line(cx: &mut TestAppContext) {
assert_eq!(
editor.selections.display_ranges(cx),
vec![
DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
]
);
});
@@ -4527,8 +4470,8 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
let snapshot = editor.snapshot(window, cx);
assert_eq!(
snapshot
.buffer_snapshot()
.diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
.buffer_snapshot
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.collect::<Vec<_>>(),
Vec::new(),
"Should not have any diffs for files with custom newlines"
@@ -5742,7 +5685,7 @@ async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
// Create a four-line block that replaces three lines of text.
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot();
let snapshot = &snapshot.buffer_snapshot;
let placement = BlockPlacement::Replace(
snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
);
@@ -16515,7 +16458,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
leader.update(cx, |leader, cx| {
leader.buffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path(
PathKey::namespaced(1, rel_path("b.txt").into_arc()),
PathKey::namespaced(1, "b.txt".into()),
buffer_1.clone(),
vec![
Point::row_range(0..3),
@@ -16526,7 +16469,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
cx,
);
multibuffer.set_excerpts_for_path(
PathKey::namespaced(1, rel_path("a.txt").into_arc()),
PathKey::namespaced(1, "a.txt".into()),
buffer_2.clone(),
vec![Point::row_range(0..6), Point::row_range(8..12)],
0,
@@ -18798,9 +18741,8 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
let active_item = workspace
.active_item(cx)
.expect("should have an active item after adding the multi buffer");
assert_eq!(
active_item.buffer_kind(cx),
ItemBufferKind::Multibuffer,
assert!(
!active_item.is_singleton(cx),
"A multi buffer was expected to active after adding"
);
active_item.item_id()
@@ -18828,9 +18770,8 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
first_item_id, multibuffer_item_id,
"Should navigate into the 1st buffer and activate it"
);
assert_eq!(
active_item.buffer_kind(cx),
ItemBufferKind::Singleton,
assert!(
active_item.is_singleton(cx),
"New active item should be a singleton buffer"
);
assert_eq!(
@@ -18860,7 +18801,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
multibuffer_item_id,
"Should navigate back to the multi buffer"
);
assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
assert!(!active_item.is_singleton(cx));
})
.unwrap();
@@ -18888,9 +18829,8 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
second_item_id, first_item_id,
"Should navigate into the 2nd buffer and activate it"
);
assert_eq!(
active_item.buffer_kind(cx),
ItemBufferKind::Singleton,
assert!(
active_item.is_singleton(cx),
"New active item should be a singleton buffer"
);
assert_eq!(
@@ -18920,7 +18860,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
multibuffer_item_id,
"Should navigate back from the 2nd buffer to the multi buffer"
);
assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
assert!(!active_item.is_singleton(cx));
})
.unwrap();
@@ -18946,9 +18886,8 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
);
assert_ne!(third_item_id, first_item_id);
assert_ne!(third_item_id, second_item_id);
assert_eq!(
active_item.buffer_kind(cx),
ItemBufferKind::Singleton,
assert!(
active_item.is_singleton(cx),
"New active item should be a singleton buffer"
);
assert_eq!(
@@ -18976,7 +18915,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
multibuffer_item_id,
"Should navigate back from the 3rd buffer to the multi buffer"
);
assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
assert!(!active_item.is_singleton(cx));
})
.unwrap();
}
@@ -20739,7 +20678,7 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
let mut actual_guides = cx.update_editor(|editor, window, cx| {
editor
.snapshot(window, cx)
.buffer_snapshot()
.buffer_snapshot
.indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
.map(|guide| (guide.start_row..=guide.end_row, guide.depth))
.collect::<Vec<_>>()
@@ -20795,7 +20734,7 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
.collect::<Vec<_>>();
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
let buffer_id = hunks[0].buffer_id;
@@ -20886,7 +20825,7 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
.collect::<Vec<_>>();
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
let buffer_id = hunks[0].buffer_id;
@@ -20952,7 +20891,7 @@ async fn test_toggle_deletion_hunk_at_start_of_file(
let hunk_ranges = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
.collect::<Vec<_>>();
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
let buffer_id = hunks[0].buffer_id;
@@ -21029,7 +20968,10 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) {
for buffer in &buffers {
let snapshot = buffer.read(cx).snapshot();
multibuffer.set_excerpts_for_path(
PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
PathKey::namespaced(
0,
buffer.read(cx).file().unwrap().path().as_unix_str().into(),
),
buffer.clone(),
vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
2,
@@ -21117,7 +21059,7 @@ async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let hunks = editor
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
.collect::<Vec<_>>();
assert_eq!(hunks.len(), 1);
assert_eq!(
@@ -22572,7 +22514,7 @@ fn add_log_breakpoint_at_cursor(
let breakpoint_position = editor
.snapshot(window, cx)
.display_snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(Point::new(cursor_position.row, 0));
(breakpoint_position, Breakpoint::new_log(log_message))
@@ -25364,8 +25306,8 @@ fn assert_hunk_revert(
let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let reverted_hunk_statuses = snapshot
.buffer_snapshot()
.diff_hunks_in_range(0..snapshot.buffer_snapshot().len())
.buffer_snapshot
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
.map(|hunk| hunk.status().kind)
.collect::<Vec<_>>();
@@ -26354,118 +26296,6 @@ async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_mult
));
}
#[gpui::test]
async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/project"),
json!({
"first.rs": "# First Document\nSome content here.",
"second.rs": "Plain text content for second file.",
}),
)
.await;
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let language = rust_lang();
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(language.clone());
let mut fake_servers = language_registry.register_fake_lsp(
"Rust",
FakeLspAdapter {
..FakeLspAdapter::default()
},
);
let buffer1 = project
.update(cx, |project, cx| {
project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
})
.await
.unwrap();
let buffer2 = project
.update(cx, |project, cx| {
project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
})
.await
.unwrap();
let multi_buffer = cx.new(|cx| {
let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
multi_buffer.set_excerpts_for_path(
PathKey::for_buffer(&buffer1, cx),
buffer1.clone(),
[Point::zero()..buffer1.read(cx).max_point()],
3,
cx,
);
multi_buffer.set_excerpts_for_path(
PathKey::for_buffer(&buffer2, cx),
buffer2.clone(),
[Point::zero()..buffer1.read(cx).max_point()],
3,
cx,
);
multi_buffer
});
let (editor, cx) = cx.add_window_view(|window, cx| {
Editor::new(
EditorMode::full(),
multi_buffer,
Some(project.clone()),
window,
cx,
)
});
let fake_language_server = fake_servers.next().await.unwrap();
buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
let save = editor.update_in(cx, |editor, window, cx| {
assert!(editor.is_dirty(cx));
editor.save(
SaveOptions {
format: true,
autosave: true,
},
project,
window,
cx,
)
});
let (start_edit_tx, start_edit_rx) = oneshot::channel();
let (done_edit_tx, done_edit_rx) = oneshot::channel();
let mut done_edit_rx = Some(done_edit_rx);
let mut start_edit_tx = Some(start_edit_tx);
fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
start_edit_tx.take().unwrap().send(()).unwrap();
let done_edit_rx = done_edit_rx.take().unwrap();
async move {
done_edit_rx.await.unwrap();
Ok(None)
}
});
start_edit_rx.await.unwrap();
buffer2
.update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
.unwrap();
done_edit_tx.send(()).unwrap();
save.await.unwrap();
cx.update(|_, cx| assert!(editor.is_dirty(cx)));
}
#[track_caller]
fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
editor

File diff suppressed because it is too large Load Diff

View File

@@ -20,28 +20,28 @@ pub fn refresh_matching_bracket_highlights(
let snapshot = editor.snapshot(window, cx);
let head = newest_selection.head();
if head > snapshot.buffer_snapshot().len() {
if head > snapshot.buffer_snapshot.len() {
log::error!("bug: cursor offset is out of range while refreshing bracket highlights");
return;
}
let mut tail = head;
if (editor.cursor_shape == CursorShape::Block || editor.cursor_shape == CursorShape::Hollow)
&& head < snapshot.buffer_snapshot().len()
&& head < snapshot.buffer_snapshot.len()
{
if let Some(tail_ch) = snapshot.buffer_snapshot().chars_at(tail).next() {
if let Some(tail_ch) = snapshot.buffer_snapshot.chars_at(tail).next() {
tail += tail_ch.len_utf8();
}
}
if let Some((opening_range, closing_range)) = snapshot
.buffer_snapshot()
.buffer_snapshot
.innermost_enclosing_bracket_ranges(head..tail, None)
{
editor.highlight_text::<MatchingBracketHighlight>(
vec![
opening_range.to_anchors(&snapshot.buffer_snapshot()),
closing_range.to_anchors(&snapshot.buffer_snapshot()),
opening_range.to_anchors(&snapshot.buffer_snapshot),
closing_range.to_anchors(&snapshot.buffer_snapshot),
],
HighlightStyle {
background_color: Some(

View File

@@ -49,8 +49,8 @@ impl RangeInEditor {
) -> bool {
match (self, trigger_point) {
(Self::Text(range), TriggerPoint::Text(point)) => {
let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot()).is_le();
point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot()).is_ge()
let point_after_start = range.start.cmp(point, &snapshot.buffer_snapshot).is_le();
point_after_start && range.end.cmp(point, &snapshot.buffer_snapshot).is_ge()
}
(Self::Inlay(highlight), TriggerPoint::InlayHint(point, _, _)) => {
highlight.inlay == point.inlay
@@ -131,7 +131,7 @@ impl Editor {
Some(point) => {
let trigger_point = TriggerPoint::Text(
snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(point.to_offset(&snapshot.display_snapshot, Bias::Left)),
);
@@ -326,7 +326,7 @@ pub fn update_inlay_link_and_hover_points(
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = snapshot
.buffer_snapshot()
.buffer_snapshot
.buffer_id_for_anchor(previous_valid_anchor)
{
inlay_hint_cache.spawn_hint_resolve(
@@ -534,7 +534,7 @@ pub fn show_link_definition(
let project = editor.project.clone();
let provider = editor.semantics_provider.clone();
let snapshot = snapshot.buffer_snapshot().clone();
let snapshot = snapshot.buffer_snapshot.clone();
hovered_link_state.task = Some(cx.spawn_in(window, async move |this, cx| {
async move {
let result = match &trigger_point {

View File

@@ -269,7 +269,7 @@ fn show_hover(
// Don't request again if the location is the same as the previous request
if let Some(triggered_from) = &editor.hover_state.triggered_from
&& triggered_from
.cmp(&anchor, &snapshot.buffer_snapshot())
.cmp(&anchor, &snapshot.buffer_snapshot)
.is_eq()
{
return None;
@@ -308,12 +308,12 @@ fn show_hover(
delay.await;
}
let offset = anchor.to_offset(&snapshot.buffer_snapshot());
let offset = anchor.to_offset(&snapshot.buffer_snapshot);
let local_diagnostic = if all_diagnostics_active {
None
} else {
snapshot
.buffer_snapshot()
.buffer_snapshot
.diagnostics_with_buffer_ids_in_range::<usize>(offset..offset)
.filter(|(_, diagnostic)| {
Some(diagnostic.diagnostic.group_id) != active_group_id
@@ -324,17 +324,17 @@ fn show_hover(
let diagnostic_popover = if let Some((buffer_id, local_diagnostic)) = local_diagnostic {
let group = snapshot
.buffer_snapshot()
.buffer_snapshot
.diagnostic_group(buffer_id, local_diagnostic.diagnostic.group_id)
.collect::<Vec<_>>();
let point_range = local_diagnostic
.range
.start
.to_point(&snapshot.buffer_snapshot())
.to_point(&snapshot.buffer_snapshot)
..local_diagnostic
.range
.end
.to_point(&snapshot.buffer_snapshot());
.to_point(&snapshot.buffer_snapshot);
let markdown = cx.update(|_, cx| {
renderer
.as_ref()
@@ -373,10 +373,10 @@ fn show_hover(
let local_diagnostic = DiagnosticEntry {
diagnostic: local_diagnostic.diagnostic.to_owned(),
range: snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(local_diagnostic.range.start)
..snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_after(local_diagnostic.range.end),
};
@@ -401,23 +401,23 @@ fn show_hover(
})?;
let invisible_char = if let Some(invisible) = snapshot
.buffer_snapshot()
.buffer_snapshot
.chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let after = snapshot.buffer_snapshot().anchor_after(
anchor.to_offset(&snapshot.buffer_snapshot()) + invisible.len_utf8(),
let after = snapshot.buffer_snapshot.anchor_after(
anchor.to_offset(&snapshot.buffer_snapshot) + invisible.len_utf8(),
);
Some((invisible, anchor..after))
} else if let Some(invisible) = snapshot
.buffer_snapshot()
.buffer_snapshot
.reversed_chars_at(anchor)
.next()
.filter(|&c| is_invisible(c))
{
let before = snapshot.buffer_snapshot().anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot()) - invisible.len_utf8(),
let before = snapshot.buffer_snapshot.anchor_before(
anchor.to_offset(&snapshot.buffer_snapshot) - invisible.len_utf8(),
);
Some((invisible, before..anchor))
@@ -468,15 +468,15 @@ fn show_hover(
.range
.and_then(|range| {
let start = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.start)?;
let end = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_in_excerpt(excerpt_id, range.end)?;
Some(start..end)
})
.or_else(|| {
let snapshot = &snapshot.buffer_snapshot();
let snapshot = &snapshot.buffer_snapshot;
let range = snapshot.syntax_ancestor(anchor..anchor)?.1;
Some(snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end))
})
@@ -542,8 +542,8 @@ fn same_info_hover(editor: &Editor, snapshot: &EditorSnapshot, anchor: Anchor) -
symbol_range
.as_text_range()
.map(|range| {
let hover_range = range.to_offset(&snapshot.buffer_snapshot());
let offset = anchor.to_offset(&snapshot.buffer_snapshot());
let hover_range = range.to_offset(&snapshot.buffer_snapshot);
let offset = anchor.to_offset(&snapshot.buffer_snapshot);
// LSP returns a hover result for the end index of ranges that should be hovered, so we need to
// use an inclusive range here to check if we should dismiss the popover
(hover_range.start..=hover_range.end).contains(&offset)
@@ -561,8 +561,8 @@ fn same_diagnostic_hover(editor: &Editor, snapshot: &EditorSnapshot, anchor: Anc
let hover_range = diagnostic
.local_diagnostic
.range
.to_offset(&snapshot.buffer_snapshot());
let offset = anchor.to_offset(&snapshot.buffer_snapshot());
.to_offset(&snapshot.buffer_snapshot);
let offset = anchor.to_offset(&snapshot.buffer_snapshot);
// Here we do basically the same as in `same_info_hover`, see comment there for an explanation
(hover_range.start..=hover_range.end).contains(&offset)
@@ -1090,7 +1090,7 @@ mod tests {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), window, cx)
});
@@ -1190,7 +1190,7 @@ mod tests {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), window, cx)
});
@@ -1228,7 +1228,7 @@ mod tests {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), window, cx)
});
@@ -1282,7 +1282,7 @@ mod tests {
cx.update_editor(|editor, window, cx| {
let snapshot = editor.snapshot(window, cx);
let anchor = snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_before(hover_point.to_offset(&snapshot, Bias::Left));
hover_at(editor, Some(anchor), window, cx)
});

View File

@@ -155,19 +155,19 @@ pub fn indent_guides_in_range(
cx: &App,
) -> Vec<IndentGuide> {
let start_offset = snapshot
.buffer_snapshot()
.buffer_snapshot
.point_to_offset(Point::new(visible_buffer_range.start.0, 0));
let end_offset = snapshot
.buffer_snapshot()
.buffer_snapshot
.point_to_offset(Point::new(visible_buffer_range.end.0, 0));
let start_anchor = snapshot.buffer_snapshot().anchor_before(start_offset);
let end_anchor = snapshot.buffer_snapshot().anchor_after(end_offset);
let start_anchor = snapshot.buffer_snapshot.anchor_before(start_offset);
let end_anchor = snapshot.buffer_snapshot.anchor_after(end_offset);
let mut fold_ranges = Vec::<Range<Point>>::new();
let folds = snapshot.folds_in_range(start_offset..end_offset).peekable();
for fold in folds {
let start = fold.range.start.to_point(&snapshot.buffer_snapshot());
let end = fold.range.end.to_point(&snapshot.buffer_snapshot());
let start = fold.range.start.to_point(&snapshot.buffer_snapshot);
let end = fold.range.end.to_point(&snapshot.buffer_snapshot);
if let Some(last_range) = fold_ranges.last_mut()
&& last_range.end >= start
{
@@ -178,7 +178,7 @@ pub fn indent_guides_in_range(
}
snapshot
.buffer_snapshot()
.buffer_snapshot
.indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
.filter(|indent_guide| {
if editor.is_buffer_folded(indent_guide.buffer_id, cx) {
@@ -207,7 +207,7 @@ async fn resolve_indented_range(
buffer_row: MultiBufferRow,
) -> Option<ActiveIndentedRange> {
snapshot
.buffer_snapshot()
.buffer_snapshot
.enclosing_indent(buffer_row)
.await
.map(|(row_range, indent)| ActiveIndentedRange { row_range, indent })
@@ -222,23 +222,23 @@ fn should_recalculate_indented_range(
if prev_row.0 == new_row.0 {
return false;
}
if snapshot.buffer_snapshot().is_singleton() {
if snapshot.buffer_snapshot.is_singleton() {
if !current_indent_range.row_range.contains(&new_row) {
return true;
}
let old_line_indent = snapshot.buffer_snapshot().line_indent_for_row(prev_row);
let new_line_indent = snapshot.buffer_snapshot().line_indent_for_row(new_row);
let old_line_indent = snapshot.buffer_snapshot.line_indent_for_row(prev_row);
let new_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row);
if old_line_indent.is_line_empty()
|| new_line_indent.is_line_empty()
|| old_line_indent != new_line_indent
|| snapshot.buffer_snapshot().max_point().row == new_row.0
|| snapshot.buffer_snapshot.max_point().row == new_row.0
{
return true;
}
let next_line_indent = snapshot.buffer_snapshot().line_indent_for_row(new_row + 1);
let next_line_indent = snapshot.buffer_snapshot.line_indent_for_row(new_row + 1);
next_line_indent.is_line_empty() || next_line_indent != old_line_indent
} else {
true

View File

@@ -5,7 +5,7 @@ use crate::{
display_map::HighlightKey,
editor_settings::SeedQuerySetting,
persistence::{DB, SerializedEditor},
scroll::{ScrollAnchor, ScrollOffset},
scroll::ScrollAnchor,
};
use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet};
@@ -43,7 +43,7 @@ use util::{ResultExt, TryFutureExt, paths::PathExt};
use workspace::{
CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
invalid_buffer_view::InvalidBufferView,
item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions},
item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions},
searchable::{
Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle,
},
@@ -747,11 +747,8 @@ impl Item for Editor {
.for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx)));
}
fn buffer_kind(&self, cx: &App) -> ItemBufferKind {
match self.buffer.read(cx).is_singleton() {
true => ItemBufferKind::Singleton,
false => ItemBufferKind::Multibuffer,
}
fn is_singleton(&self, cx: &App) -> bool {
self.buffer.read(cx).is_singleton()
}
fn can_save_as(&self, cx: &App) -> bool {
@@ -835,11 +832,12 @@ impl Item for Editor {
// let mut buffers_to_save =
let buffers_to_save = if self.buffer.read(cx).is_singleton() && !options.autosave {
buffers
buffers.clone()
} else {
buffers
.into_iter()
.iter()
.filter(|buffer| buffer.read(cx).is_dirty())
.cloned()
.collect()
};
@@ -865,6 +863,22 @@ impl Item for Editor {
.await?;
}
// Notify about clean buffers for language server events
let buffers_that_were_not_saved: Vec<_> = buffers
.into_iter()
.filter(|b| !buffers_to_save.contains(b))
.collect();
for buffer in buffers_that_were_not_saved {
buffer
.update(cx, |buffer, cx| {
let version = buffer.saved_version().clone();
let mtime = buffer.saved_mtime();
buffer.did_save(version, mtime, cx);
})
.ok();
}
Ok(())
})
}
@@ -1324,7 +1338,7 @@ struct EditorRestorationData {
#[derive(Default, Debug)]
pub struct RestorationData {
pub scroll_position: (BufferRow, gpui::Point<ScrollOffset>),
pub scroll_position: (BufferRow, gpui::Point<f32>),
pub folds: Vec<Range<Point>>,
pub selections: Vec<Range<Point>>,
}
@@ -1539,8 +1553,7 @@ impl SearchableItem for Editor {
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor;
let snapshot = self.snapshot(window, cx);
let snapshot = snapshot.buffer_snapshot();
let snapshot = &self.snapshot(window, cx).buffer_snapshot;
let selection = self.selections.newest_adjusted(cx);
match setting {

View File

@@ -54,7 +54,7 @@ impl MouseContextMenu {
let content_origin = editor.last_bounds?.origin
+ Point {
x: editor.gutter_dimensions.width,
y: Pixels::ZERO,
y: Pixels(0.0),
};
let source_position = editor.to_pixel_point(source, &editor_snapshot, window)?;
let menu_position = MenuPosition::PinnedToEditor {
@@ -170,8 +170,7 @@ pub fn deploy_context_menu(
};
let display_map = editor.selections.display_map(cx);
let snapshot = editor.snapshot(window, cx);
let buffer = snapshot.buffer_snapshot();
let buffer = &editor.snapshot(window, cx).buffer_snapshot;
let anchor = buffer.anchor_before(point.to_point(&display_map));
if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) {
// Move the cursor to the clicked location so that dispatched actions make sense

View File

@@ -2,10 +2,7 @@
//! in editor given a given motion (e.g. it handles converting a "move left" command into coordinates in editor). It is exposed mostly for use by vim crate.
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
use crate::{
DisplayRow, EditorStyle, ToOffset, ToPoint,
scroll::{ScrollAnchor, ScrollOffset},
};
use crate::{DisplayRow, EditorStyle, ToOffset, ToPoint, scroll::ScrollAnchor};
use gpui::{Pixels, WindowTextSystem};
use language::{CharClassifier, Point};
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
@@ -30,8 +27,8 @@ pub struct TextLayoutDetails {
pub(crate) editor_style: EditorStyle,
pub(crate) rem_size: Pixels,
pub scroll_anchor: ScrollAnchor,
pub visible_rows: Option<f64>,
pub vertical_scroll_margin: ScrollOffset,
pub visible_rows: Option<f32>,
pub vertical_scroll_margin: f32,
}
/// Returns a column to the left of the current point, wrapping
@@ -223,7 +220,7 @@ pub fn indented_line_beginning(
let soft_line_start = map.clip_point(DisplayPoint::new(display_point.row(), 0), Bias::Right);
let indent_start = Point::new(
point.row,
map.buffer_snapshot()
map.buffer_snapshot
.indent_size_for_line(MultiBufferRow(point.row))
.len,
)
@@ -265,7 +262,7 @@ pub fn line_end(
/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS).
pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
let mut is_first_iteration = true;
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
@@ -289,7 +286,7 @@ pub fn previous_word_start(map: &DisplaySnapshot, point: DisplayPoint) -> Displa
/// uppercase letter, lowercase letter, '_' character, language-specific word character (like '-' in CSS) or newline.
pub fn previous_word_start_or_newline(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
(classifier.kind(left) != classifier.kind(right) && !classifier.is_whitespace(right))
@@ -314,23 +311,23 @@ pub fn adjust_greedy_deletion(
let is_backward = delete_from > delete_until;
let delete_range = if is_backward {
map.display_point_to_point(delete_until, Bias::Left)
.to_offset(map.buffer_snapshot())
.to_offset(&map.buffer_snapshot)
..map
.display_point_to_point(delete_from, Bias::Right)
.to_offset(map.buffer_snapshot())
.to_offset(&map.buffer_snapshot)
} else {
map.display_point_to_point(delete_from, Bias::Left)
.to_offset(map.buffer_snapshot())
.to_offset(&map.buffer_snapshot)
..map
.display_point_to_point(delete_until, Bias::Right)
.to_offset(map.buffer_snapshot())
.to_offset(&map.buffer_snapshot)
};
let trimmed_delete_range = if ignore_brackets {
delete_range
} else {
let brackets_in_delete_range = map
.buffer_snapshot()
.buffer_snapshot
.bracket_ranges(delete_range.clone())
.into_iter()
.flatten()
@@ -361,7 +358,7 @@ pub fn adjust_greedy_deletion(
let mut whitespace_sequence_length = 0;
let mut whitespace_sequence_start = 0;
for ch in map
.buffer_snapshot()
.buffer_snapshot
.text_for_range(trimmed_delete_range.clone())
.flat_map(str::chars)
{
@@ -405,7 +402,7 @@ pub fn adjust_greedy_deletion(
/// lowerspace characters and uppercase characters.
pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
is_subword_start(left, right, &classifier) || left == '\n'
@@ -424,7 +421,7 @@ pub fn is_subword_start(left: char, right: char, classifier: &CharClassifier) ->
/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS).
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
let mut is_first_iteration = true;
find_boundary(map, point, FindRange::MultiLine, |left, right| {
// Make alt-right skip punctuation to respect VSCode behaviour. For example: |.hello goes to .hello|
@@ -447,7 +444,7 @@ pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint
/// uppercase letter, lowercase letter, '_' character, language-specific word character (like '-' in CSS) or newline.
pub fn next_word_end_or_newline(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
let mut on_starting_row = true;
find_boundary(map, point, FindRange::MultiLine, |left, right| {
@@ -466,7 +463,7 @@ pub fn next_word_end_or_newline(map: &DisplaySnapshot, point: DisplayPoint) -> D
/// lowerspace characters and uppercase characters.
pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
let raw_point = point.to_point(map);
let classifier = map.buffer_snapshot().char_classifier_at(raw_point);
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
find_boundary(map, point, FindRange::MultiLine, |left, right| {
is_subword_end(left, right, &classifier) || right == '\n'
@@ -496,7 +493,7 @@ pub fn start_of_paragraph(
let mut found_non_blank_line = false;
for row in (0..point.row + 1).rev() {
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
if found_non_blank_line && blank {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
@@ -519,13 +516,13 @@ pub fn end_of_paragraph(
mut count: usize,
) -> DisplayPoint {
let point = display_point.to_point(map);
if point.row == map.buffer_snapshot().max_row().0 {
if point.row == map.buffer_snapshot.max_row().0 {
return map.max_point();
}
let mut found_non_blank_line = false;
for row in point.row..=map.buffer_snapshot().max_row().0 {
let blank = map.buffer_snapshot().is_line_blank(MultiBufferRow(row));
for row in point.row..=map.buffer_snapshot.max_row().0 {
let blank = map.buffer_snapshot.is_line_blank(MultiBufferRow(row));
if found_non_blank_line && blank {
if count <= 1 {
return Point::new(row, 0).to_display_point(map);
@@ -546,14 +543,14 @@ pub fn start_of_excerpt(
direction: Direction,
) -> DisplayPoint {
let point = map.display_point_to_point(display_point, Bias::Left);
let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
return display_point;
};
match direction {
Direction::Prev => {
let mut start = excerpt.start_anchor().to_display_point(map);
if start >= display_point && start.row() > DisplayRow(0) {
let Some(excerpt) = map.buffer_snapshot().excerpt_before(excerpt.id()) else {
let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else {
return display_point;
};
start = excerpt.start_anchor().to_display_point(map);
@@ -574,7 +571,7 @@ pub fn end_of_excerpt(
direction: Direction,
) -> DisplayPoint {
let point = map.display_point_to_point(display_point, Bias::Left);
let Some(excerpt) = map.buffer_snapshot().excerpt_containing(point..point) else {
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
return display_point;
};
match direction {
@@ -593,9 +590,7 @@ pub fn end_of_excerpt(
if end <= display_point {
*end.row_mut() += 1;
let point_end = map.display_point_to_point(end, Bias::Right);
let Some(excerpt) = map
.buffer_snapshot()
.excerpt_containing(point_end..point_end)
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point_end..point_end)
else {
return display_point;
};
@@ -648,7 +643,7 @@ pub fn find_preceding_boundary_display_point(
is_boundary: impl FnMut(char, char) -> bool,
) -> DisplayPoint {
let result = find_preceding_boundary_point(
map.buffer_snapshot(),
&map.buffer_snapshot,
from.to_point(map),
find_range,
is_boundary,
@@ -672,7 +667,7 @@ pub fn find_boundary_point(
let mut prev_offset = offset;
let mut prev_ch = None;
for ch in map.buffer_snapshot().chars_at(offset) {
for ch in map.buffer_snapshot.chars_at(offset) {
if find_range == FindRange::SingleLine && ch == '\n' {
break;
}
@@ -700,8 +695,8 @@ pub fn find_preceding_boundary_trail(
let mut offset = head.to_offset(map, Bias::Left);
let mut trail_offset = None;
let mut prev_ch = map.buffer_snapshot().chars_at(offset).next();
let mut forward = map.buffer_snapshot().reversed_chars_at(offset).peekable();
let mut prev_ch = map.buffer_snapshot.chars_at(offset).next();
let mut forward = map.buffer_snapshot.reversed_chars_at(offset).peekable();
// Skip newlines
while let Some(&ch) = forward.peek() {
@@ -748,8 +743,8 @@ pub fn find_boundary_trail(
let mut offset = head.to_offset(map, Bias::Right);
let mut trail_offset = None;
let mut prev_ch = map.buffer_snapshot().reversed_chars_at(offset).next();
let mut forward = map.buffer_snapshot().chars_at(offset).peekable();
let mut prev_ch = map.buffer_snapshot.reversed_chars_at(offset).next();
let mut forward = map.buffer_snapshot.chars_at(offset).peekable();
// Skip newlines
while let Some(&ch) = forward.peek() {
@@ -812,7 +807,7 @@ pub fn chars_after(
map: &DisplaySnapshot,
mut offset: usize,
) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
map.buffer_snapshot().chars_at(offset).map(move |ch| {
map.buffer_snapshot.chars_at(offset).map(move |ch| {
let before = offset;
offset += ch.len_utf8();
(ch, before..offset)
@@ -826,7 +821,7 @@ pub fn chars_before(
map: &DisplaySnapshot,
mut offset: usize,
) -> impl Iterator<Item = (char, Range<usize>)> + '_ {
map.buffer_snapshot()
map.buffer_snapshot
.reversed_chars_at(offset)
.map(move |ch| {
let after = offset;
@@ -1057,7 +1052,7 @@ mod tests {
|left, _| left == 'e',
),
snapshot
.buffer_snapshot()
.buffer_snapshot
.offset_to_point(5)
.to_display_point(&snapshot),
"Should not stop at inlays when looking for boundaries"
@@ -1225,13 +1220,13 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(0), 2),
SelectionGoal::HorizontalPosition(f64::from(col_2_x)),
SelectionGoal::HorizontalPosition(col_2_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(0), 0),
SelectionGoal::HorizontalPosition(f64::from(col_2_x)),
SelectionGoal::HorizontalPosition(col_2_x.0),
),
);
assert_eq!(
@@ -1256,26 +1251,26 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.into()),
SelectionGoal::HorizontalPosition(col_4_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.into())
SelectionGoal::HorizontalPosition(col_4_x.0)
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(0), 3),
SelectionGoal::HorizontalPosition(col_4_x.into()),
SelectionGoal::HorizontalPosition(col_4_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_4_x.into())
SelectionGoal::HorizontalPosition(col_4_x.0)
),
);
@@ -1287,26 +1282,26 @@ mod tests {
up(
&snapshot,
DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.into()),
SelectionGoal::HorizontalPosition(col_5_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.into())
SelectionGoal::HorizontalPosition(col_5_x.0)
),
);
assert_eq!(
down(
&snapshot,
DisplayPoint::new(DisplayRow(1), 4),
SelectionGoal::HorizontalPosition(col_5_x.into()),
SelectionGoal::HorizontalPosition(col_5_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(3), 5),
SelectionGoal::HorizontalPosition(col_5_x.into())
SelectionGoal::HorizontalPosition(col_5_x.0)
),
);
@@ -1331,13 +1326,13 @@ mod tests {
down(
&snapshot,
DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.into()),
SelectionGoal::HorizontalPosition(max_point_x.0),
false,
&text_layout_details
),
(
DisplayPoint::new(DisplayRow(4), 2),
SelectionGoal::HorizontalPosition(max_point_x.into())
SelectionGoal::HorizontalPosition(max_point_x.0)
),
);
});

View File

@@ -235,7 +235,7 @@ impl EditorDb {
// Returns the scroll top row, and offset
query! {
pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<(u32, f64, f64)>> {
pub fn get_scroll_position(item_id: ItemId, workspace_id: WorkspaceId) -> Result<Option<(u32, f32, f32)>> {
SELECT scroll_top_row, scroll_horizontal_offset, scroll_vertical_offset
FROM editors
WHERE item_id = ? AND workspace_id = ?
@@ -247,8 +247,8 @@ impl EditorDb {
item_id: ItemId,
workspace_id: WorkspaceId,
top_row: u32,
vertical_offset: f64,
horizontal_offset: f64
vertical_offset: f32,
horizontal_offset: f32
) -> Result<()> {
UPDATE OR IGNORE editors
SET

View File

@@ -30,11 +30,9 @@ const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
pub struct WasScrolled(pub(crate) bool);
pub type ScrollOffset = f64;
pub type ScrollPixelOffset = f64;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScrollAnchor {
pub offset: gpui::Point<ScrollOffset>,
pub offset: gpui::Point<f32>,
pub anchor: Anchor,
}
@@ -46,12 +44,12 @@ impl ScrollAnchor {
}
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
self.offset.apply_along(Axis::Vertical, |offset| {
if self.anchor == Anchor::min() {
0.
} else {
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64();
let scroll_top = self.anchor.to_display_point(snapshot).row().as_f32();
(offset + scroll_top).max(0.)
}
})
@@ -149,24 +147,19 @@ impl ActiveScrollbarState {
}
pub struct ScrollManager {
pub(crate) vertical_scroll_margin: ScrollOffset,
pub(crate) vertical_scroll_margin: f32,
anchor: ScrollAnchor,
ongoing: OngoingScroll,
/// The second element indicates whether the autoscroll request is local
/// (true) or remote (false). Local requests are initiated by user actions,
/// while remote requests come from external sources.
autoscroll_request: Option<(Autoscroll, bool)>,
last_autoscroll: Option<(
gpui::Point<ScrollOffset>,
ScrollOffset,
ScrollOffset,
AutoscrollStrategy,
)>,
last_autoscroll: Option<(gpui::Point<f32>, f32, f32, AutoscrollStrategy)>,
show_scrollbars: bool,
hide_scrollbar_task: Option<Task<()>>,
active_scrollbar: Option<ActiveScrollbarState>,
visible_line_count: Option<f64>,
visible_column_count: Option<f64>,
visible_line_count: Option<f32>,
visible_column_count: Option<f32>,
forbid_vertical_scroll: bool,
minimap_thumb_state: Option<ScrollbarThumbState>,
}
@@ -207,13 +200,13 @@ impl ScrollManager {
self.ongoing.axis = axis;
}
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> {
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<f32> {
self.anchor.scroll_position(snapshot)
}
fn set_scroll_position(
&mut self,
scroll_position: gpui::Point<ScrollOffset>,
scroll_position: gpui::Point<f32>,
map: &DisplaySnapshot,
local: bool,
autoscroll: bool,
@@ -226,7 +219,7 @@ impl ScrollManager {
ScrollBeyondLastLine::OnePage => scroll_top,
ScrollBeyondLastLine::Off => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().as_f64();
let max_row = map.max_point().row().0 as f32;
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
} else {
scroll_top
@@ -234,7 +227,7 @@ impl ScrollManager {
}
ScrollBeyondLastLine::VerticalScrollMargin => {
if let Some(height_in_lines) = self.visible_line_count {
let max_row = map.max_point().row().as_f64();
let max_row = map.max_point().row().0 as f32;
scroll_top
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
.max(0.)
@@ -251,14 +244,14 @@ impl ScrollManager {
Bias::Left,
)
.to_point(map);
let top_anchor = map.buffer_snapshot().anchor_after(scroll_top_buffer_point);
let top_anchor = map.buffer_snapshot.anchor_after(scroll_top_buffer_point);
self.set_anchor(
ScrollAnchor {
anchor: top_anchor,
offset: point(
scroll_position.x.max(0.),
scroll_top - top_anchor.to_display_point(map).row().as_f64(),
scroll_top - top_anchor.to_display_point(map).row().as_f32(),
),
},
scroll_top_buffer_point.row,
@@ -444,7 +437,7 @@ impl ScrollManager {
self.minimap_thumb_state
}
pub fn clamp_scroll_left(&mut self, max: f64) -> bool {
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
if max < self.anchor.offset.x {
self.anchor.offset.x = max;
true
@@ -468,11 +461,11 @@ impl Editor {
}
pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) {
self.scroll_manager.vertical_scroll_margin = margin_rows as f64;
self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
cx.notify();
}
pub fn visible_line_count(&self) -> Option<f64> {
pub fn visible_line_count(&self) -> Option<f32> {
self.scroll_manager.visible_line_count
}
@@ -481,13 +474,13 @@ impl Editor {
.map(|line_count| line_count as u32 - 1)
}
pub fn visible_column_count(&self) -> Option<f64> {
pub fn visible_column_count(&self) -> Option<f32> {
self.scroll_manager.visible_column_count
}
pub(crate) fn set_visible_line_count(
&mut self,
lines: f64,
lines: f32,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -506,7 +499,7 @@ impl Editor {
}
}
pub(crate) fn set_visible_column_count(&mut self, columns: f64) {
pub(crate) fn set_visible_column_count(&mut self, columns: f32) {
self.scroll_manager.visible_column_count = Some(columns);
}
@@ -521,14 +514,13 @@ impl Editor {
delta.y = 0.0;
}
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let position =
self.scroll_manager.anchor.scroll_position(&display_map) + delta.map(f64::from);
let position = self.scroll_manager.anchor.scroll_position(&display_map) + delta;
self.set_scroll_position_taking_display_map(position, true, false, display_map, window, cx);
}
pub fn set_scroll_position(
&mut self,
scroll_position: gpui::Point<ScrollOffset>,
scroll_position: gpui::Point<f32>,
window: &mut Window,
cx: &mut Context<Self>,
) -> WasScrolled {
@@ -550,7 +542,7 @@ impl Editor {
let snapshot = self.snapshot(window, cx).display_snapshot;
let new_screen_top = DisplayPoint::new(row, 0);
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
let new_anchor = snapshot.buffer_snapshot().anchor_before(new_screen_top);
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
self.set_scroll_anchor(
ScrollAnchor {
@@ -564,7 +556,7 @@ impl Editor {
pub(crate) fn set_scroll_position_internal(
&mut self,
scroll_position: gpui::Point<ScrollOffset>,
scroll_position: gpui::Point<f32>,
local: bool,
autoscroll: bool,
window: &mut Window,
@@ -583,7 +575,7 @@ impl Editor {
fn set_scroll_position_taking_display_map(
&mut self,
scroll_position: gpui::Point<ScrollOffset>,
scroll_position: gpui::Point<f32>,
local: bool,
autoscroll: bool,
display_map: DisplaySnapshot,
@@ -618,7 +610,7 @@ impl Editor {
editor_was_scrolled
}
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<ScrollOffset> {
pub fn scroll_position(&self, cx: &mut Context<Self>) -> gpui::Point<f32> {
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
self.scroll_manager.anchor.scroll_position(&display_map)
}
@@ -705,9 +697,9 @@ impl Editor {
if matches!(
settings.defaults.soft_wrap,
SoftWrap::PreferredLineLength | SoftWrap::Bounded
) && (settings.defaults.preferred_line_length as f64) < visible_column_count
) && (settings.defaults.preferred_line_length as f32) < visible_column_count
{
visible_column_count = settings.defaults.preferred_line_length as f64;
visible_column_count = settings.defaults.preferred_line_length as f32;
}
// If the scroll position is currently at the left edge of the document
@@ -718,8 +710,7 @@ impl Editor {
&& amount.columns(visible_column_count) > 0.
&& let Some(last_position_map) = &self.last_position_map
{
current_position.x +=
f64::from(self.gutter_dimensions.margin / last_position_map.em_advance);
current_position.x += self.gutter_dimensions.margin / last_position_map.em_advance;
}
let new_position = current_position
+ point(

View File

@@ -2,7 +2,7 @@ use super::Axis;
use crate::{
Autoscroll, Editor, EditorMode, NextScreen, NextScrollCursorCenterTopBottom,
SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT, ScrollCursorBottom, ScrollCursorCenter,
ScrollCursorCenterTopBottom, ScrollCursorTop, display_map::DisplayRow, scroll::ScrollOffset,
ScrollCursorCenterTopBottom, ScrollCursorTop, display_map::DisplayRow,
};
use gpui::{Context, Point, Window};
@@ -25,7 +25,7 @@ impl Editor {
pub fn scroll(
&mut self,
scroll_position: Point<ScrollOffset>,
scroll_position: Point<f32>,
axis: Option<Axis>,
window: &mut Window,
cx: &mut Context<Self>,

View File

@@ -1,12 +1,11 @@
use crate::{
DisplayRow, Editor, EditorMode, LineWithInvisibles, RowExt, SelectionEffects,
display_map::ToDisplayPoint,
scroll::{ScrollOffset, WasScrolled},
display_map::ToDisplayPoint, scroll::WasScrolled,
};
use gpui::{Bounds, Context, Pixels, Window};
use gpui::{Bounds, Context, Pixels, Window, px};
use language::Point;
use multi_buffer::Anchor;
use std::cmp;
use std::{cmp, f32};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Autoscroll {
@@ -107,21 +106,20 @@ impl Editor {
&mut self,
bounds: Bounds<Pixels>,
line_height: Pixels,
max_scroll_top: ScrollOffset,
max_scroll_top: f32,
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
let viewport_height = bounds.size.height;
let visible_lines = ScrollOffset::from(viewport_height / line_height);
let visible_lines = viewport_height / line_height;
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
let original_y = scroll_position.y;
if let Some(last_bounds) = self.expect_bounds_change.take()
&& scroll_position.y != 0.
{
scroll_position.y +=
ScrollOffset::from((bounds.top() - last_bounds.top()) / line_height);
scroll_position.y += (bounds.top() - last_bounds.top()) / line_height;
if scroll_position.y < 0. {
scroll_position.y = 0.;
}
@@ -145,7 +143,7 @@ impl Editor {
if let Some(first_highlighted_row) =
self.highlighted_display_row_for_autoscroll(&display_map)
{
target_top = first_highlighted_row.as_f64();
target_top = first_highlighted_row.as_f32();
target_bottom = target_top + 1.;
} else {
let selections = self.selections.all::<Point>(cx);
@@ -156,7 +154,7 @@ impl Editor {
.head()
.to_display_point(&display_map)
.row()
.as_f64();
.as_f32();
target_bottom = selections
.last()
.unwrap()
@@ -164,7 +162,7 @@ impl Editor {
.to_display_point(&display_map)
.row()
.next_row()
.as_f64();
.as_f32();
let selections_fit = target_bottom - target_top <= visible_lines;
if matches!(
@@ -180,7 +178,7 @@ impl Editor {
.head()
.to_display_point(&display_map)
.row()
.as_f64();
.as_f32();
target_top = newest_selection_top;
target_bottom = newest_selection_top + 1.;
}
@@ -211,7 +209,7 @@ impl Editor {
}
};
if let Autoscroll::Strategy(_, Some(anchor)) = autoscroll {
target_top = anchor.to_display_point(&display_map).row().as_f64();
target_top = anchor.to_display_point(&display_map).row().as_f32();
target_bottom = target_top + 1.;
}
@@ -256,11 +254,11 @@ impl Editor {
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::TopRelative(lines) => {
scroll_position.y = target_top - lines as ScrollOffset;
scroll_position.y = target_top - lines as f32;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::BottomRelative(lines) => {
scroll_position.y = target_bottom + lines as ScrollOffset;
scroll_position.y = target_bottom + lines as f32;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
};
@@ -286,25 +284,22 @@ impl Editor {
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Point<ScrollOffset>> {
) -> Option<gpui::Point<f32>> {
let (_, local) = autoscroll_request?;
let em_advance = ScrollOffset::from(em_advance);
let viewport_width = ScrollOffset::from(viewport_width);
let scroll_width = ScrollOffset::from(scroll_width);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let selections = self.selections.all::<Point>(cx);
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
let mut target_left;
let mut target_right: f64;
let mut target_right;
if self
.highlighted_display_row_for_autoscroll(&display_map)
.is_none()
{
target_left = f64::INFINITY;
target_right = 0.;
target_left = px(f32::INFINITY);
target_right = px(0.);
for selection in selections {
let head = selection.head().to_display_point(&display_map);
if head.row() >= start_row
@@ -312,22 +307,21 @@ impl Editor {
{
let start_column = head.column();
let end_column = cmp::min(display_map.line_len(head.row()), head.column());
target_left = target_left.min(ScrollOffset::from(
target_left = target_left.min(
layouts[head.row().minus(start_row) as usize]
.x_for_index(start_column as usize)
+ self.gutter_dimensions.margin,
));
);
target_right = target_right.max(
ScrollOffset::from(
layouts[head.row().minus(start_row) as usize]
.x_for_index(end_column as usize),
) + em_advance,
layouts[head.row().minus(start_row) as usize]
.x_for_index(end_column as usize)
+ em_advance,
);
}
}
} else {
target_left = 0.;
target_right = 0.;
target_left = px(0.);
target_right = px(0.);
}
target_right = target_right.min(scroll_width);

View File

@@ -1,5 +1,5 @@
use serde::Deserialize;
use ui::Pixels;
use ui::{Pixels, px};
#[derive(Debug)]
pub enum ScrollDirection {
@@ -28,41 +28,41 @@ pub enum ScrollAmount {
}
impl ScrollAmount {
pub fn lines(&self, mut visible_line_count: f64) -> f64 {
pub fn lines(&self, mut visible_line_count: f32) -> f32 {
match self {
Self::Line(count) => *count as f64,
Self::Line(count) => *count,
Self::Page(count) => {
// for full pages subtract one to leave an anchor line
if self.is_full_page() {
visible_line_count -= 1.0
}
(visible_line_count * (*count as f64)).trunc()
(visible_line_count * count).trunc()
}
Self::Column(_count) => 0.0,
Self::PageWidth(_count) => 0.0,
}
}
pub fn columns(&self, visible_column_count: f64) -> f64 {
pub fn columns(&self, visible_column_count: f32) -> f32 {
match self {
Self::Line(_count) => 0.0,
Self::Page(_count) => 0.0,
Self::Column(count) => *count as f64,
Self::PageWidth(count) => (visible_column_count * *count as f64).trunc(),
Self::Column(count) => *count,
Self::PageWidth(count) => (visible_column_count * count).trunc(),
}
}
pub fn pixels(&self, line_height: Pixels, height: Pixels) -> Pixels {
match self {
ScrollAmount::Line(x) => line_height * *x,
ScrollAmount::Page(x) => height * *x,
ScrollAmount::Line(x) => px(line_height.0 * x),
ScrollAmount::Page(x) => px(height.0 * x),
// This function seems to only be leveraged by the popover that is
// displayed by the editor when, for example, viewing a function's
// documentation. Right now that only supports vertical scrolling,
// so I'm leaving this at 0.0 for now to try and make it clear that
// this should not have an impact on that?
ScrollAmount::Column(_) => Pixels::ZERO,
ScrollAmount::PageWidth(_) => Pixels::ZERO,
ScrollAmount::Column(_) => px(0.0),
ScrollAmount::PageWidth(_) => px(0.0),
}
}

View File

@@ -225,13 +225,13 @@ impl SelectionsCollection {
let map = self.display_map(cx);
let start_ix = match self
.disjoint
.binary_search_by(|probe| probe.end.cmp(&range.start, map.buffer_snapshot()))
.binary_search_by(|probe| probe.end.cmp(&range.start, &map.buffer_snapshot))
{
Ok(ix) | Err(ix) => ix,
};
let end_ix = match self
.disjoint
.binary_search_by(|probe| probe.start.cmp(&range.end, map.buffer_snapshot()))
.binary_search_by(|probe| probe.start.cmp(&range.end, &map.buffer_snapshot))
{
Ok(ix) => ix + 1,
Err(ix) => ix,
@@ -332,9 +332,6 @@ impl SelectionsCollection {
self.all(cx).last().unwrap().clone()
}
/// Returns a list of (potentially backwards!) ranges representing the selections.
/// Useful for test assertions, but prefer `.all()` instead.
#[cfg(any(test, feature = "test-support"))]
pub fn ranges<D: TextDimension + Ord + Sub<D, Output = D>>(
&self,
cx: &mut App,
@@ -954,7 +951,7 @@ fn resolve_selections_point<'a>(
) -> impl 'a + Iterator<Item = Selection<Point>> {
let (to_summarize, selections) = selections.into_iter().tee();
let mut summaries = map
.buffer_snapshot()
.buffer_snapshot
.summaries_for_anchors::<Point, _>(to_summarize.flat_map(|s| [&s.start, &s.end]))
.into_iter();
selections.map(move |s| {
@@ -1014,7 +1011,7 @@ where
{
let (to_convert, selections) = resolve_selections_display(selections, map).tee();
let mut converted_endpoints =
map.buffer_snapshot()
map.buffer_snapshot
.dimensions_from_points::<D>(to_convert.flat_map(|s| {
let start = map.display_point_to_point(s.start, Bias::Left);
let end = map.display_point_to_point(s.end, Bias::Right);

View File

@@ -28,12 +28,12 @@ impl Editor {
let selection_range = selection.range();
let start = editor_snapshot
.display_snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_after(selection_range.start)
.text_anchor;
let end = editor_snapshot
.display_snapshot
.buffer_snapshot()
.buffer_snapshot
.anchor_after(selection_range.end)
.text_anchor;
let location = Location {

View File

@@ -186,7 +186,7 @@ pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestCo
match block {
Block::Custom(custom_block) => {
if let BlockPlacement::Near(x) = &custom_block.placement
&& snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot()))
&& snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot))
{
continue;
};

View File

@@ -303,8 +303,8 @@ impl EditorLspTestContext {
#[expect(clippy::wrong_self_convention, reason = "This is test code")]
pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));
let start_point = range.start.to_point(&snapshot.buffer_snapshot());
let end_point = range.end.to_point(&snapshot.buffer_snapshot());
let start_point = range.start.to_point(&snapshot.buffer_snapshot);
let end_point = range.end.to_point(&snapshot.buffer_snapshot);
self.editor(|editor, _, cx| {
let buffer = editor.buffer().read(cx);
@@ -330,7 +330,7 @@ impl EditorLspTestContext {
#[expect(clippy::wrong_self_convention, reason = "This is test code")]
pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
let snapshot = self.update_editor(|editor, window, cx| editor.snapshot(window, cx));
let point = offset.to_point(&snapshot.buffer_snapshot());
let point = offset.to_point(&snapshot.buffer_snapshot);
self.editor(|editor, _, cx| {
let buffer = editor.buffer().read(cx);

View File

@@ -275,8 +275,7 @@ impl EditorTestContext {
let details = editor.text_layout_details(window);
let y = pixel_position.y
+ f32::from(line_height)
* Pixels::from(display_point.row().as_f64() - newest_point.row().as_f64());
+ line_height * (display_point.row().as_f32() - newest_point.row().as_f32());
let x = pixel_position.x + snapshot.x_for_display_point(display_point, &details)
- snapshot.x_for_display_point(newest_point, &details);
Point::new(x, y)
@@ -504,7 +503,7 @@ impl EditorTestContext {
.map(|h| h.1.clone())
.unwrap_or_default()
.iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot()))
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect()
});
assert_set_eq!(actual_ranges, expected_ranges);
@@ -519,7 +518,7 @@ impl EditorTestContext {
.map(|ranges| ranges.as_ref().clone().1)
.unwrap_or_default()
.into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot()))
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect();
assert_set_eq!(actual_ranges, expected_ranges);
}
@@ -579,7 +578,7 @@ pub fn assert_state_with_diff(
) {
let (snapshot, selections) = editor.update_in(cx, |editor, window, cx| {
(
editor.snapshot(window, cx).buffer_snapshot().clone(),
editor.snapshot(window, cx).buffer_snapshot.clone(),
editor.selections.ranges::<usize>(cx),
)
});

View File

@@ -1,7 +1,5 @@
use collections::HashMap;
use extension::{
DownloadFileCapability, ExtensionCapability, NpmInstallPackageCapability, ProcessExecCapability,
};
use gpui::App;
use settings::Settings;
use std::sync::Arc;
@@ -15,7 +13,6 @@ pub struct ExtensionSettings {
/// Default: { "html": true }
pub auto_install_extensions: HashMap<Arc<str>, bool>,
pub auto_update_extensions: HashMap<Arc<str>, bool>,
pub granted_capabilities: Vec<ExtensionCapability>,
}
impl ExtensionSettings {
@@ -36,30 +33,10 @@ impl ExtensionSettings {
}
impl Settings for ExtensionSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self {
auto_install_extensions: content.extension.auto_install_extensions.clone(),
auto_update_extensions: content.extension.auto_update_extensions.clone(),
granted_capabilities: content
.extension
.granted_extension_capabilities
.clone()
.unwrap_or_default()
.into_iter()
.map(|capability| match capability {
settings::ExtensionCapabilityContent::ProcessExec { command, args } => {
ExtensionCapability::ProcessExec(ProcessExecCapability { command, args })
}
settings::ExtensionCapabilityContent::DownloadFile { host, path } => {
ExtensionCapability::DownloadFile(DownloadFileCapability { host, path })
}
settings::ExtensionCapabilityContent::NpmInstallPackage { package } => {
ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability {
package,
})
}
})
.collect(),
}
}
}

View File

@@ -1,15 +1,15 @@
pub mod wit;
use crate::ExtensionManifest;
use crate::capability_granter::CapabilityGranter;
use crate::{ExtensionManifest, ExtensionSettings};
use anyhow::{Context as _, Result, anyhow, bail};
use async_trait::async_trait;
use dap::{DebugRequest, StartDebuggingRequestArgumentsRequest};
use extension::{
CodeLabel, Command, Completion, ContextServerConfiguration, DebugAdapterBinary,
DebugTaskDefinition, ExtensionCapability, ExtensionHostProxy, KeyValueStoreDelegate,
ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol,
WorktreeDelegate,
DebugTaskDefinition, DownloadFileCapability, ExtensionCapability, ExtensionHostProxy,
KeyValueStoreDelegate, NpmInstallPackageCapability, ProcessExecCapability, ProjectDelegate,
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
};
use fs::{Fs, normalize_path};
use futures::future::LocalBoxFuture;
@@ -29,7 +29,6 @@ use moka::sync::Cache;
use node_runtime::NodeRuntime;
use release_channel::ReleaseChannel;
use semantic_version::SemanticVersion;
use settings::Settings;
use std::borrow::Cow;
use std::sync::{LazyLock, OnceLock};
use std::time::Duration;
@@ -570,9 +569,6 @@ impl WasmHost {
message(cx).await;
}
});
let extension_settings = ExtensionSettings::get_global(cx);
Arc::new(Self {
engine: wasm_engine(cx.background_executor()),
fs,
@@ -581,7 +577,19 @@ impl WasmHost {
node_runtime,
proxy,
release_channel: ReleaseChannel::global(cx),
granted_capabilities: extension_settings.granted_capabilities.clone(),
granted_capabilities: vec![
ExtensionCapability::ProcessExec(ProcessExecCapability {
command: "*".to_string(),
args: vec!["**".to_string()],
}),
ExtensionCapability::DownloadFile(DownloadFileCapability {
host: "*".to_string(),
path: vec!["**".to_string()],
}),
ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability {
package: "*".to_string(),
}),
],
_main_thread_message_task: task,
main_thread_message_tx: tx,
})

View File

@@ -1503,28 +1503,39 @@ impl Render for ExtensionsPage {
})),
)
.child(self.render_feature_upsells(cx))
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
let mut count = self.filtered_remote_extension_indices.len();
if self.filter.include_dev_extensions() {
count += self.dev_extension_entries.len();
}
.child(
v_flex()
.pl_4()
.pr_6()
.size_full()
.overflow_y_hidden()
.map(|this| {
let mut count = self.filtered_remote_extension_indices.len();
if self.filter.include_dev_extensions() {
count += self.dev_extension_entries.len();
}
if count == 0 {
this.py_4()
.child(self.render_empty_state(cx))
.into_any_element()
} else {
let scroll_handle = self.list.clone();
this.child(
uniform_list("entries", count, cx.processor(Self::render_extensions))
.flex_grow()
.pb_4()
.track_scroll(scroll_handle.clone()),
)
.vertical_scrollbar_for(scroll_handle, window, cx)
.into_any_element()
}
}))
if count == 0 {
this.py_4()
.child(self.render_empty_state(cx))
.into_any_element()
} else {
let scroll_handle = self.list.clone();
this.child(
uniform_list(
"entries",
count,
cx.processor(Self::render_extensions),
)
.flex_grow()
.pb_4()
.track_scroll(scroll_handle.clone()),
)
.vertical_scrollbar_for(scroll_handle, window, cx)
.into_any_element()
}
}),
)
}
}

View File

@@ -355,9 +355,9 @@ impl FileFinder {
match width_setting {
FileFinderWidth::Small => small_width,
FileFinderWidth::Full => window_width,
FileFinderWidth::XLarge => (window_width - px(512.)).max(small_width),
FileFinderWidth::Large => (window_width - px(768.)).max(small_width),
FileFinderWidth::Medium => (window_width - px(1024.)).max(small_width),
FileFinderWidth::XLarge => (window_width - Pixels(512.)).max(small_width),
FileFinderWidth::Large => (window_width - Pixels(768.)).max(small_width),
FileFinderWidth::Medium => (window_width - Pixels(1024.)).max(small_width),
}
}
}

View File

@@ -11,7 +11,7 @@ pub struct FileFinderSettings {
}
impl Settings for FileFinderSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let file_finder = content.file_finder.as_ref().unwrap();
Self {

View File

@@ -755,7 +755,7 @@ impl PickerDelegate for OpenPathDelegate {
.with_default_highlights(
&window.text_style(),
vec![(
delta..label_len,
delta..delta + label_len,
HighlightStyle::color(Color::Conflict.color(cx)),
)],
)
@@ -765,7 +765,7 @@ impl PickerDelegate for OpenPathDelegate {
.with_default_highlights(
&window.text_style(),
vec![(
delta..label_len,
delta..delta + label_len,
HighlightStyle::color(Color::Created.color(cx)),
)],
)

View File

@@ -15,6 +15,7 @@ doctest = false
[dependencies]
gpui.workspace = true
serde.workspace = true
settings.workspace = true
theme.workspace = true
util.workspace = true
workspace-hack.workspace = true

View File

@@ -2,7 +2,8 @@ use std::sync::Arc;
use std::{path::Path, str};
use gpui::{App, SharedString};
use theme::{GlobalTheme, IconTheme, ThemeRegistry};
use settings::Settings;
use theme::{IconTheme, ThemeRegistry, ThemeSettings};
use util::paths::PathExt;
#[derive(Debug)]
@@ -12,8 +13,10 @@ pub struct FileIcons {
impl FileIcons {
pub fn get(cx: &App) -> Self {
let theme_settings = ThemeSettings::get_global(cx);
Self {
icon_theme: GlobalTheme::icon_theme(cx).clone(),
icon_theme: theme_settings.active_icon_theme.clone(),
}
}
@@ -49,15 +52,6 @@ impl FileIcons {
}
}
// handle cases where the file extension is made up of multiple important
// parts (e.g Component.stories.tsx) that refer to an alternative icon style
if let Some(suffix) = path.multiple_extensions() {
let maybe_path = get_icon_from_suffix(suffix.as_str());
if maybe_path.is_some() {
return maybe_path;
}
}
// primary case: check if the files extension or the hidden file name
// matches some icon path
if let Some(suffix) = path.extension_or_hidden_file_name() {
@@ -94,7 +88,7 @@ impl FileIcons {
.map(|icon_definition| icon_definition.path.clone())
}
get_icon_for_type(GlobalTheme::icon_theme(cx), typ).or_else(|| {
get_icon_for_type(&ThemeSettings::get_global(cx).active_icon_theme, typ).or_else(|| {
Self::default_icon_theme(cx).and_then(|icon_theme| get_icon_for_type(&icon_theme, typ))
})
}
@@ -119,16 +113,20 @@ impl FileIcons {
}
}
get_folder_icon(GlobalTheme::icon_theme(cx), path, expanded)
.or_else(|| {
Self::default_icon_theme(cx)
.and_then(|icon_theme| get_folder_icon(&icon_theme, path, expanded))
})
.or_else(|| {
// If we can't find a specific folder icon for the folder at the given path, fall back to the generic folder
// icon.
Self::get_generic_folder_icon(expanded, cx)
})
get_folder_icon(
&ThemeSettings::get_global(cx).active_icon_theme,
path,
expanded,
)
.or_else(|| {
Self::default_icon_theme(cx)
.and_then(|icon_theme| get_folder_icon(&icon_theme, path, expanded))
})
.or_else(|| {
// If we can't find a specific folder icon for the folder at the given path, fall back to the generic folder
// icon.
Self::get_generic_folder_icon(expanded, cx)
})
}
fn get_generic_folder_icon(expanded: bool, cx: &App) -> Option<SharedString> {
@@ -143,10 +141,12 @@ impl FileIcons {
}
}
get_generic_folder_icon(GlobalTheme::icon_theme(cx), expanded).or_else(|| {
Self::default_icon_theme(cx)
.and_then(|icon_theme| get_generic_folder_icon(&icon_theme, expanded))
})
get_generic_folder_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(
|| {
Self::default_icon_theme(cx)
.and_then(|icon_theme| get_generic_folder_icon(&icon_theme, expanded))
},
)
}
pub fn get_chevron_icon(expanded: bool, cx: &App) -> Option<SharedString> {
@@ -158,7 +158,7 @@ impl FileIcons {
}
}
get_chevron_icon(GlobalTheme::icon_theme(cx), expanded).or_else(|| {
get_chevron_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
Self::default_icon_theme(cx)
.and_then(|icon_theme| get_chevron_icon(&icon_theme, expanded))
})

View File

@@ -753,9 +753,7 @@ impl Fs for RealFs {
Some(PathEventKind::Removed)
} else if event.flags.contains(StreamFlags::ITEM_CREATED) {
Some(PathEventKind::Created)
} else if event.flags.contains(StreamFlags::ITEM_MODIFIED)
| event.flags.contains(StreamFlags::ITEM_RENAMED)
{
} else if event.flags.contains(StreamFlags::ITEM_MODIFIED) {
Some(PathEventKind::Changed)
} else {
None
@@ -793,15 +791,11 @@ impl Fs for RealFs {
let watcher = Arc::new(fs_watcher::FsWatcher::new(tx, pending_paths.clone()));
// If the path doesn't exist yet (e.g. settings.json), watch the parent dir to learn when it's created.
if let Err(e) = watcher.add(path)
if watcher.add(path).is_err()
&& let Some(parent) = path.parent()
&& let Err(parent_e) = watcher.add(parent)
&& let Err(e) = watcher.add(parent)
{
log::warn!(
"Failed to watch {} and its parent directory {}:\n{e}\n{parent_e}",
path.display(),
parent.display()
);
log::warn!("Failed to watch: {e}");
}
// Check if path is a symlink and follow the target parent
@@ -1263,7 +1257,7 @@ impl FakeFs {
async move {
while let Ok(git_event) = rx.recv().await {
if let Some(mut state) = this.state.try_lock() {
state.emit_event([(git_event, Some(PathEventKind::Changed))]);
state.emit_event([(git_event, None)]);
} else {
panic!("Failed to lock file system state, this execution would have caused a test hang");
}
@@ -1310,7 +1304,7 @@ impl FakeFs {
Ok(())
})
.unwrap();
state.emit_event([(path.to_path_buf(), Some(PathEventKind::Changed))]);
state.emit_event([(path.to_path_buf(), None)]);
}
pub async fn insert_file(&self, path: impl AsRef<Path>, content: Vec<u8>) {
@@ -1333,7 +1327,7 @@ impl FakeFs {
}
})
.unwrap();
state.emit_event([(path, Some(PathEventKind::Created))]);
state.emit_event([(path, None)]);
}
fn write_file_internal(
@@ -1524,7 +1518,7 @@ impl FakeFs {
drop(repo_state);
if emit_git_event {
state.emit_event([(dot_git, Some(PathEventKind::Changed))]);
state.emit_event([(dot_git, None)]);
}
Ok(result)
@@ -1575,7 +1569,7 @@ impl FakeFs {
if emit_git_event {
drop(repo_state);
state.emit_event([(canonical_path, Some(PathEventKind::Changed))]);
state.emit_event([(canonical_path, None)]);
}
Ok(result)
@@ -1888,10 +1882,6 @@ impl FakeFs {
.unwrap_or(0)
}
pub fn emit_fs_event(&self, path: impl Into<PathBuf>, event: Option<PathEventKind>) {
self.state.lock().emit_event(std::iter::once((path, event)));
}
fn simulate_random_delay(&self) -> impl futures::Future<Output = ()> {
self.executor.simulate_random_delay()
}
@@ -2059,7 +2049,7 @@ impl Fs for FakeFs {
}
})
.unwrap();
state.emit_event([(path, Some(PathEventKind::Created))]);
state.emit_event([(path, None)]);
Ok(())
}

View File

@@ -1,8 +1,7 @@
use notify::EventKind;
use parking_lot::Mutex;
use std::{
collections::{BTreeMap, HashMap},
ops::DerefMut,
collections::HashMap,
sync::{Arc, OnceLock},
};
use util::{ResultExt, paths::SanitizedPath};
@@ -12,7 +11,7 @@ use crate::{PathEvent, PathEventKind, Watcher};
pub struct FsWatcher {
tx: smol::channel::Sender<()>,
pending_path_events: Arc<Mutex<Vec<PathEvent>>>,
registrations: Mutex<BTreeMap<Arc<std::path::Path>, WatcherRegistrationId>>,
registrations: Mutex<HashMap<Arc<std::path::Path>, WatcherRegistrationId>>,
}
impl FsWatcher {
@@ -30,11 +29,8 @@ impl FsWatcher {
impl Drop for FsWatcher {
fn drop(&mut self) {
let mut registrations = BTreeMap::new();
{
let old = &mut self.registrations.lock();
std::mem::swap(old.deref_mut(), &mut registrations);
}
let mut registrations = self.registrations.lock();
let registrations = registrations.drain();
let _ = global(|g| {
for (_, registration) in registrations {
@@ -46,77 +42,57 @@ impl Drop for FsWatcher {
impl Watcher for FsWatcher {
fn add(&self, path: &std::path::Path) -> anyhow::Result<()> {
let root_path = SanitizedPath::new_arc(path);
let tx = self.tx.clone();
let pending_paths = self.pending_path_events.clone();
#[cfg(target_os = "windows")]
{
// Return early if an ancestor of this path was already being watched.
// saves a huge amount of memory
if let Some((watched_path, _)) = self
.registrations
.lock()
.range::<std::path::Path, _>((
std::ops::Bound::Unbounded,
std::ops::Bound::Included(path),
))
.next_back()
&& path.starts_with(watched_path.as_ref())
{
return Ok(());
}
}
#[cfg(target_os = "linux")]
{
if self.registrations.lock().contains_key(path) {
return Ok(());
}
}
let root_path = SanitizedPath::new_arc(path);
let path: Arc<std::path::Path> = path.into();
#[cfg(target_os = "windows")]
let mode = notify::RecursiveMode::Recursive;
#[cfg(target_os = "linux")]
let mode = notify::RecursiveMode::NonRecursive;
if self.registrations.lock().contains_key(&path) {
return Ok(());
}
let registration_id = global({
let path = path.clone();
|g| {
g.add(path, mode, 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 path_events = event
.paths
.iter()
.filter_map(|event_path| {
let event_path = SanitizedPath::new(event_path);
event_path.starts_with(&root_path).then(|| PathEvent {
path: event_path.as_path().to_path_buf(),
kind,
g.add(
path,
notify::RecursiveMode::NonRecursive,
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 path_events = event
.paths
.iter()
.filter_map(|event_path| {
let event_path = SanitizedPath::new(event_path);
event_path.starts_with(&root_path).then(|| PathEvent {
path: event_path.as_path().to_path_buf(),
kind,
})
})
})
.collect::<Vec<_>>();
.collect::<Vec<_>>();
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
if !path_events.is_empty() {
path_events.sort();
let mut pending_paths = pending_paths.lock();
if pending_paths.is_empty() {
tx.try_send(()).ok();
}
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
util::extend_sorted(
&mut *pending_paths,
path_events,
usize::MAX,
|a, b| a.path.cmp(&b.path),
);
}
})
},
)
}
})??;

View File

@@ -58,7 +58,7 @@ pub struct GitHostingProviderSettings {
}
impl Settings for GitHostingProviderSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
Self {
git_hosting_providers: content
.project

View File

@@ -40,8 +40,7 @@ impl ModalContainerProperties {
let font_size = style.font_size.to_pixels(window.rem_size());
if let Ok(em_width) = window.text_system().em_width(font_id, font_size) {
modal_width =
f32::from(preferred_char_width as f32 * em_width + px(container_padding * 2.0));
modal_width = preferred_char_width as f32 * em_width.0 + (container_padding * 2.0);
}
Self {

View File

@@ -43,8 +43,8 @@ struct CommitMetadataFile {
worktree_id: WorktreeId,
}
const COMMIT_METADATA_NAMESPACE: u64 = 0;
const FILE_NAMESPACE: u64 = 1;
const COMMIT_METADATA_NAMESPACE: u32 = 0;
const FILE_NAMESPACE: u32 = 1;
impl CommitView {
pub fn open(
@@ -145,7 +145,7 @@ impl CommitView {
});
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.set_excerpts_for_path(
PathKey::namespaced(COMMIT_METADATA_NAMESPACE, file.title.clone()),
PathKey::namespaced(COMMIT_METADATA_NAMESPACE, file.title.as_unix_str().into()),
buffer.clone(),
vec![Point::zero()..buffer.read(cx).max_point()],
0,
@@ -193,7 +193,7 @@ impl CommitView {
.collect::<Vec<_>>();
let path = snapshot.file().unwrap().path().clone();
let _is_newly_added = multibuffer.set_excerpts_for_path(
PathKey::namespaced(FILE_NAMESPACE, path),
PathKey::namespaced(FILE_NAMESPACE, path.as_unix_str().into()),
buffer,
diff_hunk_ranges,
multibuffer_context_lines(cx),
@@ -452,6 +452,10 @@ impl Item for CommitView {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,

View File

@@ -263,6 +263,10 @@ impl Item for FileDiffView {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
@@ -360,7 +364,7 @@ mod tests {
use editor::test::editor_test_context::assert_state_with_diff;
use gpui::TestAppContext;
use project::{FakeFs, Fs, Project};
use settings::SettingsStore;
use settings::{Settings, SettingsStore};
use std::path::PathBuf;
use unindent::unindent;
use util::path;
@@ -374,7 +378,7 @@ mod tests {
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
theme::ThemeSettings::register(cx)
});
}

View File

@@ -43,7 +43,7 @@ impl ScrollbarVisibility for GitPanelSettings {
}
impl Settings for GitPanelSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let git_panel = content.git_panel.clone().unwrap();
Self {
button: git_panel.button.unwrap(),

View File

@@ -27,7 +27,7 @@ use language::{Anchor, Buffer, Capability, OffsetRangeExt};
use multi_buffer::{MultiBuffer, PathKey};
use project::{
Project, ProjectPath,
git_store::{GitStore, GitStoreEvent},
git_store::{GitStore, GitStoreEvent, RepositoryEvent},
};
use settings::{Settings, SettingsStore};
use std::any::{Any, TypeId};
@@ -73,9 +73,9 @@ struct DiffBuffer {
file_status: FileStatus,
}
const CONFLICT_NAMESPACE: u64 = 1;
const TRACKED_NAMESPACE: u64 = 2;
const NEW_NAMESPACE: u64 = 3;
const CONFLICT_NAMESPACE: u32 = 1;
const TRACKED_NAMESPACE: u32 = 2;
const NEW_NAMESPACE: u32 = 3;
impl ProjectDiff {
pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
@@ -177,7 +177,7 @@ impl ProjectDiff {
window,
move |this, _git_store, event, _window, _cx| match event {
GitStoreEvent::ActiveRepositoryChanged(_)
| GitStoreEvent::RepositoryUpdated(_, _, true)
| GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, true)
| GitStoreEvent::ConflictsUpdated => {
*this.update_needed.borrow_mut() = ();
}
@@ -243,7 +243,7 @@ impl ProjectDiff {
TRACKED_NAMESPACE
};
let path_key = PathKey::namespaced(namespace, entry.repo_path.0);
let path_key = PathKey::namespaced(namespace, entry.repo_path.as_unix_str().into());
self.move_to_path(path_key, window, cx)
}
@@ -397,7 +397,7 @@ impl ProjectDiff {
} else {
TRACKED_NAMESPACE
};
let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone());
let path_key = PathKey::namespaced(namespace, entry.repo_path.as_unix_str().into());
previous_paths.remove(&path_key);
let load_buffer = self
@@ -531,12 +531,11 @@ impl ProjectDiff {
}
#[cfg(any(test, feature = "test-support"))]
pub fn excerpt_paths(&self, cx: &App) -> Vec<std::sync::Arc<util::rel_path::RelPath>> {
pub fn excerpt_paths(&self, cx: &App) -> Vec<String> {
self.multibuffer
.read(cx)
.excerpt_paths()
.map(|key| key.path())
.cloned()
.map(|key| key.path().to_string())
.collect()
}
}
@@ -613,6 +612,10 @@ impl Item for ProjectDiff {
self.editor.for_each_project_item(cx, f)
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn set_nav_history(
&mut self,
nav_history: ItemNavHistory,
@@ -1346,6 +1349,7 @@ fn merge_anchor_ranges<'a>(
})
}
#[cfg(not(target_os = "windows"))]
#[cfg(test)]
mod tests {
use db::indoc;
@@ -1357,7 +1361,7 @@ mod tests {
use settings::SettingsStore;
use std::path::Path;
use unindent::Unindent as _;
use util::{path, rel_path::rel_path};
use util::path;
use super::*;
@@ -1463,7 +1467,7 @@ mod tests {
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
diff.move_to_path(
PathKey::namespaced(TRACKED_NAMESPACE, rel_path("foo").into_arc()),
PathKey::namespaced(TRACKED_NAMESPACE, "foo".into()),
window,
cx,
);
@@ -1484,7 +1488,7 @@ mod tests {
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
diff.move_to_path(
PathKey::namespaced(TRACKED_NAMESPACE, rel_path("bar").into_arc()),
PathKey::namespaced(TRACKED_NAMESPACE, "bar".into()),
window,
cx,
);
@@ -1556,7 +1560,7 @@ mod tests {
let prev_buffer_hunks =
cx.update_window_entity(&buffer_editor, |buffer_editor, window, cx| {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot();
let snapshot = &snapshot.buffer_snapshot;
let prev_buffer_hunks = buffer_editor
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
.collect::<Vec<_>>();
@@ -1569,7 +1573,7 @@ mod tests {
let new_buffer_hunks =
cx.update_window_entity(&buffer_editor, |buffer_editor, window, cx| {
let snapshot = buffer_editor.snapshot(window, cx);
let snapshot = &snapshot.buffer_snapshot();
let snapshot = &snapshot.buffer_snapshot;
buffer_editor
.diff_hunks_in_ranges(&[editor::Anchor::min()..editor::Anchor::max()], snapshot)
.collect::<Vec<_>>()
@@ -1623,7 +1627,6 @@ mod tests {
project_diff::{self, ProjectDiff},
};
#[cfg_attr(windows, ignore = "currently fails on windows")]
#[gpui::test]
async fn test_go_to_prev_hunk_multibuffer(cx: &mut TestAppContext) {
init_test(cx);
@@ -1711,7 +1714,6 @@ mod tests {
));
}
#[cfg_attr(windows, ignore = "currently fails on windows")]
#[gpui::test]
async fn test_excerpts_splitting_after_restoring_the_middle_excerpt(cx: &mut TestAppContext) {
init_test(cx);
@@ -1870,128 +1872,4 @@ mod tests {
let contents = String::from_utf8(contents).unwrap();
assert_eq!(contents, "ours\n");
}
#[gpui::test]
async fn test_new_hunk_in_modified_file(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"foo.txt": "
one
two
three
four
five
six
seven
eight
nine
ten
ELEVEN
twelve
".unindent()
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let diff = cx.new_window_entity(|window, cx| {
ProjectDiff::new(project.clone(), workspace, window, cx)
});
cx.run_until_parked();
fs.set_head_and_index_for_repo(
Path::new(path!("/project/.git")),
&[(
"foo.txt",
"
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
"
.unindent(),
)],
);
cx.run_until_parked();
let editor = diff.read_with(cx, |diff, _| diff.editor.clone());
assert_state_with_diff(
&editor,
cx,
&"
ˇnine
ten
- eleven
+ ELEVEN
twelve
"
.unindent(),
);
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(path!("/project/foo.txt"), cx)
})
.await
.unwrap();
buffer.update(cx, |buffer, cx| {
buffer.edit_via_marked_text(
&"
one
«TWO»
three
four
five
six
seven
eight
nine
ten
ELEVEN
twelve
"
.unindent(),
None,
cx,
);
});
project
.update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
.await
.unwrap();
cx.run_until_parked();
assert_state_with_diff(
&editor,
cx,
&"
one
- two
+ TWO
three
four
five
ˇnine
ten
- eleven
+ ELEVEN
twelve
"
.unindent(),
);
}
}

View File

@@ -324,6 +324,10 @@ impl Item for TextDiffView {
.update(cx, |editor, cx| editor.deactivated(window, cx));
}
fn is_singleton(&self, _: &App) -> bool {
false
}
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
@@ -450,7 +454,7 @@ mod tests {
use gpui::{TestAppContext, VisualContext};
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use settings::{Settings, SettingsStore};
use unindent::unindent;
use util::{path, test::marked_text_ranges};
@@ -462,7 +466,7 @@ mod tests {
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
theme::ThemeSettings::register(cx)
});
}

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