Compare commits

..

1 Commits

Author SHA1 Message Date
Smit Barmase
acac967c63 fix macos focus issue 2025-10-03 18:57:12 +05:30
284 changed files with 7367 additions and 15433 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' }}

1143
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,8 +369,7 @@
"bindings": {
"new": "rules_library::NewRule",
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
}
},
{
@@ -1243,27 +1241,5 @@
"bindings": {
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow",
"ctrl-f": "search::FocusSearch",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"ctrl-pageup": "settings_editor::FocusPreviousFile",
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
}
]

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",
@@ -1348,27 +1347,5 @@
"bindings": {
"ctrl-shift-enter": "workspace::OpenWithSystem"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"cmd-w": "workspace::CloseWindow",
"cmd-f": "search::FocusSearch",
"cmd-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"cmd-{": "settings_editor::FocusPreviousFile",
"cmd-}": "settings_editor::FocusNextFile"
}
}
]

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,8 +378,7 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "rules_library::NewRule",
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
"ctrl-w": "workspace::CloseWindow"
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
}
},
{
@@ -1264,27 +1262,5 @@
"alt-shift-l": "onboarding::SignIn",
"shift-alt-a": "onboarding::OpenAccount"
}
},
{
"context": "SettingsWindow",
"use_key_equivalents": true,
"bindings": {
"ctrl-w": "workspace::CloseWindow",
"ctrl-f": "search::FocusSearch",
"ctrl-shift-e": "settings_editor::ToggleFocusNav",
// todo(settings_ui): cut this down based on the max files and overflow UI
"ctrl-1": ["settings_editor::FocusFile", 0],
"ctrl-2": ["settings_editor::FocusFile", 1],
"ctrl-3": ["settings_editor::FocusFile", 2],
"ctrl-4": ["settings_editor::FocusFile", 3],
"ctrl-5": ["settings_editor::FocusFile", 4],
"ctrl-6": ["settings_editor::FocusFile", 5],
"ctrl-7": ["settings_editor::FocusFile", 6],
"ctrl-8": ["settings_editor::FocusFile", 7],
"ctrl-9": ["settings_editor::FocusFile", 8],
"ctrl-0": ["settings_editor::FocusFile", 9],
"ctrl-pageup": "settings_editor::FocusPreviousFile",
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
}
]

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

@@ -1276,6 +1276,62 @@ impl Thread {
);
}
pub fn retry_last_completion(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
// Clear any existing error state
self.retry_state = None;
// Use the last error context if available, otherwise fall back to configured model
let (model, intent) = if let Some((model, intent)) = self.last_error_context.take() {
(model, intent)
} else if let Some(configured_model) = self.configured_model.as_ref() {
let model = configured_model.model.clone();
let intent = if self.has_pending_tool_uses() {
CompletionIntent::ToolResults
} else {
CompletionIntent::UserPrompt
};
(model, intent)
} else if let Some(configured_model) = self.get_or_init_configured_model(cx) {
let model = configured_model.model.clone();
let intent = if self.has_pending_tool_uses() {
CompletionIntent::ToolResults
} else {
CompletionIntent::UserPrompt
};
(model, intent)
} else {
return;
};
self.send_to_model(model, intent, window, cx);
}
pub fn enable_burn_mode_and_retry(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
self.completion_mode = CompletionMode::Burn;
cx.emit(ThreadEvent::ProfileChanged);
self.retry_last_completion(window, cx);
}
pub fn used_tools_since_last_user_message(&self) -> bool {
for message in self.messages.iter().rev() {
if self.tool_use.message_has_tool_results(message.id) {
return true;
} else if message.role == Role::User {
return false;
}
}
false
}
pub fn to_completion_request(
&self,
model: Arc<dyn LanguageModel>,

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

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

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

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

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,

View File

@@ -53,7 +53,7 @@ use gpui::{
};
use language::LanguageRegistry;
use language_model::{ConfigurationError, LanguageModelRegistry};
use project::{Project, ProjectPath, Worktree};
use project::{DisableAiSettings, Project, ProjectPath, Worktree};
use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
use rules_library::{RulesLibrary, open_rules_library};
use search::{BufferSearchBar, buffer_search};
@@ -519,7 +519,6 @@ impl AgentPanel {
cx,
)
});
panel.as_mut(cx).loading = true;
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
@@ -671,6 +670,43 @@ impl AgentPanel {
)
});
let mut old_disable_ai = false;
cx.observe_global_in::<SettingsStore>(window, move |panel, window, cx| {
let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
if old_disable_ai != disable_ai {
let agent_panel_id = cx.entity_id();
let agent_panel_visible = panel
.workspace
.update(cx, |workspace, cx| {
let agent_dock_position = panel.position(window, cx);
let agent_dock = workspace.dock_at_position(agent_dock_position);
let agent_panel_focused = agent_dock
.read(cx)
.active_panel()
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
let active_panel_visible = agent_dock
.read(cx)
.visible_panel()
.is_some_and(|panel| panel.panel_id() == agent_panel_id);
if agent_panel_focused {
cx.dispatch_action(&ToggleFocus);
}
active_panel_visible
})
.unwrap_or_default();
if agent_panel_visible {
cx.emit(PanelEvent::Close);
}
old_disable_ai = disable_ai;
}
})
.detach();
Self {
active_view,
workspace,
@@ -1072,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 => {
@@ -1100,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);
}
}
@@ -2534,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()

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

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

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

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

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

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

@@ -41,9 +41,12 @@ impl StdioTransport {
command.current_dir(working_directory);
}
let mut server = command
.spawn()
.with_context(|| format!("failed to spawn command {command:?})",))?;
let mut server = command.spawn().with_context(|| {
format!(
"failed to spawn command. (path={:?}, args={:?})",
binary.executable, &binary.args
)
})?;
let stdin = server.stdin.take().unwrap();
let stdout = server.stdout.take().unwrap();

View File

