Compare commits
1 Commits
static-rel
...
mac-focus-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acac967c63 |
@@ -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" },
|
||||
]
|
||||
|
||||
2
.github/actions/run_tests/action.yml
vendored
2
.github/actions/run_tests/action.yml
vendored
@@ -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
|
||||
|
||||
2
.github/actions/run_tests_windows/action.yml
vendored
2
.github/actions/run_tests_windows/action.yml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -826,9 +826,8 @@ jobs:
|
||||
timeout-minutes: 120
|
||||
name: Create a Windows installer
|
||||
runs-on: [self-32vcpu-windows-2022]
|
||||
if: |
|
||||
( startsWith(github.ref, 'refs/tags/v')
|
||||
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') )
|
||||
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
|
||||
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
|
||||
needs: [windows_tests]
|
||||
env:
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
|
||||
@@ -871,7 +870,8 @@ jobs:
|
||||
|
||||
- name: Upload Artifacts to release
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) }}
|
||||
# Re-enable when we are ready to publish windows preview releases
|
||||
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
|
||||
|
||||
1119
Cargo.lock
generated
1119
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
39
Cargo.toml
39
Cargo.toml
@@ -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 }
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||
"ctrl-,": "zed::OpenSettingsEditor",
|
||||
"ctrl-alt-,": "zed::OpenSettings",
|
||||
"ctrl-,": "zed::OpenSettings",
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
@@ -370,15 +369,7 @@
|
||||
"bindings": {
|
||||
"new": "rules_library::NewRule",
|
||||
"ctrl-n": "rules_library::NewRule",
|
||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
|
||||
"ctrl-w": "workspace::CloseWindow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow"
|
||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
"cmd-+": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||
"cmd--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||
"cmd-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||
"cmd-,": "zed::OpenSettingsEditor",
|
||||
"cmd-alt-,": "zed::OpenSettings",
|
||||
"cmd-,": "zed::OpenSettings",
|
||||
"cmd-q": "zed::Quit",
|
||||
"cmd-h": "zed::Hide",
|
||||
"alt-cmd-h": "zed::HideOthers",
|
||||
@@ -431,13 +430,6 @@
|
||||
"cmd-w": "workspace::CloseWindow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-w": "workspace::CloseWindow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar",
|
||||
"use_key_equivalents": true,
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||
"ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }],
|
||||
"ctrl-,": "zed::OpenSettingsEditor",
|
||||
"ctrl-alt-,": "zed::OpenSettings",
|
||||
"ctrl-,": "zed::OpenSettings",
|
||||
"ctrl-q": "zed::Quit",
|
||||
"f4": "debugger::Start",
|
||||
"shift-f5": "debugger::Stop",
|
||||
@@ -379,15 +378,7 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-n": "rules_library::NewRule",
|
||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule",
|
||||
"ctrl-w": "workspace::CloseWindow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "SettingsWindow",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"ctrl-w": "workspace::CloseWindow"
|
||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3276,6 +3276,7 @@ mod tests {
|
||||
use settings::{LanguageModelParameters, Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use theme::ThemeSettings;
|
||||
use util::path;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -5336,7 +5337,7 @@ fn main() {{
|
||||
thread_store::init(fs.clone(), cx);
|
||||
workspace::init_settings(cx);
|
||||
language_model::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
ToolRegistry::default_global(cx);
|
||||
assistant_tool::init(cx);
|
||||
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -25,23 +25,21 @@ use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::sync::Arc;
|
||||
use util::ResultExt;
|
||||
use util::rel_path::RelPath;
|
||||
|
||||
static RULES_FILE_NAMES: LazyLock<[&RelPath; 9]> = LazyLock::new(|| {
|
||||
[
|
||||
RelPath::unix(".rules").unwrap(),
|
||||
RelPath::unix(".cursorrules").unwrap(),
|
||||
RelPath::unix(".windsurfrules").unwrap(),
|
||||
RelPath::unix(".clinerules").unwrap(),
|
||||
RelPath::unix(".github/copilot-instructions.md").unwrap(),
|
||||
RelPath::unix("CLAUDE.md").unwrap(),
|
||||
RelPath::unix("AGENT.md").unwrap(),
|
||||
RelPath::unix("AGENTS.md").unwrap(),
|
||||
RelPath::unix("GEMINI.md").unwrap(),
|
||||
]
|
||||
});
|
||||
const RULES_FILE_NAMES: [&str; 9] = [
|
||||
".rules",
|
||||
".cursorrules",
|
||||
".windsurfrules",
|
||||
".clinerules",
|
||||
".github/copilot-instructions.md",
|
||||
"CLAUDE.md",
|
||||
"AGENT.md",
|
||||
"AGENTS.md",
|
||||
"GEMINI.md",
|
||||
];
|
||||
|
||||
pub struct RulesLoadingError {
|
||||
pub message: SharedString,
|
||||
@@ -477,7 +475,7 @@ impl NativeAgent {
|
||||
.into_iter()
|
||||
.filter_map(|name| {
|
||||
worktree
|
||||
.entry_for_path(name)
|
||||
.entry_for_path(RelPath::unix(name).unwrap())
|
||||
.filter(|entry| entry.is_file())
|
||||
.map(|entry| entry.path.clone())
|
||||
})
|
||||
@@ -558,10 +556,11 @@ impl NativeAgent {
|
||||
self.project_context_needs_refresh.send(()).ok();
|
||||
}
|
||||
project::Event::WorktreeUpdatedEntries(_, items) => {
|
||||
if items
|
||||
.iter()
|
||||
.any(|(path, _, _)| RULES_FILE_NAMES.iter().any(|name| path.as_ref() == *name))
|
||||
{
|
||||
if items.iter().any(|(path, _, _)| {
|
||||
RULES_FILE_NAMES
|
||||
.iter()
|
||||
.any(|name| path.as_ref() == RelPath::unix(name).unwrap())
|
||||
}) {
|
||||
self.project_context_needs_refresh.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -151,7 +151,7 @@ impl Default for AgentProfileId {
|
||||
}
|
||||
|
||||
impl Settings for AgentSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let agent = content.agent.clone().unwrap();
|
||||
Self {
|
||||
enabled: agent.enabled.unwrap(),
|
||||
|
||||
@@ -203,7 +203,7 @@ impl EntryViewState {
|
||||
self.entries.drain(range);
|
||||
}
|
||||
|
||||
pub fn agent_ui_font_size_changed(&mut self, cx: &mut App) {
|
||||
pub fn agent_font_size_changed(&mut self, cx: &mut App) {
|
||||
for entry in self.entries.iter() {
|
||||
match entry {
|
||||
Entry::UserMessage { .. } | Entry::AssistantMessage { .. } => {}
|
||||
@@ -387,7 +387,7 @@ fn diff_editor_text_style_refinement(cx: &mut App) -> TextStyleRefinement {
|
||||
font_size: Some(
|
||||
TextSize::Small
|
||||
.rems(cx)
|
||||
.to_pixels(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
|
||||
.to_pixels(ThemeSettings::get_global(cx).agent_font_size(cx))
|
||||
.into(),
|
||||
),
|
||||
..Default::default()
|
||||
@@ -414,6 +414,7 @@ mod tests {
|
||||
use project::Project;
|
||||
use serde_json::json;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use theme::ThemeSettings;
|
||||
use util::path;
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -543,7 +544,7 @@ mod tests {
|
||||
Project::init_settings(cx);
|
||||
AgentSettings::register(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
EditorSettings::register(cx);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use gpui::{
|
||||
CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length,
|
||||
ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task,
|
||||
TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div,
|
||||
ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between,
|
||||
ease_in_out, linear_color_stop, linear_gradient, list, point, prelude::*, pulsating_between,
|
||||
};
|
||||
use language::Buffer;
|
||||
|
||||
@@ -289,9 +289,8 @@ pub struct AcpThreadView {
|
||||
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
|
||||
is_loading_contents: bool,
|
||||
new_server_version_available: Option<SharedString>,
|
||||
resume_thread_metadata: Option<DbThreadMetadata>,
|
||||
_cancel_task: Option<Task<()>>,
|
||||
_subscriptions: [Subscription; 5],
|
||||
_subscriptions: [Subscription; 4],
|
||||
}
|
||||
|
||||
enum ThreadState {
|
||||
@@ -381,17 +380,11 @@ impl AcpThreadView {
|
||||
)
|
||||
});
|
||||
|
||||
let agent_server_store = project.read(cx).agent_server_store().clone();
|
||||
let subscriptions = [
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed),
|
||||
cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed),
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::agent_font_size_changed),
|
||||
cx.observe_global_in::<AgentFontSize>(window, Self::agent_font_size_changed),
|
||||
cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event),
|
||||
cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event),
|
||||
cx.subscribe_in(
|
||||
&agent_server_store,
|
||||
window,
|
||||
Self::handle_agent_servers_updated,
|
||||
),
|
||||
];
|
||||
|
||||
Self {
|
||||
@@ -399,14 +392,7 @@ impl AcpThreadView {
|
||||
workspace: workspace.clone(),
|
||||
project: project.clone(),
|
||||
entry_view_state,
|
||||
thread_state: Self::initial_state(
|
||||
agent.clone(),
|
||||
resume_thread.clone(),
|
||||
workspace.clone(),
|
||||
project.clone(),
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
thread_state: Self::initial_state(agent, resume_thread, workspace, project, window, cx),
|
||||
login: None,
|
||||
message_editor,
|
||||
model_selector: None,
|
||||
@@ -435,14 +421,13 @@ impl AcpThreadView {
|
||||
_cancel_task: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
new_server_version_available: None,
|
||||
resume_thread_metadata: resume_thread,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.thread_state = Self::initial_state(
|
||||
self.agent.clone(),
|
||||
self.resume_thread_metadata.clone(),
|
||||
None,
|
||||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
window,
|
||||
@@ -592,6 +577,31 @@ impl AcpThreadView {
|
||||
|
||||
AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
|
||||
|
||||
// Proactively surface Authentication Required if the agent advertises auth methods.
|
||||
if let Some(acp_conn) = thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
.clone()
|
||||
.downcast::<agent_servers::AcpConnection>()
|
||||
{
|
||||
let methods = acp_conn.auth_methods();
|
||||
if !methods.is_empty() {
|
||||
// Immediately transition to auth-required UI, but defer to avoid re-entrant update.
|
||||
let err = AuthRequired {
|
||||
description: None,
|
||||
provider_id: None,
|
||||
};
|
||||
let this_weak = cx.weak_entity();
|
||||
let agent = agent.clone();
|
||||
let connection = thread.read(cx).connection().clone();
|
||||
window.defer(cx, move |window, cx| {
|
||||
Self::handle_auth_required(
|
||||
this_weak, err, agent, connection, window, cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.model_selector = thread
|
||||
.read(cx)
|
||||
.connection()
|
||||
@@ -790,25 +800,6 @@ impl AcpThreadView {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn handle_agent_servers_updated(
|
||||
&mut self,
|
||||
_agent_server_store: &Entity<project::AgentServerStore>,
|
||||
_event: &project::AgentServersUpdated,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// If we're in a LoadError state OR have a thread_error set (which can happen
|
||||
// when agent.connect() fails during loading), retry loading the thread.
|
||||
// This handles the case where a thread is restored before authentication completes.
|
||||
let should_retry =
|
||||
matches!(&self.thread_state, ThreadState::LoadError(_)) || self.thread_error.is_some();
|
||||
|
||||
if should_retry {
|
||||
self.thread_error = None;
|
||||
self.reset(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspace(&self) -> &WeakEntity<Workspace> {
|
||||
&self.workspace
|
||||
}
|
||||
@@ -2750,7 +2741,7 @@ impl AcpThreadView {
|
||||
|
||||
let working_dir = working_dir
|
||||
.as_ref()
|
||||
.map(|path| path.display().to_string())
|
||||
.map(|path| format!("{}", path.display()))
|
||||
.unwrap_or_else(|| "current directory".to_string());
|
||||
|
||||
let is_expanded = self.expanded_tool_calls.contains(&tool_call.id);
|
||||
@@ -3388,12 +3379,6 @@ impl AcpThreadView {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn activity_bar_bg(&self, cx: &Context<Self>) -> Hsla {
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
let active_color = cx.theme().colors().element_selected;
|
||||
editor_bg_color.blend(active_color.opacity(0.3))
|
||||
}
|
||||
|
||||
fn render_activity_bar(
|
||||
&self,
|
||||
thread_entity: &Entity<AcpThread>,
|
||||
@@ -3409,6 +3394,10 @@ impl AcpThreadView {
|
||||
return None;
|
||||
}
|
||||
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
let active_color = cx.theme().colors().element_selected;
|
||||
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
|
||||
|
||||
// Temporarily always enable ACP edit controls. This is temporary, to lessen the
|
||||
// impact of a nasty bug that causes them to sometimes be disabled when they shouldn't
|
||||
// be, which blocks you from being able to accept or reject edits. This switches the
|
||||
@@ -3419,7 +3408,7 @@ impl AcpThreadView {
|
||||
v_flex()
|
||||
.mt_1()
|
||||
.mx_2()
|
||||
.bg(self.activity_bar_bg(cx))
|
||||
.bg(bg_edit_files_disclosure)
|
||||
.border_1()
|
||||
.border_b_0()
|
||||
.border_color(cx.theme().colors().border)
|
||||
@@ -3460,33 +3449,27 @@ impl AcpThreadView {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_plan_summary(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
window: &mut Window,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
fn render_plan_summary(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
|
||||
let stats = plan.stats();
|
||||
|
||||
let title = if let Some(entry) = stats.in_progress_entry
|
||||
&& !self.plan_expanded
|
||||
{
|
||||
h_flex()
|
||||
.cursor_default()
|
||||
.relative()
|
||||
.w_full()
|
||||
.cursor_default()
|
||||
.gap_1()
|
||||
.truncate()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.justify_between()
|
||||
.child(
|
||||
Label::new("Current:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.line_clamp(1)
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new("Current:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(MarkdownElement::new(
|
||||
entry.content.clone(),
|
||||
plan_label_markdown_style(&entry.status, window, cx),
|
||||
@@ -3494,23 +3477,10 @@ impl AcpThreadView {
|
||||
)
|
||||
.when(stats.pending > 0, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right_0()
|
||||
.h_full()
|
||||
.child(div().min_w_8().h_full().bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(self.activity_bar_bg(cx), 1.),
|
||||
linear_color_stop(self.activity_bar_bg(cx).opacity(0.2), 0.),
|
||||
)))
|
||||
.child(
|
||||
div().pr_0p5().bg(self.activity_bar_bg(cx)).child(
|
||||
Label::new(format!("{} left", stats.pending))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
Label::new(format!("{} left", stats.pending))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mr_1(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
@@ -3540,19 +3510,23 @@ impl AcpThreadView {
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("plan_summary")
|
||||
.p_1()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.when(self.plan_expanded, |this| {
|
||||
this.border_b_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
|
||||
.child(title)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.plan_expanded = !this.plan_expanded;
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.id("plan_summary")
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
|
||||
.child(title)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.plan_expanded = !this.plan_expanded;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
|
||||
@@ -3801,7 +3775,7 @@ impl AcpThreadView {
|
||||
.id(("file-name", index))
|
||||
.pr_8()
|
||||
.gap_1p5()
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
.overflow_x_scroll()
|
||||
.child(file_icon)
|
||||
.child(h_flex().gap_0p5().children(file_name).children(file_path))
|
||||
@@ -4953,9 +4927,9 @@ impl AcpThreadView {
|
||||
)
|
||||
}
|
||||
|
||||
fn agent_ui_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn agent_font_size_changed(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.entry_view_state.update(cx, |entry_view_state, cx| {
|
||||
entry_view_state.agent_ui_font_size_changed(cx);
|
||||
entry_view_state.agent_font_size_changed(cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5585,23 +5559,23 @@ fn default_markdown_style(
|
||||
}),
|
||||
code_block: StyleRefinement {
|
||||
padding: EdgesRefinement {
|
||||
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
|
||||
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
|
||||
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
|
||||
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(8.)))),
|
||||
top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
|
||||
},
|
||||
margin: EdgesRefinement {
|
||||
top: Some(Length::Definite(px(8.).into())),
|
||||
left: Some(Length::Definite(px(0.).into())),
|
||||
right: Some(Length::Definite(px(0.).into())),
|
||||
bottom: Some(Length::Definite(px(12.).into())),
|
||||
top: Some(Length::Definite(Pixels(8.).into())),
|
||||
left: Some(Length::Definite(Pixels(0.).into())),
|
||||
right: Some(Length::Definite(Pixels(0.).into())),
|
||||
bottom: Some(Length::Definite(Pixels(12.).into())),
|
||||
},
|
||||
border_style: Some(BorderStyle::Solid),
|
||||
border_widths: EdgesRefinement {
|
||||
top: Some(AbsoluteLength::Pixels(px(1.))),
|
||||
left: Some(AbsoluteLength::Pixels(px(1.))),
|
||||
right: Some(AbsoluteLength::Pixels(px(1.))),
|
||||
bottom: Some(AbsoluteLength::Pixels(px(1.))),
|
||||
top: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
left: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
right: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
bottom: Some(AbsoluteLength::Pixels(Pixels(1.))),
|
||||
},
|
||||
border_color: Some(colors.border_variant),
|
||||
background: Some(colors.editor_background.into()),
|
||||
@@ -6086,7 +6060,7 @@ pub(crate) mod tests {
|
||||
Project::init_settings(cx);
|
||||
AgentSettings::register(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
EditorSettings::register(cx);
|
||||
prompt_store::init(cx)
|
||||
|
||||
@@ -562,6 +562,10 @@ impl Item for AgentDiffPane {
|
||||
self.editor.for_each_project_item(cx, f)
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
nav_history: ItemNavHistory,
|
||||
@@ -846,7 +850,7 @@ fn render_diff_hunk_controls(
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let position =
|
||||
hunk_range.end.to_point(&snapshot.buffer_snapshot());
|
||||
hunk_range.end.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
position,
|
||||
@@ -882,7 +886,7 @@ fn render_diff_hunk_controls(
|
||||
editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let point =
|
||||
hunk_range.start.to_point(&snapshot.buffer_snapshot());
|
||||
hunk_range.start.to_point(&snapshot.buffer_snapshot);
|
||||
editor.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
point,
|
||||
@@ -1814,6 +1818,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::{path::Path, rc::Rc};
|
||||
use theme::ThemeSettings;
|
||||
use util::path;
|
||||
|
||||
#[gpui::test]
|
||||
@@ -1826,7 +1831,7 @@ mod tests {
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
EditorSettings::register(cx);
|
||||
language_model::init_settings(cx);
|
||||
});
|
||||
@@ -1978,7 +1983,7 @@ mod tests {
|
||||
AgentSettings::register(cx);
|
||||
prompt_store::init(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
EditorSettings::register(cx);
|
||||
language_model::init_settings(cx);
|
||||
workspace::register_project_item::<Editor>(cx);
|
||||
|
||||
@@ -48,8 +48,8 @@ use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
|
||||
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, ReadGlobal as _, Subscription, Task,
|
||||
UpdateGlobal, WeakEntity, prelude::*,
|
||||
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
|
||||
WeakEntity, prelude::*,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{ConfigurationError, LanguageModelRegistry};
|
||||
@@ -519,14 +519,6 @@ impl AgentPanel {
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
if SettingsStore::global(cx)
|
||||
.get::<DisableAiSettings>(None)
|
||||
.disable_ai
|
||||
{
|
||||
return panel;
|
||||
}
|
||||
|
||||
panel.as_mut(cx).loading = true;
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
@@ -1116,15 +1108,15 @@ impl AgentPanel {
|
||||
WhichFontSize::AgentFont => {
|
||||
if persist {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, cx| {
|
||||
let agent_ui_font_size =
|
||||
ThemeSettings::get_global(cx).agent_ui_font_size(cx) + delta;
|
||||
let agent_font_size =
|
||||
ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
|
||||
let _ = settings
|
||||
.theme
|
||||
.agent_ui_font_size
|
||||
.insert(theme::clamp_font_size(agent_ui_font_size).into());
|
||||
.agent_font_size
|
||||
.insert(theme::clamp_font_size(agent_font_size).into());
|
||||
});
|
||||
} else {
|
||||
theme::adjust_agent_ui_font_size(cx, |size| size + delta);
|
||||
theme::adjust_agent_font_size(cx, |size| size + delta);
|
||||
}
|
||||
}
|
||||
WhichFontSize::BufferFont => {
|
||||
@@ -1144,10 +1136,10 @@ impl AgentPanel {
|
||||
) {
|
||||
if action.persist {
|
||||
update_settings_file(self.fs.clone(), cx, move |settings, _| {
|
||||
settings.theme.agent_ui_font_size = None;
|
||||
settings.theme.agent_font_size = None;
|
||||
});
|
||||
} else {
|
||||
theme::reset_agent_ui_font_size(cx);
|
||||
theme::reset_agent_font_size(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2578,7 +2570,7 @@ impl Render for AgentPanel {
|
||||
|
||||
match self.active_view.which_font_size_used() {
|
||||
WhichFontSize::AgentFont => {
|
||||
WithRemSize::new(ThemeSettings::get_global(cx).agent_ui_font_size(cx))
|
||||
WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
|
||||
.size_full()
|
||||
.child(content)
|
||||
.into_any()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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))?)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -18,7 +18,7 @@ use portable_pty::{CommandBuilder, PtySize, native_pty_system};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsLocation};
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
@@ -32,8 +32,8 @@ use terminal_view::TerminalView;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{CommonAnimationExt, Disclosure, Tooltip, prelude::*};
|
||||
use util::{
|
||||
ResultExt, get_default_system_shell_preferring_bash, markdown::MarkdownInlineCode,
|
||||
size::format_file_size, time::duration_alt_display,
|
||||
ResultExt, get_default_system_shell, markdown::MarkdownInlineCode, size::format_file_size,
|
||||
time::duration_alt_display,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
@@ -122,27 +122,16 @@ impl Tool for TerminalTool {
|
||||
let cwd = working_dir.clone();
|
||||
let env = match &cwd {
|
||||
Some(dir) => project.update(cx, |project, cx| {
|
||||
let worktree = project.find_worktree(dir.as_path(), cx);
|
||||
let shell = TerminalSettings::get(
|
||||
worktree.as_ref().map(|(worktree, path)| SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: &path,
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
.shell
|
||||
.clone();
|
||||
let shell = TerminalSettings::get_global(cx).shell.clone();
|
||||
project.directory_environment(&shell, dir.as_path().into(), cx)
|
||||
}),
|
||||
None => Task::ready(None).shared(),
|
||||
};
|
||||
let shell = project
|
||||
.update(cx, |project, cx| {
|
||||
project
|
||||
.remote_client()
|
||||
.and_then(|r| r.read(cx).default_system_shell())
|
||||
})
|
||||
.unwrap_or_else(|| get_default_system_shell_preferring_bash());
|
||||
let remote_shell = project.update(cx, |project, cx| {
|
||||
project
|
||||
.remote_client()
|
||||
.and_then(|r| r.read(cx).default_system_shell())
|
||||
});
|
||||
|
||||
let env = cx.spawn(async move |_| {
|
||||
let mut env = env.await.unwrap_or_default();
|
||||
@@ -155,9 +144,12 @@ impl Tool for TerminalTool {
|
||||
let build_cmd = {
|
||||
let input_command = input.command.clone();
|
||||
move || {
|
||||
ShellBuilder::new(&Shell::Program(shell))
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(input_command), &[])
|
||||
ShellBuilder::new(
|
||||
remote_shell.as_deref(),
|
||||
&Shell::Program(get_default_system_shell()),
|
||||
)
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(input_command.clone()), &[])
|
||||
}
|
||||
};
|
||||
|
||||
@@ -486,7 +478,7 @@ impl ToolCard for TerminalToolCard {
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| env::current_dir().ok())
|
||||
.map(|path| path.display().to_string())
|
||||
.map(|path| format!("{}", path.display()))
|
||||
.unwrap_or_else(|| "current directory".to_string());
|
||||
|
||||
let header = h_flex()
|
||||
@@ -704,6 +696,7 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use terminal::terminal_settings::TerminalSettings;
|
||||
use theme::ThemeSettings;
|
||||
use util::{ResultExt as _, test::TempTree};
|
||||
|
||||
use super::*;
|
||||
@@ -718,7 +711,7 @@ mod tests {
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
ThemeSettings::register(cx);
|
||||
TerminalSettings::register(cx);
|
||||
EditorSettings::register(cx);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ pub struct AudioSettings {
|
||||
|
||||
/// Configuration of audio in Zed
|
||||
impl Settings for AudioSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let audio = &content.audio.as_ref().unwrap();
|
||||
AudioSettings {
|
||||
rodio_audio: audio.rodio_audio.unwrap(),
|
||||
|
||||
@@ -127,7 +127,7 @@ struct AutoUpdateSetting(bool);
|
||||
///
|
||||
/// Default: true
|
||||
impl Settings for AutoUpdateSetting {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self(content.auto_update.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use gpui::App;
|
||||
use settings::Settings;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -7,11 +8,17 @@ pub struct CallSettings {
|
||||
}
|
||||
|
||||
impl Settings for CallSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let call = content.calls.clone().unwrap();
|
||||
CallSettings {
|
||||
mute_on_join: call.mute_on_join.unwrap(),
|
||||
share_on_join: call.share_on_join.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn import_from_vscode(
|
||||
_vscode: &settings::VsCodeSettings,
|
||||
_current: &mut settings::SettingsContent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -101,7 +101,7 @@ pub struct ClientSettings {
|
||||
}
|
||||
|
||||
impl Settings for ClientSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
if let Some(server_url) = &*ZED_SERVER_URL {
|
||||
return Self {
|
||||
server_url: server_url.clone(),
|
||||
@@ -133,7 +133,7 @@ impl ProxySettings {
|
||||
}
|
||||
|
||||
impl Settings for ProxySettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
proxy: content.proxy.clone(),
|
||||
}
|
||||
@@ -519,7 +519,7 @@ pub struct TelemetrySettings {
|
||||
}
|
||||
|
||||
impl settings::Settings for TelemetrySettings {
|
||||
fn from_settings(content: &SettingsContent) -> Self {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),
|
||||
metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
|
||||
@@ -2041,10 +2041,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
});
|
||||
}
|
||||
|
||||
// This test started hanging on seed 2 after the theme settings
|
||||
// PR. The hypothesis is that it's been buggy for a while, but got lucky
|
||||
// on seeds.
|
||||
#[ignore]
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_inlay_hint_refresh_is_forwarded(
|
||||
cx_a: &mut TestAppContext,
|
||||
|
||||
@@ -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"]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -183,10 +183,9 @@ pub async fn run_randomized_test<T: RandomizedTest>(
|
||||
|
||||
for (client, cx) in clients {
|
||||
cx.update(|cx| {
|
||||
let settings = cx.remove_global::<SettingsStore>();
|
||||
let store = cx.remove_global::<SettingsStore>();
|
||||
cx.clear_globals();
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
cx.set_global(store);
|
||||
drop(client);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,7 +172,6 @@ impl TestServer {
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct NotificationPanelSettings {
|
||||
}
|
||||
|
||||
impl Settings for CollaborationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.collaboration_panel.as_ref().unwrap();
|
||||
|
||||
Self {
|
||||
@@ -30,7 +30,7 @@ impl Settings for CollaborationPanelSettings {
|
||||
}
|
||||
|
||||
impl Settings for NotificationPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let panel = content.notification_panel.as_ref().unwrap();
|
||||
return Self {
|
||||
button: panel.button.unwrap(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use dap_types::SteppingGranularity;
|
||||
use gpui::App;
|
||||
use settings::{Settings, SettingsContent};
|
||||
|
||||
pub struct DebuggerSettings {
|
||||
@@ -33,7 +34,7 @@ pub struct DebuggerSettings {
|
||||
}
|
||||
|
||||
impl Settings for DebuggerSettings {
|
||||
fn from_settings(content: &SettingsContent) -> Self {
|
||||
fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self {
|
||||
let content = content.debugger.clone().unwrap();
|
||||
Self {
|
||||
stepping_granularity: dap_granularity_from_settings(
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<_>>()
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -33,8 +33,8 @@ const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
|
||||
///
|
||||
/// See the [`display_map` module documentation](crate::display_map) for more information.
|
||||
pub struct BlockMap {
|
||||
pub(super) wrap_snapshot: RefCell<WrapSnapshot>,
|
||||
next_block_id: AtomicUsize,
|
||||
wrap_snapshot: RefCell<WrapSnapshot>,
|
||||
custom_blocks: Vec<Arc<CustomBlock>>,
|
||||
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
|
||||
transforms: RefCell<SumTree<Transform>>,
|
||||
@@ -53,7 +53,7 @@ pub struct BlockMapWriter<'a>(&'a mut BlockMap);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockSnapshot {
|
||||
pub(super) wrap_snapshot: WrapSnapshot,
|
||||
wrap_snapshot: WrapSnapshot,
|
||||
transforms: SumTree<Transform>,
|
||||
custom_blocks_by_id: TreeMap<CustomBlockId, Arc<CustomBlock>>,
|
||||
pub(super) buffer_header_height: u32,
|
||||
@@ -1395,7 +1395,7 @@ impl BlockSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn sticky_header_excerpt(&self, position: f64) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
pub fn sticky_header_excerpt(&self, position: f32) -> Option<StickyHeaderExcerpt<'_>> {
|
||||
let top_row = position as u32;
|
||||
let mut cursor = self.transforms.cursor::<BlockRow>(());
|
||||
cursor.seek(&BlockRow(top_row), Bias::Right);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -25,12 +25,13 @@ pub struct EditorSettings {
|
||||
pub lsp_highlight_debounce: u64,
|
||||
pub hover_popover_enabled: bool,
|
||||
pub hover_popover_delay: u64,
|
||||
pub status_bar: StatusBar,
|
||||
pub toolbar: Toolbar,
|
||||
pub scrollbar: Scrollbar,
|
||||
pub minimap: Minimap,
|
||||
pub gutter: Gutter,
|
||||
pub scroll_beyond_last_line: ScrollBeyondLastLine,
|
||||
pub vertical_scroll_margin: f64,
|
||||
pub vertical_scroll_margin: f32,
|
||||
pub autoscroll_on_clicks: bool,
|
||||
pub horizontal_scroll_margin: f32,
|
||||
pub scroll_sensitivity: f32,
|
||||
@@ -66,6 +67,18 @@ pub struct Jupyter {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StatusBar {
|
||||
/// Whether to display the active language button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub active_language_button: bool,
|
||||
/// Whether to show the cursor position button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub cursor_position_button: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Toolbar {
|
||||
pub breadcrumbs: bool,
|
||||
@@ -176,12 +189,13 @@ impl ScrollbarVisibility for EditorSettings {
|
||||
}
|
||||
|
||||
impl Settings for EditorSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let editor = content.editor.clone();
|
||||
let scrollbar = editor.scrollbar.unwrap();
|
||||
let minimap = editor.minimap.unwrap();
|
||||
let gutter = editor.gutter.unwrap();
|
||||
let axes = scrollbar.axes.unwrap();
|
||||
let status_bar = editor.status_bar.unwrap();
|
||||
let toolbar = editor.toolbar.unwrap();
|
||||
let search = editor.search.unwrap();
|
||||
let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
|
||||
@@ -194,6 +208,10 @@ impl Settings for EditorSettings {
|
||||
lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(),
|
||||
hover_popover_enabled: editor.hover_popover_enabled.unwrap(),
|
||||
hover_popover_delay: editor.hover_popover_delay.unwrap(),
|
||||
status_bar: StatusBar {
|
||||
active_language_button: status_bar.active_language_button.unwrap(),
|
||||
cursor_position_button: status_bar.cursor_position_button.unwrap(),
|
||||
},
|
||||
toolbar: Toolbar {
|
||||
breadcrumbs: toolbar.breadcrumbs.unwrap(),
|
||||
quick_actions: toolbar.quick_actions.unwrap(),
|
||||
@@ -230,7 +248,7 @@ impl Settings for EditorSettings {
|
||||
folds: gutter.folds.unwrap(),
|
||||
},
|
||||
scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(),
|
||||
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap() as f64,
|
||||
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap(),
|
||||
autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(),
|
||||
horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(),
|
||||
scroll_sensitivity: editor.scroll_sensitivity.unwrap(),
|
||||
|
||||
384
crates/editor/src/editor_settings_controls.rs
Normal file
384
crates/editor/src/editor_settings_controls.rs
Normal 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),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use collections::HashMap;
|
||||
use extension::{
|
||||
DownloadFileCapability, ExtensionCapability, NpmInstallPackageCapability, ProcessExecCapability,
|
||||
};
|
||||
use gpui::App;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -15,7 +13,6 @@ pub struct ExtensionSettings {
|
||||
/// Default: { "html": true }
|
||||
pub auto_install_extensions: HashMap<Arc<str>, bool>,
|
||||
pub auto_update_extensions: HashMap<Arc<str>, bool>,
|
||||
pub granted_capabilities: Vec<ExtensionCapability>,
|
||||
}
|
||||
|
||||
impl ExtensionSettings {
|
||||
@@ -36,30 +33,10 @@ impl ExtensionSettings {
|
||||
}
|
||||
|
||||
impl Settings for ExtensionSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
auto_install_extensions: content.extension.auto_install_extensions.clone(),
|
||||
auto_update_extensions: content.extension.auto_update_extensions.clone(),
|
||||
granted_capabilities: content
|
||||
.extension
|
||||
.granted_extension_capabilities
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|capability| match capability {
|
||||
settings::ExtensionCapabilityContent::ProcessExec { command, args } => {
|
||||
ExtensionCapability::ProcessExec(ProcessExecCapability { command, args })
|
||||
}
|
||||
settings::ExtensionCapabilityContent::DownloadFile { host, path } => {
|
||||
ExtensionCapability::DownloadFile(DownloadFileCapability { host, path })
|
||||
}
|
||||
settings::ExtensionCapabilityContent::NpmInstallPackage { package } => {
|
||||
ExtensionCapability::NpmInstallPackage(NpmInstallPackageCapability {
|
||||
package,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub struct FileFinderSettings {
|
||||
}
|
||||
|
||||
impl Settings for FileFinderSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let file_finder = content.file_finder.as_ref().unwrap();
|
||||
|
||||
Self {
|
||||
|
||||
@@ -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)),
|
||||
)],
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ doctest = false
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
util.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
@@ -2,7 +2,8 @@ use std::sync::Arc;
|
||||
use std::{path::Path, str};
|
||||
|
||||
use gpui::{App, SharedString};
|
||||
use theme::{GlobalTheme, IconTheme, ThemeRegistry};
|
||||
use settings::Settings;
|
||||
use theme::{IconTheme, ThemeRegistry, ThemeSettings};
|
||||
use util::paths::PathExt;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -12,8 +13,10 @@ pub struct FileIcons {
|
||||
|
||||
impl FileIcons {
|
||||
pub fn get(cx: &App) -> Self {
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
|
||||
Self {
|
||||
icon_theme: GlobalTheme::icon_theme(cx).clone(),
|
||||
icon_theme: theme_settings.active_icon_theme.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +52,6 @@ impl FileIcons {
|
||||
}
|
||||
}
|
||||
|
||||
// handle cases where the file extension is made up of multiple important
|
||||
// parts (e.g Component.stories.tsx) that refer to an alternative icon style
|
||||
if let Some(suffix) = path.multiple_extensions() {
|
||||
let maybe_path = get_icon_from_suffix(suffix.as_str());
|
||||
if maybe_path.is_some() {
|
||||
return maybe_path;
|
||||
}
|
||||
}
|
||||
|
||||
// primary case: check if the files extension or the hidden file name
|
||||
// matches some icon path
|
||||
if let Some(suffix) = path.extension_or_hidden_file_name() {
|
||||
@@ -94,7 +88,7 @@ impl FileIcons {
|
||||
.map(|icon_definition| icon_definition.path.clone())
|
||||
}
|
||||
|
||||
get_icon_for_type(GlobalTheme::icon_theme(cx), typ).or_else(|| {
|
||||
get_icon_for_type(&ThemeSettings::get_global(cx).active_icon_theme, typ).or_else(|| {
|
||||
Self::default_icon_theme(cx).and_then(|icon_theme| get_icon_for_type(&icon_theme, typ))
|
||||
})
|
||||
}
|
||||
@@ -119,16 +113,20 @@ impl FileIcons {
|
||||
}
|
||||
}
|
||||
|
||||
get_folder_icon(GlobalTheme::icon_theme(cx), path, expanded)
|
||||
.or_else(|| {
|
||||
Self::default_icon_theme(cx)
|
||||
.and_then(|icon_theme| get_folder_icon(&icon_theme, path, expanded))
|
||||
})
|
||||
.or_else(|| {
|
||||
// If we can't find a specific folder icon for the folder at the given path, fall back to the generic folder
|
||||
// icon.
|
||||
Self::get_generic_folder_icon(expanded, cx)
|
||||
})
|
||||
get_folder_icon(
|
||||
&ThemeSettings::get_global(cx).active_icon_theme,
|
||||
path,
|
||||
expanded,
|
||||
)
|
||||
.or_else(|| {
|
||||
Self::default_icon_theme(cx)
|
||||
.and_then(|icon_theme| get_folder_icon(&icon_theme, path, expanded))
|
||||
})
|
||||
.or_else(|| {
|
||||
// If we can't find a specific folder icon for the folder at the given path, fall back to the generic folder
|
||||
// icon.
|
||||
Self::get_generic_folder_icon(expanded, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_generic_folder_icon(expanded: bool, cx: &App) -> Option<SharedString> {
|
||||
@@ -143,10 +141,12 @@ impl FileIcons {
|
||||
}
|
||||
}
|
||||
|
||||
get_generic_folder_icon(GlobalTheme::icon_theme(cx), expanded).or_else(|| {
|
||||
Self::default_icon_theme(cx)
|
||||
.and_then(|icon_theme| get_generic_folder_icon(&icon_theme, expanded))
|
||||
})
|
||||
get_generic_folder_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(
|
||||
|| {
|
||||
Self::default_icon_theme(cx)
|
||||
.and_then(|icon_theme| get_generic_folder_icon(&icon_theme, expanded))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_chevron_icon(expanded: bool, cx: &App) -> Option<SharedString> {
|
||||
@@ -158,7 +158,7 @@ impl FileIcons {
|
||||
}
|
||||
}
|
||||
|
||||
get_chevron_icon(GlobalTheme::icon_theme(cx), expanded).or_else(|| {
|
||||
get_chevron_icon(&ThemeSettings::get_global(cx).active_icon_theme, expanded).or_else(|| {
|
||||
Self::default_icon_theme(cx)
|
||||
.and_then(|icon_theme| get_chevron_icon(&icon_theme, expanded))
|
||||
})
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut 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: ¬ify::Event| {
|
||||
let kind = match event.kind {
|
||||
EventKind::Create(_) => Some(PathEventKind::Created),
|
||||
EventKind::Modify(_) => Some(PathEventKind::Changed),
|
||||
EventKind::Remove(_) => Some(PathEventKind::Removed),
|
||||
_ => None,
|
||||
};
|
||||
let mut 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),
|
||||
);
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
})??;
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct GitHostingProviderSettings {
|
||||
}
|
||||
|
||||
impl Settings for GitHostingProviderSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
Self {
|
||||
git_hosting_providers: content
|
||||
.project
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -263,6 +263,10 @@ impl Item for FileDiffView {
|
||||
.update(cx, |editor, cx| editor.deactivated(window, cx));
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
@@ -360,7 +364,7 @@ mod tests {
|
||||
use editor::test::editor_test_context::assert_state_with_diff;
|
||||
use gpui::TestAppContext;
|
||||
use project::{FakeFs, Fs, Project};
|
||||
use settings::SettingsStore;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::path::PathBuf;
|
||||
use unindent::unindent;
|
||||
use util::path;
|
||||
@@ -374,7 +378,7 @@ mod tests {
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
editor::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
theme::ThemeSettings::register(cx)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ScrollbarVisibility for GitPanelSettings {
|
||||
}
|
||||
|
||||
impl Settings for GitPanelSettings {
|
||||
fn from_settings(content: &settings::SettingsContent) -> Self {
|
||||
fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
|
||||
let git_panel = content.git_panel.clone().unwrap();
|
||||
Self {
|
||||
button: git_panel.button.unwrap(),
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +324,10 @@ impl Item for TextDiffView {
|
||||
.update(cx, |editor, cx| editor.deactivated(window, cx));
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
@@ -450,7 +454,7 @@ mod tests {
|
||||
use gpui::{TestAppContext, VisualContext};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use unindent::unindent;
|
||||
use util::{path, test::marked_text_ranges};
|
||||
|
||||
@@ -462,7 +466,7 @@ mod tests {
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
editor::init_settings(cx);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
theme::ThemeSettings::register(cx)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user