@@ -674,7 +674,13 @@ impl StdioTransport {
command.args(&binary.arguments);
command.envs(&binary.envs);
let mut process = Child::spawn(command, Stdio::piped())?;
let mut process = Child::spawn(command, Stdio::piped()).with_context(|| {
format!(
"failed to spawn command `{} {}`.",
binary_command,
binary.arguments.join(" ")
)
})?;
let err_task = process.stderr.take().map(|stderr| {
cx.background_spawn(TransportDelegate::handle_adapter_log(
@@ -1052,13 +1058,11 @@ impl Child {
#[cfg(not(windows))]
fn spawn(mut command: std::process::Command, stdin: Stdio) -> Result<Self> {
util::set_pre_exec_to_start_new_session(&mut command);
let mut command = smol::process::Command::from(command);
let process = command
let process = smol::process::Command::from(command)
.stdin(stdin)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("failed to spawn command `{command:?}`",))?;
.spawn()?;
Ok(Self { process })
}
@@ -1066,13 +1070,11 @@ impl Child {
fn spawn(command: std::process::Command, stdin: Stdio) -> Result<Self> {
// TODO(windows): create a job object and add the child process handle to it,
// see https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects
let mut command = smol::process::Command::from(command);
let process = command
let process = smol::process::Command::from(command)
.stdin(stdin)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("failed to spawn command `{command:?}`",))?;
.spawn()?;
Ok(Self { process })
}

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,
@@ -689,7 +689,6 @@ impl BlockMap {
// For each of these blocks, insert a new isomorphic transform preceding the block,
// and then insert the block itself.
let mut just_processed_folded_buffer = false;
for (block_placement, block) in blocks_in_edit.drain(..) {
let mut summary = TransformSummary {
input_rows: 0,
@@ -702,12 +701,8 @@ impl BlockMap {
match block_placement {
BlockPlacement::Above(position) => {
rows_before_block = position.0 - new_transforms.summary().input_rows;
just_processed_folded_buffer = false;
}
BlockPlacement::Near(position) | BlockPlacement::Below(position) => {
if just_processed_folded_buffer {
continue;
}
if position.0 + 1 < new_transforms.summary().input_rows {
continue;
}
@@ -716,7 +711,6 @@ impl BlockMap {
BlockPlacement::Replace(range) => {
rows_before_block = range.start().0 - new_transforms.summary().input_rows;
summary.input_rows = range.end().0 - range.start().0 + 1;
just_processed_folded_buffer = matches!(block, Block::FoldedBuffer { .. });
}
}
@@ -1401,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);
@@ -3572,96 +3566,6 @@ mod tests {
assert_eq!(blocks_snapshot.text(), "abc\n\ndef\nghi\njkl\nmno");
}
#[gpui::test]
fn test_folded_buffer_with_near_blocks(cx: &mut gpui::TestAppContext) {
cx.update(init_test);
let text = "line 1\nline 2\nline 3";
let buffer = cx.update(|cx| {
MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(2, 6)])], cx)
});
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 1);
let buffer_id = buffer_ids[0];
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
writer.insert(vec![BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(0, 0))),
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
}]);
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
assert_eq!(blocks_snapshot.text(), "\nline 1\n\nline 2\nline 3");
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| {
writer.fold_buffers([buffer_id], buffer, cx);
});
let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
assert_eq!(blocks_snapshot.text(), "");
}
#[gpui::test]
fn test_folded_buffer_with_near_blocks_on_last_line(cx: &mut gpui::TestAppContext) {
cx.update(init_test);
let text = "line 1\nline 2\nline 3\nline 4";
let buffer = cx.update(|cx| {
MultiBuffer::build_multi([(text, vec![Point::new(0, 0)..Point::new(3, 6)])], cx)
});
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let buffer_ids = buffer_snapshot
.excerpts()
.map(|(_, buffer_snapshot, _)| buffer_snapshot.remote_id())
.dedup()
.collect::<Vec<_>>();
assert_eq!(buffer_ids.len(), 1);
let buffer_id = buffer_ids[0];
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wrap_snapshot) =
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wrap_snapshot.clone(), 1, 1);
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
writer.insert(vec![BlockProperties {
style: BlockStyle::Fixed,
placement: BlockPlacement::Near(buffer_snapshot.anchor_after(Point::new(3, 6))),
height: Some(1),
render: Arc::new(|_| div().into_any()),
priority: 0,
}]);
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
assert_eq!(blocks_snapshot.text(), "\nline 1\nline 2\nline 3\nline 4\n");
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
buffer.read_with(cx, |buffer, cx| {
writer.fold_buffers([buffer_id], buffer, cx);
});
let blocks_snapshot = block_map.read(wrap_snapshot, Patch::default());
assert_eq!(blocks_snapshot.text(), "");
}
fn init_test(cx: &mut gpui::App) {
let settings = SettingsStore::test(cx);
cx.set_global(settings);

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,
@@ -182,6 +195,7 @@ impl Settings for EditorSettings {
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,4 @@
use collections::HashMap;
use extension::{
DownloadFileCapability, ExtensionCapability, NpmInstallPackageCapability, ProcessExecCapability,
};
use gpui::App;
use settings::Settings;
use std::sync::Arc;
@@ -16,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 {
@@ -41,26 +37,6 @@ impl Settings for ExtensionSettings {
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

@@ -29,7 +29,7 @@ use ui::{
};
use vim_mode_setting::VimModeSetting;
use workspace::{
Workspace,
Workspace, WorkspaceId,
item::{Item, ItemEvent},
};
use zed_actions::ExtensionCategoryFilter;
@@ -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()
}
}),
)
}
}
@@ -1551,6 +1562,15 @@ impl Item for ExtensionsPage {
false
}
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
_window: &mut Window,
_: &mut Context<Self>,
) -> Option<Entity<Self>> {
None
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
f(*event)
}

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

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

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

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

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

View File

@@ -386,7 +386,6 @@ impl GitPanel {
cx.observe_global_in::<SettingsStore>(window, move |this, window, cx| {
let is_sort_by_path = GitPanelSettings::get_global(cx).sort_by_path;
if is_sort_by_path != was_sort_by_path {
this.entries.clear();
this.update_visible_entries(window, cx);
}
was_sort_by_path = is_sort_by_path
@@ -4888,7 +4887,7 @@ mod tests {
repository::repo_path,
status::{StatusCode, UnmergedStatus, UnmergedStatusCode},
};
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
use gpui::{TestAppContext, VisualTestContext};
use project::{FakeFs, WorktreeSettings};
use serde_json::json;
use settings::SettingsStore;
@@ -5211,242 +5210,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_bulk_staging_with_sort_by_paths(cx: &mut TestAppContext) {
use GitListEntry::*;
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/root",
json!({
"project": {
".git": {},
"src": {
"main.rs": "fn main() {}",
"lib.rs": "pub fn hello() {}",
"utils.rs": "pub fn util() {}"
},
"tests": {
"test.rs": "fn test() {}"
},
"new_file.txt": "new content",
"another_new.rs": "// new file",
"conflict.txt": "conflicted content"
}
}),
)
.await;
fs.set_status_for_repo(
Path::new(path!("/root/project/.git")),
&[
("src/main.rs", StatusCode::Modified.worktree()),
("src/lib.rs", StatusCode::Modified.worktree()),
("tests/test.rs", StatusCode::Modified.worktree()),
("new_file.txt", FileStatus::Untracked),
("another_new.rs", FileStatus::Untracked),
("src/utils.rs", FileStatus::Untracked),
(
"conflict.txt",
UnmergedStatus {
first_head: UnmergedStatusCode::Updated,
second_head: UnmergedStatusCode::Updated,
}
.into(),
),
],
);
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
let workspace =
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let cx = &mut VisualTestContext::from_window(*workspace, cx);
cx.read(|cx| {
project
.read(cx)
.worktrees(cx)
.next()
.unwrap()
.read(cx)
.as_local()
.unwrap()
.scan_complete()
})
.await;
cx.executor().run_until_parked();
let panel = workspace.update(cx, GitPanel::new).unwrap();
let handle = cx.update_window_entity(&panel, |panel, _, _| {
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
});
cx.executor().advance_clock(2 * UPDATE_DEBOUNCE);
handle.await;
let entries = panel.read_with(cx, |panel, _| panel.entries.clone());
#[rustfmt::skip]
pretty_assertions::assert_matches!(
entries.as_slice(),
&[
Header(GitHeaderEntry { header: Section::Conflict }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Header(GitHeaderEntry { header: Section::Tracked }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Header(GitHeaderEntry { header: Section::New }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { staging: StageStatus::Unstaged, .. }),
],
);
assert_entry_paths(
&entries,
&[
None,
Some("conflict.txt"),
None,
Some("src/lib.rs"),
Some("src/main.rs"),
Some("tests/test.rs"),
None,
Some("another_new.rs"),
Some("new_file.txt"),
Some("src/utils.rs"),
],
);
let second_status_entry = entries[3].clone();
panel.update_in(cx, |panel, window, cx| {
panel.toggle_staged_for_entry(&second_status_entry, window, cx);
});
cx.update(|_window, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.git_panel.get_or_insert_default().sort_by_path = Some(true);
})
});
});
panel.update_in(cx, |panel, window, cx| {
panel.selected_entry = Some(7);
panel.stage_range(&git::StageRange, window, cx);
});
cx.read(|cx| {
project
.read(cx)
.worktrees(cx)
.next()
.unwrap()
.read(cx)
.as_local()
.unwrap()
.scan_complete()
})
.await;
cx.executor().run_until_parked();
let handle = cx.update_window_entity(&panel, |panel, _, _| {
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
});
cx.executor().advance_clock(2 * UPDATE_DEBOUNCE);
handle.await;
let entries = panel.read_with(cx, |panel, _| panel.entries.clone());
#[rustfmt::skip]
pretty_assertions::assert_matches!(
entries.as_slice(),
&[
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Unmerged(..), staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Staged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Unstaged, .. }),
],
);
assert_entry_paths(
&entries,
&[
Some("another_new.rs"),
Some("conflict.txt"),
Some("new_file.txt"),
Some("src/lib.rs"),
Some("src/main.rs"),
Some("src/utils.rs"),
Some("tests/test.rs"),
],
);
let third_status_entry = entries[4].clone();
panel.update_in(cx, |panel, window, cx| {
panel.toggle_staged_for_entry(&third_status_entry, window, cx);
});
panel.update_in(cx, |panel, window, cx| {
panel.selected_entry = Some(9);
panel.stage_range(&git::StageRange, window, cx);
});
cx.read(|cx| {
project
.read(cx)
.worktrees(cx)
.next()
.unwrap()
.read(cx)
.as_local()
.unwrap()
.scan_complete()
})
.await;
cx.executor().run_until_parked();
let handle = cx.update_window_entity(&panel, |panel, _, _| {
std::mem::replace(&mut panel.update_visible_entries_task, Task::ready(()))
});
cx.executor().advance_clock(2 * UPDATE_DEBOUNCE);
handle.await;
let entries = panel.read_with(cx, |panel, _| panel.entries.clone());
#[rustfmt::skip]
pretty_assertions::assert_matches!(
entries.as_slice(),
&[
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Unmerged(..), staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Staged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Staged, .. }),
Status(GitStatusEntry { status: FileStatus::Untracked, staging: StageStatus::Unstaged, .. }),
Status(GitStatusEntry { status: FileStatus::Tracked(..), staging: StageStatus::Unstaged, .. }),
],
);
assert_entry_paths(
&entries,
&[
Some("another_new.rs"),
Some("conflict.txt"),
Some("new_file.txt"),
Some("src/lib.rs"),
Some("src/main.rs"),
Some("src/utils.rs"),
Some("tests/test.rs"),
],
);
}
#[gpui::test]
async fn test_amend_commit_message_handling(cx: &mut TestAppContext) {
init_test(cx);
@@ -5515,19 +5278,4 @@ mod tests {
assert_eq!(current_message, "");
});
}
fn assert_entry_paths(entries: &[GitListEntry], expected_paths: &[Option<&str>]) {
assert_eq!(entries.len(), expected_paths.len());
for (entry, expected_path) in entries.iter().zip(expected_paths) {
assert_eq!(
entry.status_entry().map(|status| status
.repo_path
.0
.as_std_path()
.to_string_lossy()
.to_string()),
expected_path.map(|s| s.to_string())
);
}
}
}

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,

View File

@@ -1,4 +1,4 @@
use editor::{Editor, MultiBufferSnapshot};
use editor::{Editor, EditorSettings, MultiBufferSnapshot};
use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity};
use settings::Settings;
use std::{fmt::Write, num::NonZeroU32, time::Duration};
@@ -8,7 +8,7 @@ use ui::{
Render, Tooltip, Window, div,
};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::{StatusBarSettings, StatusItemView, Workspace, item::ItemHandle};
use workspace::{StatusItemView, Workspace, item::ItemHandle};
#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)]
pub(crate) struct SelectionStats {
@@ -205,7 +205,10 @@ impl CursorPosition {
impl Render for CursorPosition {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !StatusBarSettings::get_global(cx).cursor_position_button {
if !EditorSettings::get_global(cx)
.status_bar
.cursor_position_button
{
return div();
}

View File

@@ -3,8 +3,7 @@ pub mod cursor_position;
use cursor_position::{LineIndicatorFormat, UserCaretPosition};
use editor::{
Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, SelectionEffects, ToOffset, ToPoint,
actions::Tab,
scroll::{Autoscroll, ScrollOffset},
actions::Tab, scroll::Autoscroll,
};
use gpui::{
App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled,
@@ -27,7 +26,7 @@ pub struct GoToLine {
line_editor: Entity<Editor>,
active_editor: Entity<Editor>,
current_text: SharedString,
prev_scroll_position: Option<gpui::Point<ScrollOffset>>,
prev_scroll_position: Option<gpui::Point<f32>>,
_subscriptions: Vec<Subscription>,
}

View File

@@ -5,13 +5,8 @@ edition.workspace = true
authors = ["Nathan Sobo <nathan@zed.dev>"]
description = "Zed's GPU-accelerated UI framework"
repository = "https://github.com/zed-industries/zed"
publish = true
publish.workspace = true
license = "Apache-2.0"
homepage = "https://gpui.rs"
readme = "README.md"
keywords = ["desktop", "gui", "immediate"]
categories = ["gui"]
[lints]
workspace = true
@@ -43,7 +38,7 @@ wayland = [
"blade-macros",
"blade-util",
"bytemuck",
"ashpd/wayland",
"ashpd",
"cosmic-text",
"font-kit",
"calloop-wayland-source",
@@ -144,8 +139,7 @@ core-foundation-sys.workspace = true
core-graphics = "0.24"
core-video.workspace = true
core-text = "21"
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", optional = true }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5474cfad4b719a72ec8ed2cb7327b2b01fd10568", optional = true }
foreign-types = "0.5"
log.workspace = true
media.workspace = true
@@ -176,8 +170,7 @@ blade-macros = { workspace = true, optional = true }
blade-util = { workspace = true, optional = true }
bytemuck = { version = "1", optional = true }
cosmic-text = { version = "0.14.0", optional = true }
# WARNING: If you change this, you must also publish a new version of zed-font-kit to crates.io
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "110523127440aefb11ce0cf280ae7c5071337ec5", package = "zed-font-kit", version = "0.14.1-zed", features = [
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5474cfad4b719a72ec8ed2cb7327b2b01fd10568", features = [
"source-fontconfig-dlopen",
], optional = true }
@@ -217,11 +210,10 @@ xkbcommon = { version = "0.8.0", features = [
"wayland",
"x11",
], optional = true }
# WARNING: If you change this, you must also publish a new version of zed-xim to crates.io
xim = { git = "https://github.com/zed-industries/xim-rs.git", rev = "16f35a2c881b815a2b6cdfd6687988e84f8447d8" , features = [
xim = { git = "https://github.com/zed-industries/xim-rs", rev = "c0a70c1bd2ce197364216e5e818a2cb3adb99a8d" , features = [
"x11rb-xcb",
"x11rb-client",
], package = "zed-xim", version = "0.4.0-zed", optional = true }
], optional = true }
x11-clipboard = { version = "0.9.3", optional = true }
[target.'cfg(target_os = "windows")'.dependencies]

View File

@@ -5,10 +5,10 @@ for Rust, designed to support a wide variety of applications.
## Getting Started
GPUI is still in active development as we work on the Zed code editor, and is still pre-1.0. There will often be breaking changes between versions. You'll also need to use the latest version of stable Rust and be on macOS or Linux. Add the following to your `Cargo.toml`:
GPUI is still in active development as we work on the Zed code editor and isn't yet on crates.io. You'll also need to use the latest version of stable Rust and be on macOS or Linux. Add the following to your `Cargo.toml`:
```toml
gpui = { version = "*" }
gpui = { git = "https://github.com/zed-industries/zed" }
```
Everything in GPUI starts with an `Application`. You can create one with `Application::new()`, and kick off your application by passing a callback to `Application::run()`. Inside this callback, you can create a new window with `App::open_window()`, and register your first root view. See [gpui.rs](https://www.gpui.rs/) for a complete example.

View File

@@ -2,9 +2,9 @@ use std::{path::Path, sync::Arc, time::Duration};
use gpui::{
Animation, AnimationExt, App, Application, Asset, AssetLogger, AssetSource, Bounds, Context,
Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, RenderImage,
Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img, prelude::*,
pulsating_between, px, red, size,
Hsla, ImageAssetLoader, ImageCacheError, ImgResourceLoader, LOADING_DELAY, Length, Pixels,
RenderImage, Resource, SharedString, Window, WindowBounds, WindowOptions, black, div, img,
prelude::*, pulsating_between, px, red, size,
};
struct Assets {}
@@ -105,7 +105,7 @@ impl Render for ImageLoadingExample {
div()
.flex()
.bg(gpui::white())
.size(Length::Definite(px(300.0).into()))
.size(Length::Definite(Pixels(300.0).into()))
.justify_center()
.items_center()
.child({
@@ -199,7 +199,7 @@ fn main() {
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
None,
size(px(300.), px(300.)),
size(px(300.), Pixels(300.)),
cx,
))),
..Default::default()

View File

@@ -132,11 +132,11 @@ impl RenderOnce for Specimen {
let mut line_height = global_style.line_height;
if let Some(style_override) = style_override {
font_size = style_override.font_size.to_pixels(rem_size).into();
font_size = style_override.font_size.to_pixels(rem_size).0;
line_height = match style_override.line_height {
DefiniteLength::Absolute(absolute_len) => match absolute_len {
AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).into(),
AbsoluteLength::Pixels(absolute_len) => absolute_len.into(),
AbsoluteLength::Rems(absolute_len) => absolute_len.to_pixels(rem_size).0,
AbsoluteLength::Pixels(absolute_len) => absolute_len.0,
},
DefiniteLength::Fraction(value) => value,
};

View File

@@ -80,20 +80,6 @@ impl<'a, T: 'static> Context<'a, T> {
})
}
/// Observe changes to ourselves
pub fn observe_self(
&mut self,
mut on_event: impl FnMut(&mut T, &mut Context<T>) + 'static,
) -> Subscription
where
T: 'static,
{
let this = self.entity();
self.app.observe(&this, move |this, cx| {
this.update(cx, |this, cx| on_event(this, cx))
})
}
/// Subscribe to an event type from another entity
pub fn subscribe<T2, Evt>(
&mut self,

View File

@@ -618,25 +618,17 @@ pub trait InteractiveElement: Sized {
self
}
/// Set whether this element is a tab stop.
///
/// When false, the element remains in tab-index order but cannot be reached via keyboard navigation.
/// Useful for container elements: focus the container, then call `window.focus_next()` to focus
/// the first tab stop inside it while having the container element itself be unreachable via the keyboard.
/// Should only be used with `tab_index`.
fn tab_stop(mut self, tab_stop: bool) -> Self {
self.interactivity().tab_stop = tab_stop;
self
/// Designate this element as a tab stop, equivalent to `tab_index(0)`.
/// This should be the primary mechanism for tab navigation within the application.
fn tab_stop(mut self) -> Self {
self.tab_index(0)
}
/// Set index of the tab stop order, and set this node as a tab stop.
/// This will default the element to being a tab stop. See [`Self::tab_stop`] for more information.
/// This should only be used in conjunction with `tab_group`
/// Set index of the tab stop order. This should only be used in conjunction with `tab_group`
/// in order to not interfere with the tab index of other elements.
fn tab_index(mut self, index: isize) -> Self {
self.interactivity().focusable = true;
self.interactivity().tab_index = Some(index);
self.interactivity().tab_stop = true;
self
}
@@ -1513,7 +1505,6 @@ pub struct Interactivity {
pub(crate) hitbox_behavior: HitboxBehavior,
pub(crate) tab_index: Option<isize>,
pub(crate) tab_group: bool,
pub(crate) tab_stop: bool,
#[cfg(any(feature = "inspector", debug_assertions))]
pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
@@ -1578,10 +1569,10 @@ impl Interactivity {
.focus_handle
.get_or_insert_with(|| cx.focus_handle())
.clone()
.tab_stop(self.tab_stop);
.tab_stop(false);
if let Some(index) = self.tab_index {
handle = handle.tab_index(index);
handle = handle.tab_index(index).tab_stop(true);
}
self.tracked_focus_handle = Some(handle);
@@ -3034,20 +3025,7 @@ struct ScrollHandleState {
child_bounds: Vec<Bounds<Pixels>>,
scroll_to_bottom: bool,
overflow: Point<Overflow>,
active_item: Option<ScrollActiveItem>,
}
#[derive(Default, Debug, Clone, Copy)]
struct ScrollActiveItem {
index: usize,
strategy: ScrollStrategy,
}
#[derive(Default, Debug, Clone, Copy)]
enum ScrollStrategy {
#[default]
FirstVisible,
Top,
active_item: Option<usize>,
}
/// A handle to the scrollable aspects of an element.
@@ -3097,25 +3075,6 @@ impl ScrollHandle {
}
}
/// Get the bottom child that's scrolled into view.
pub fn bottom_item(&self) -> usize {
let state = self.0.borrow();
let bottom = state.bounds.bottom() - state.offset.borrow().y;
match state.child_bounds.binary_search_by(|bounds| {
if bottom < bounds.top() {
Ordering::Greater
} else if bottom > bounds.bottom() {
Ordering::Less
} else {
Ordering::Equal
}
}) {
Ok(ix) => ix,
Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
}
}
/// Return the bounds into which this child is painted
pub fn bounds(&self) -> Bounds<Pixels> {
self.0.borrow().bounds
@@ -3129,48 +3088,26 @@ impl ScrollHandle {
/// Update [ScrollHandleState]'s active item for scrolling to in prepaint
pub fn scroll_to_item(&self, ix: usize) {
let mut state = self.0.borrow_mut();
state.active_item = Some(ScrollActiveItem {
index: ix,
strategy: ScrollStrategy::default(),
});
state.active_item = Some(ix);
}
/// Update [ScrollHandleState]'s active item for scrolling to in prepaint
/// This scrolls the minimal amount to ensure that the child is the first visible element
pub fn scroll_to_top_of_item(&self, ix: usize) {
let mut state = self.0.borrow_mut();
state.active_item = Some(ScrollActiveItem {
index: ix,
strategy: ScrollStrategy::Top,
});
}
/// Scrolls the minimal amount to either ensure that the child is
/// fully visible or the top element of the view depends on the
/// scroll strategy
/// Scrolls the minimal amount to ensure that the child is
/// fully visible
fn scroll_to_active_item(&self) {
let mut state = self.0.borrow_mut();
let Some(active_item) = state.active_item else {
let Some(active_item_index) = state.active_item else {
return;
};
let active_item = match state.child_bounds.get(active_item.index) {
let active_item = match state.child_bounds.get(active_item_index) {
Some(bounds) => {
let mut scroll_offset = state.offset.borrow_mut();
match active_item.strategy {
ScrollStrategy::FirstVisible => {
if state.overflow.y == Overflow::Scroll {
if bounds.top() + scroll_offset.y < state.bounds.top() {
scroll_offset.y = state.bounds.top() - bounds.top();
} else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
scroll_offset.y = state.bounds.bottom() - bounds.bottom();
}
}
}
ScrollStrategy::Top => {
if state.overflow.y == Overflow::Scroll {
if bounds.top() + scroll_offset.y < state.bounds.top() {
scroll_offset.y = state.bounds.top() - bounds.top();
} else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
scroll_offset.y = state.bounds.bottom() - bounds.bottom();
}
}
@@ -3183,7 +3120,7 @@ impl ScrollHandle {
}
None
}
None => Some(active_item),
None => Some(active_item_index),
};
state.active_item = active_item;
}
@@ -3217,21 +3154,6 @@ impl ScrollHandle {
}
}
/// Get the logical scroll bottom, based on a child index and a pixel offset.
pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
let ix = self.bottom_item();
let state = self.0.borrow();
if let Some(child_bounds) = state.child_bounds.get(ix) {
(
ix,
child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
)
} else {
(ix, px(0.))
}
}
/// Get the count of children for scrollable item.
pub fn children_count(&self) -> usize {
self.0.borrow().child_bounds.len()

View File

@@ -352,7 +352,7 @@ impl Element for Img {
Length::Definite(DefiniteLength::Absolute(
AbsoluteLength::Pixels(width),
)) => Length::Definite(
px(image_size.height * f32::from(width) / image_size.width)
px(image_size.height.0 * width.0 / image_size.width.0)
.into(),
),
_ => Length::Definite(image_size.height.into()),

View File

@@ -180,7 +180,8 @@ impl StyledText {
"Can't use `with_default_highlights` and `with_highlights`"
);
let runs = Self::compute_runs(&self.text, default_style, highlights);
self.with_runs(runs)
self.runs = Some(runs);
self
}
/// Set the styling attributes for the given text, as well as
@@ -193,15 +194,7 @@ impl StyledText {
self.runs.is_none(),
"Can't use `with_highlights` and `with_default_highlights`"
);
self.delayed_highlights = Some(
highlights
.into_iter()
.inspect(|(run, _)| {
debug_assert!(self.text.is_char_boundary(run.start));
debug_assert!(self.text.is_char_boundary(run.end));
})
.collect::<Vec<_>>(),
);
self.delayed_highlights = Some(highlights.into_iter().collect::<Vec<_>>());
self
}
@@ -214,10 +207,8 @@ impl StyledText {
let mut ix = 0;
for (range, highlight) in highlights {
if ix < range.start {
debug_assert!(text.is_char_boundary(range.start));
runs.push(default_style.clone().to_run(range.start - ix));
}
debug_assert!(text.is_char_boundary(range.end));
runs.push(
default_style
.clone()
@@ -234,11 +225,6 @@ impl StyledText {
/// Set the text runs for this piece of text.
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
let mut text = &**self.text;
for run in &runs {
text = text.get(run.len..).expect("invalid text run");
}
assert!(text.is_empty(), "invalid text run");
self.runs = Some(runs);
self
}

View File

@@ -200,9 +200,9 @@ impl Point<Pixels> {
///
/// ```
/// # use gpui::{Point, Pixels, ScaledPixels};
/// let p = Point { x: Pixels::from(10.0), y: Pixels::from(20.0) };
/// let p = Point { x: Pixels(10.0), y: Pixels(20.0) };
/// let scaled_p = p.scale(1.5);
/// assert_eq!(scaled_p, Point { x: ScaledPixels::from(15.0), y: ScaledPixels::from(30.0) });
/// assert_eq!(scaled_p, Point { x: ScaledPixels(15.0), y: ScaledPixels(30.0) });
/// ```
pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
Point {
@@ -217,7 +217,7 @@ impl Point<Pixels> {
///
/// ```
/// # use gpui::{Pixels, Point};
/// let p = Point { x: Pixels::from(3.0), y: Pixels::from(4.0) };
/// let p = Point { x: Pixels(3.0), y: Pixels(4.0) };
/// assert_eq!(p.magnitude(), 5.0);
/// ```
pub fn magnitude(&self) -> f64 {
@@ -493,9 +493,9 @@ impl Size<Pixels> {
///
/// ```
/// # use gpui::{Size, Pixels, ScaledPixels};
/// let size = Size { width: Pixels::from(100.0), height: Pixels::from(50.0) };
/// let size = Size { width: Pixels(100.0), height: Pixels(50.0) };
/// let scaled_size = size.scale(2.0);
/// assert_eq!(scaled_size, Size { width: ScaledPixels::from(200.0), height: ScaledPixels::from(100.0) });
/// assert_eq!(scaled_size, Size { width: ScaledPixels(200.0), height: ScaledPixels(100.0) });
/// ```
pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
Size {
@@ -1628,19 +1628,19 @@ impl Bounds<Pixels> {
/// ```
/// # use gpui::{Bounds, Point, Size, Pixels, ScaledPixels, DevicePixels};
/// let bounds = Bounds {
/// origin: Point { x: Pixels::from(10.0), y: Pixels::from(20.0) },
/// size: Size { width: Pixels::from(30.0), height: Pixels::from(40.0) },
/// origin: Point { x: Pixels(10.0), y: Pixels(20.0) },
/// size: Size { width: Pixels(30.0), height: Pixels(40.0) },
/// };
/// let display_scale_factor = 2.0;
/// let scaled_bounds = bounds.scale(display_scale_factor);
/// assert_eq!(scaled_bounds, Bounds {
/// origin: Point {
/// x: ScaledPixels::from(20.0),
/// y: ScaledPixels::from(40.0),
/// x: ScaledPixels(20.0),
/// y: ScaledPixels(40.0),
/// },
/// size: Size {
/// width: ScaledPixels::from(60.0),
/// height: ScaledPixels::from(80.0)
/// width: ScaledPixels(60.0),
/// height: ScaledPixels(80.0)
/// },
/// });
/// ```
@@ -1888,10 +1888,10 @@ impl Edges<Length> {
/// ```
/// # use gpui::{DefiniteLength, Edges, Length, Pixels};
/// let no_edges = Edges::<Length>::zero();
/// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels::ZERO)));
/// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels::ZERO)));
/// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels::ZERO)));
/// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels::ZERO)));
/// assert_eq!(no_edges.top, Length::Definite(DefiniteLength::from(Pixels(0.))));
/// assert_eq!(no_edges.right, Length::Definite(DefiniteLength::from(Pixels(0.))));
/// assert_eq!(no_edges.bottom, Length::Definite(DefiniteLength::from(Pixels(0.))));
/// assert_eq!(no_edges.left, Length::Definite(DefiniteLength::from(Pixels(0.))));
/// ```
pub fn zero() -> Self {
Self {
@@ -1993,10 +1993,10 @@ impl Edges<AbsoluteLength> {
/// ```
/// # use gpui::{AbsoluteLength, Edges, Pixels};
/// let no_edges = Edges::<AbsoluteLength>::zero();
/// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels::ZERO));
/// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels::ZERO));
/// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels::ZERO));
/// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels::ZERO));
/// assert_eq!(no_edges.top, AbsoluteLength::Pixels(Pixels(0.0)));
/// assert_eq!(no_edges.right, AbsoluteLength::Pixels(Pixels(0.0)));
/// assert_eq!(no_edges.bottom, AbsoluteLength::Pixels(Pixels(0.0)));
/// assert_eq!(no_edges.left, AbsoluteLength::Pixels(Pixels(0.0)));
/// ```
pub fn zero() -> Self {
Self {
@@ -2066,16 +2066,16 @@ impl Edges<Pixels> {
/// ```
/// # use gpui::{Edges, Pixels, ScaledPixels};
/// let edges = Edges {
/// top: Pixels::from(10.0),
/// right: Pixels::from(20.0),
/// bottom: Pixels::from(30.0),
/// left: Pixels::from(40.0),
/// top: Pixels(10.0),
/// right: Pixels(20.0),
/// bottom: Pixels(30.0),
/// left: Pixels(40.0),
/// };
/// let scaled_edges = edges.scale(2.0);
/// assert_eq!(scaled_edges.top, ScaledPixels::from(20.0));
/// assert_eq!(scaled_edges.right, ScaledPixels::from(40.0));
/// assert_eq!(scaled_edges.bottom, ScaledPixels::from(60.0));
/// assert_eq!(scaled_edges.left, ScaledPixels::from(80.0));
/// assert_eq!(scaled_edges.top, ScaledPixels(20.0));
/// assert_eq!(scaled_edges.right, ScaledPixels(40.0));
/// assert_eq!(scaled_edges.bottom, ScaledPixels(60.0));
/// assert_eq!(scaled_edges.left, ScaledPixels(80.0));
/// ```
pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
Edges {
@@ -2273,18 +2273,18 @@ impl Corners<AbsoluteLength> {
/// ```
/// # use gpui::{Corners, AbsoluteLength, Pixels, Rems, Size};
/// let corners = Corners {
/// top_left: AbsoluteLength::Pixels(Pixels::from(15.0)),
/// top_left: AbsoluteLength::Pixels(Pixels(15.0)),
/// top_right: AbsoluteLength::Rems(Rems(1.0)),
/// bottom_right: AbsoluteLength::Pixels(Pixels::from(30.0)),
/// bottom_right: AbsoluteLength::Pixels(Pixels(30.0)),
/// bottom_left: AbsoluteLength::Rems(Rems(2.0)),
/// };
/// let rem_size = Pixels::from(16.0);
/// let rem_size = Pixels(16.0);
/// let corners_in_pixels = corners.to_pixels(rem_size);
///
/// assert_eq!(corners_in_pixels.top_left, Pixels::from(15.0));
/// assert_eq!(corners_in_pixels.top_right, Pixels::from(16.0)); // 1 rem converted to pixels
/// assert_eq!(corners_in_pixels.bottom_right, Pixels::from(30.0));
/// assert_eq!(corners_in_pixels.bottom_left, Pixels::from(32.0)); // 2 rems converted to pixels
/// assert_eq!(corners_in_pixels.top_left, Pixels(15.0));
/// assert_eq!(corners_in_pixels.top_right, Pixels(16.0)); // 1 rem converted to pixels
/// assert_eq!(corners_in_pixels.bottom_right, Pixels(30.0));
/// assert_eq!(corners_in_pixels.bottom_left, Pixels(32.0)); // 2 rems converted to pixels
/// ```
pub fn to_pixels(self, rem_size: Pixels) -> Corners<Pixels> {
Corners {
@@ -2314,16 +2314,16 @@ impl Corners<Pixels> {
/// ```
/// # use gpui::{Corners, Pixels, ScaledPixels};
/// let corners = Corners {
/// top_left: Pixels::from(10.0),
/// top_right: Pixels::from(20.0),
/// bottom_right: Pixels::from(30.0),
/// bottom_left: Pixels::from(40.0),
/// top_left: Pixels(10.0),
/// top_right: Pixels(20.0),
/// bottom_right: Pixels(30.0),
/// bottom_left: Pixels(40.0),
/// };
/// let scaled_corners = corners.scale(2.0);
/// assert_eq!(scaled_corners.top_left, ScaledPixels::from(20.0));
/// assert_eq!(scaled_corners.top_right, ScaledPixels::from(40.0));
/// assert_eq!(scaled_corners.bottom_right, ScaledPixels::from(60.0));
/// assert_eq!(scaled_corners.bottom_left, ScaledPixels::from(80.0));
/// assert_eq!(scaled_corners.top_left, ScaledPixels(20.0));
/// assert_eq!(scaled_corners.top_right, ScaledPixels(40.0));
/// assert_eq!(scaled_corners.bottom_right, ScaledPixels(60.0));
/// assert_eq!(scaled_corners.bottom_left, ScaledPixels(80.0));
/// ```
#[must_use]
pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
@@ -2391,12 +2391,12 @@ impl<T: Clone + Debug + Default + PartialEq> Corners<T> {
/// ```
/// # use gpui::{Corners, Pixels, Rems};
/// let corners = Corners {
/// top_left: Pixels::from(10.0),
/// top_right: Pixels::from(20.0),
/// bottom_right: Pixels::from(30.0),
/// bottom_left: Pixels::from(40.0),
/// top_left: Pixels(10.0),
/// top_right: Pixels(20.0),
/// bottom_right: Pixels(30.0),
/// bottom_left: Pixels(40.0),
/// };
/// let corners_in_rems = corners.map(|&px| Rems(f32::from(px) / 16.0));
/// let corners_in_rems = corners.map(|&px| Rems(px.0 / 16.0));
/// assert_eq!(corners_in_rems, Corners {
/// top_left: Rems(0.625),
/// top_right: Rems(1.25),
@@ -2547,11 +2547,11 @@ impl From<Percentage> for Radians {
/// use gpui::{Pixels, ScaledPixels};
///
/// // Define a length of 10 pixels
/// let length = Pixels::from(10.0);
/// let length = Pixels(10.0);
///
/// // Define a length and scale it by a factor of 2
/// let scaled_length = length.scale(2.0);
/// assert_eq!(scaled_length, ScaledPixels::from(20.0));
/// assert_eq!(scaled_length, ScaledPixels(20.0));
/// ```
#[derive(
Clone,
@@ -2570,7 +2570,7 @@ impl From<Percentage> for Radians {
JsonSchema,
)]
#[repr(transparent)]
pub struct Pixels(pub(crate) f32);
pub struct Pixels(pub f32);
impl Div for Pixels {
type Output = f32;
@@ -2809,12 +2809,6 @@ impl From<Pixels> for u32 {
}
}
impl From<&Pixels> for u32 {
fn from(pixels: &Pixels) -> Self {
pixels.0 as u32
}
}
impl From<u32> for Pixels {
fn from(pixels: u32) -> Self {
Pixels(pixels as f32)
@@ -2951,7 +2945,7 @@ impl From<usize> for DevicePixels {
/// display resolutions.
#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, DivAssign, PartialEq)]
#[repr(transparent)]
pub struct ScaledPixels(pub(crate) f32);
pub struct ScaledPixels(pub f32);
impl ScaledPixels {
/// Floors the `ScaledPixels` value to the nearest whole number.
@@ -3017,12 +3011,6 @@ impl From<ScaledPixels> for u32 {
}
}
impl From<f32> for ScaledPixels {
fn from(pixels: f32) -> Self {
Self(pixels)
}
}
impl Div for ScaledPixels {
type Output = f32;
@@ -3192,12 +3180,12 @@ impl AbsoluteLength {
///
/// ```
/// # use gpui::{AbsoluteLength, Pixels, Rems};
/// let length_in_pixels = AbsoluteLength::Pixels(Pixels::from(42.0));
/// let length_in_pixels = AbsoluteLength::Pixels(Pixels(42.0));
/// let length_in_rems = AbsoluteLength::Rems(Rems(2.0));
/// let rem_size = Pixels::from(16.0);
/// let rem_size = Pixels(16.0);
///
/// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels::from(42.0));
/// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels::from(32.0));
/// assert_eq!(length_in_pixels.to_pixels(rem_size), Pixels(42.0));
/// assert_eq!(length_in_rems.to_pixels(rem_size), Pixels(32.0));
/// ```
pub fn to_pixels(self, rem_size: Pixels) -> Pixels {
match self {
@@ -3342,9 +3330,9 @@ impl DefiniteLength {
/// let base_size = AbsoluteLength::Pixels(px(100.0));
/// let rem_size = px(16.0);
///
/// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels::from(42.0));
/// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels::from(32.0));
/// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels::from(50.0));
/// assert_eq!(length_in_pixels.to_pixels(base_size, rem_size), Pixels(42.0));
/// assert_eq!(length_in_rems.to_pixels(base_size, rem_size), Pixels(32.0));
/// assert_eq!(length_as_fraction.to_pixels(base_size, rem_size), Pixels(50.0));
/// ```
pub fn to_pixels(self, base_size: AbsoluteLength, rem_size: Pixels) -> Pixels {
match self {

View File

@@ -259,14 +259,6 @@ impl ClickEvent {
ClickEvent::Mouse(event) => event.up.click_count,
}
}
/// Returns whether the click event is generated by a keyboard event
pub fn is_keyboard(&self) -> bool {
match self {
ClickEvent::Mouse(_) => false,
ClickEvent::Keyboard(_) => true,
}
}
}
/// An enum representing the keyboard button that was pressed for a click event.

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