Compare commits

..

4 Commits

Author SHA1 Message Date
Piotr Osiewicz
217fc298ed Merge branch 'main' into language-toolchains 2024-10-18 14:26:50 +02:00
Piotr Osiewicz
cf304df0dc WIP 2024-10-17 13:27:12 +02:00
Piotr Osiewicz
7b8463b566 💄 2024-10-17 11:16:07 +02:00
Piotr Osiewicz
241a73df54 wip 2024-10-17 10:58:42 +02:00
214 changed files with 3809 additions and 5880 deletions

View File

@@ -14,7 +14,6 @@ on:
- "**"
paths-ignore:
- "docs/**"
- ".github/workflows/community_*"
concurrency:
# Allow only one workflow per any non-`main` branch.
@@ -138,7 +137,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -170,7 +169,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "buildjet"
@@ -193,7 +192,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"
@@ -267,20 +266,20 @@ jobs:
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
- name: Upload app bundle (universal) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg
path: target/release/Zed.dmg
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
@@ -331,7 +330,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
@@ -378,7 +377,7 @@ jobs:
run: script/bundle-linux
- name: Upload Linux bundle to workflow run if main branch or specific label
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
with:
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
@@ -386,6 +385,7 @@ jobs:
- name: Upload app bundle to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

View File

@@ -14,10 +14,10 @@ jobs:
stale-issue-message: >
Hi there! 👋
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. If you are able to reproduce this issue in the latest version of Zed, please let us know by commenting on this issue, and we will keep it open. If you can't reproduce it, feel free to close the issue yourself. Otherwise, we'll close it in 7 days.
We're working to clean up our issue tracker by closing older issues that might not be relevant anymore. Are you able to reproduce this issue in the latest version of Zed? If so, please let us know by commenting on this issue and we will keep it open; otherwise, we'll close it in 7 days. Feel free to open a new issue if you're seeing this message after the issue has been closed.
Thanks for your help!
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, please open a new issue with a link to this issue."
close-issue-message: "This issue was closed due to inactivity. If you're still experiencing this problem, feel free to ping a Zed team member to reopen this issue or open a new one."
# We will increase `days-before-stale` to 365 on or after Jan 24th,
# 2024. This date marks one year since migrating issues from
# 'community' to 'zed' repository. The migration added activity to all

View File

@@ -21,7 +21,7 @@ jobs:
clean: false
- name: Cache dependencies
uses: swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2
uses: swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-provider: "github"

66
Cargo.lock generated
View File

@@ -1009,7 +1009,6 @@ dependencies = [
"smol",
"tempfile",
"util",
"which 6.0.3",
"workspace",
]
@@ -1578,7 +1577,7 @@ dependencies = [
"bitflags 2.6.0",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"itertools 0.12.1",
"lazy_static",
"lazycell",
"proc-macro2",
@@ -3650,12 +3649,6 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ec4rs"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf65d056c7da9c971c2847ce250fd1f0f9659d5718845c3ec0ad95f5668352c"
[[package]]
name = "ecdsa"
version = "0.14.8"
@@ -3723,7 +3716,6 @@ dependencies = [
"tree-sitter-rust",
"tree-sitter-typescript",
"ui",
"unicode-segmentation",
"unindent",
"url",
"util",
@@ -5589,7 +5581,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.4.10",
"socket2 0.5.7",
"tokio",
"tower-service",
"tracing",
@@ -6218,7 +6210,6 @@ dependencies = [
"clock",
"collections",
"ctor",
"ec4rs",
"env_logger",
"futures 0.3.30",
"fuzzy",
@@ -6232,7 +6223,6 @@ dependencies = [
"lsp",
"parking_lot",
"postage",
"pretty_assertions",
"pulldown-cmark 0.12.1",
"rand 0.8.5",
"regex",
@@ -6474,7 +6464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -8988,7 +8978,6 @@ dependencies = [
"itertools 0.13.0",
"language",
"log",
"markdown",
"menu",
"ordered-float 2.10.1",
"paths",
@@ -9004,7 +8993,6 @@ dependencies = [
"smol",
"task",
"terminal_view",
"theme",
"ui",
"util",
"workspace",
@@ -9131,7 +9119,6 @@ name = "remote"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"collections",
"fs",
"futures 0.3.30",
@@ -9161,7 +9148,6 @@ dependencies = [
"client",
"clock",
"env_logger",
"fork",
"fs",
"futures 0.3.30",
"git",
@@ -9170,7 +9156,6 @@ dependencies = [
"http_client",
"language",
"languages",
"libc",
"log",
"lsp",
"node_runtime",
@@ -9318,7 +9303,6 @@ dependencies = [
"system-configuration 0.6.1",
"tokio",
"tokio-rustls 0.26.0",
"tokio-socks",
"tokio-util",
"tower-service",
"url",
@@ -10316,7 +10300,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"ec4rs",
"fs",
"futures 0.3.30",
"gpui",
@@ -12018,7 +12001,6 @@ dependencies = [
"futures-io",
"futures-util",
"thiserror",
"tokio",
]
[[package]]
@@ -12443,7 +12425,7 @@ checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57"
[[package]]
name = "tree-sitter-md"
version = "0.3.2"
source = "git+https://github.com/tree-sitter-grammars/tree-sitter-markdown?rev=9a23c1a96c0513d8fc6520972beedd419a973539#9a23c1a96c0513d8fc6520972beedd419a973539"
source = "git+https://github.com/zed-industries/tree-sitter-markdown?rev=4cfa6aad6b75052a5077c80fd934757d9267d81b#4cfa6aad6b75052a5077c80fd934757d9267d81b"
dependencies = [
"cc",
"tree-sitter-language",
@@ -13427,9 +13409,9 @@ dependencies = [
[[package]]
name = "wasmtime-wasi"
version = "24.0.1"
version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda03f5bfd5c4cc09f75c7e44846663f25f2c48a2d688fbfb5c7a33af6cf34f5"
checksum = "545ae8298ffce025604f7480f9c7d6948c985bef7ce9aee249ef79307813e83c"
dependencies = [
"anyhow",
"async-trait",
@@ -13682,9 +13664,9 @@ dependencies = [
[[package]]
name = "wiggle"
version = "24.0.1"
version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3b31bd2b4d2d82a4b747b8dbc45f566214214a4ffdc5690429a73bc221dc8a"
checksum = "cc850ca3c02c5835934d23f28cec4c5a3fb66fe0b4ecd968bbb35609dda5ddc0"
dependencies = [
"anyhow",
"async-trait",
@@ -13697,9 +13679,9 @@ dependencies = [
[[package]]
name = "wiggle-generate"
version = "24.0.1"
version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2c6136b195fc12067aa9d4e7a5baf118729394df7bc7cbf8c63119bc9f2a7cd"
checksum = "634b8804a67200bcb43ea8af5f7c53e862439a086b68b16fd333454bc74d5aab"
dependencies = [
"anyhow",
"heck 0.4.1",
@@ -13712,9 +13694,9 @@ dependencies = [
[[package]]
name = "wiggle-macro"
version = "24.0.1"
version = "24.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a41eaceee468da976ac43b85c4eb82e482f828d5e8e56f49f90dfac2d9bc3b4"
checksum = "474b7cbdb942c74031e619d66c600bba7f73867c5800fc2c2306cf307649be2f"
dependencies = [
"proc-macro2",
"quote",
@@ -13744,7 +13726,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -14601,7 +14583,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.160.0"
version = "0.159.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -14709,6 +14691,7 @@ dependencies = [
"winresource",
"workspace",
"zed_actions",
"zstd",
]
[[package]]
@@ -14757,7 +14740,7 @@ dependencies = [
[[package]]
name = "zed_elixir"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -14840,7 +14823,7 @@ dependencies = [
[[package]]
name = "zed_php"
version = "0.2.2"
version = "0.2.1"
dependencies = [
"zed_extension_api 0.1.0",
]
@@ -14881,6 +14864,13 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_svelte"
version = "0.2.0"
dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_terraform"
version = "0.1.1"
@@ -14909,6 +14899,14 @@ dependencies = [
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_vue"
version = "0.1.0"
dependencies = [
"serde",
"zed_extension_api 0.1.0",
]
[[package]]
name = "zed_zig"
version = "0.3.1"

View File

@@ -158,10 +158,12 @@ members = [
"extensions/ruff",
"extensions/slash-commands-example",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
"extensions/test-extension",
"extensions/toml",
"extensions/uiua",
"extensions/vue",
"extensions/zig",
#
@@ -347,7 +349,6 @@ ctor = "0.2.6"
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
ec4rs = "1.1"
emojis = "0.6.1"
env_logger = "0.11"
exec = "0.3.1"
@@ -390,14 +391,7 @@ pulldown-cmark = { version = "0.12.0", default-features = false }
rand = "0.8.5"
regex = "1.5"
repair_json = "0.1.0"
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = [
"charset",
"http2",
"macos-system-configuration",
"rustls-tls-native-roots",
"socks",
"stream",
] }
reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29", default-features = false, features = ["charset", "http2", "macos-system-configuration", "rustls-tls-native-roots", "stream"]}
rsa = "0.9.6"
runtimelib = { version = "0.15", default-features = false, features = [
"async-dispatcher-runtime",
@@ -459,7 +453,7 @@ tree-sitter-diff = "0.1.0"
tree-sitter-html = "0.20"
tree-sitter-jsdoc = "0.23"
tree-sitter-json = "0.23"
tree-sitter-md = { git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown", rev = "9a23c1a96c0513d8fc6520972beedd419a973539" }
tree-sitter-md = { git = "https://github.com/zed-industries/tree-sitter-markdown", rev = "4cfa6aad6b75052a5077c80fd934757d9267d81b" }
tree-sitter-python = "0.23"
tree-sitter-regex = "0.23"
tree-sitter-ruby = "0.23"
@@ -468,7 +462,7 @@ tree-sitter-typescript = "0.23"
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
unicase = "2.6"
unindent = "0.1.7"
unicode-segmentation = "1.11"
unicode-segmentation = "1.10"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4", "v5", "serde"] }
wasmparser = "0.215"

View File

@@ -65,7 +65,6 @@
"h": "c",
"handlebars": "code",
"hbs": "template",
"hcl": "hcl",
"heex": "elixir",
"heic": "image",
"heif": "image",
@@ -90,7 +89,6 @@
"json": "storage",
"jsonc": "storage",
"jsx": "react",
"julia": "julia",
"jxl": "image",
"kt": "kotlin",
"ldf": "storage",
@@ -118,7 +116,6 @@
"myd": "storage",
"myi": "storage",
"nim": "nim",
"nix": "nix",
"nu": "terminal",
"odp": "document",
"ods": "document",
@@ -131,7 +128,6 @@
"php": "php",
"plist": "template",
"png": "image",
"postcss": "css",
"ppt": "document",
"pptx": "document",
"prettierignore": "prettier",
@@ -146,15 +142,12 @@
"rb": "ruby",
"rebar.config": "erlang",
"rkt": "code",
"roc": "roc",
"rs": "rust",
"rtf": "document",
"sass": "sass",
"sav": "storage",
"sc": "scala",
"scala": "scala",
"scm": "code",
"scss": "sass",
"sdf": "storage",
"sh": "terminal",
"sql": "storage",
@@ -188,7 +181,6 @@
"yaml": "settings",
"yml": "settings",
"yrl": "erlang",
"zig": "zig",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
@@ -273,9 +265,6 @@
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
"hcl": {
"icon": "icons/file_icons/hcl.svg"
},
"heroku": {
"icon": "icons/file_icons/heroku.svg"
},
@@ -288,9 +277,6 @@
"javascript": {
"icon": "icons/file_icons/javascript.svg"
},
"julia": {
"icon": "icons/file_icons/julia.svg"
},
"kotlin": {
"icon": "icons/file_icons/kotlin.svg"
},
@@ -306,9 +292,6 @@
"nim": {
"icon": "icons/file_icons/nim.svg"
},
"nix": {
"icon": "icons/file_icons/nix.svg"
},
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
@@ -333,18 +316,12 @@
"react": {
"icon": "icons/file_icons/react.svg"
},
"roc": {
"icon": "icons/file_icons/roc.svg"
},
"ruby": {
"icon": "icons/file_icons/ruby.svg"
},
"rust": {
"icon": "icons/file_icons/rust.svg"
},
"sass": {
"icon": "icons/file_icons/sass.svg"
},
"scala": {
"icon": "icons/file_icons/scala.svg"
},
@@ -383,9 +360,6 @@
},
"vue": {
"icon": "icons/file_icons/vue.svg"
},
"zig": {
"icon": "icons/file_icons/zig.svg"
}
}
}

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.11466 3.11809C7.21859 3.37393 7.09545 3.66558 6.83961 3.76952L4.31181 4.79643C4.1233 4.87302 4 5.05619 4 5.25967V11.5C4 11.7761 3.77614 12 3.5 12H2.5C2.22386 12 2 11.7761 2 11.5V4.41827C2 3.90959 2.30825 3.45164 2.77953 3.26018L6.08686 1.91658C6.34269 1.81265 6.63434 1.93579 6.73828 2.19163L7.11466 3.11809ZM10.5 1.99999C10.7761 1.99999 11 2.22384 11 2.49999V10.5C11 10.7761 10.7761 11 10.5 11H9.5C9.22386 11 9 10.7761 9 10.5V9.49999C9 9.22384 8.77614 8.99999 8.5 8.99999H7.5C7.22386 8.99999 7 9.22384 7 9.49999V13.5C7 13.7761 6.77614 14 6.5 14H5.5C5.22386 14 5 13.7761 5 13.5V5.53124C5 5.25509 5.22386 5.03124 5.5 5.03124H6.5C6.77614 5.03124 7 5.25509 7 5.53124V6.49999C7 6.77613 7.22386 6.99999 7.5 6.99999H8.5C8.77614 6.99999 9 6.77613 9 6.49999V2.49999C9 2.22384 9.22386 1.99999 9.5 1.99999H10.5ZM13.5 4.03124C13.7761 4.03124 14 4.2551 14 4.53124L14 11.5847C14 12.0859 13.7006 12.5386 13.2394 12.7349L9.99399 14.1159C9.7399 14.224 9.44626 14.1057 9.33813 13.8516L8.94658 12.9315C8.83845 12.6774 8.95678 12.3837 9.21087 12.2756L11.6958 11.2182C11.8802 11.1397 12 10.9586 12 10.7581L12 4.53124C12 4.2551 12.2238 4.03124 12.5 4.03124L13.5 4.03124Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="5" r="2.75" fill="black"/>
<circle cx="4.75" cy="11" r="2.75" fill="black" fill-opacity="0.5"/>
<circle cx="11.25" cy="11" r="2.75" fill="black" fill-opacity="0.75"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.00005 4.76556L4.76569 2.74996M6.00005 4.76556L3.75 4.76563M6.00005 4.76556L7.25006 4.7656" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M10.0232 11.2311L11.2675 13.2406M10.0232 11.2311L12.2732 11.2199M10.0232 11.2311L8.7732 11.2373" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M9.99025 4.91551L10.9985 2.77781M9.99025 4.91551L8.75599 3.03419M9.99025 4.91551L10.6759 5.9607" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.0323 11.1009L5.03465 13.2436M6.0323 11.1009L7.27585 12.9761M6.0323 11.1009L5.34151 10.0592" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M11.883 8.19023L14.2466 8.19287M11.883 8.19023L13.0602 6.27268M11.883 8.19023L11.229 9.25547" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
<path d="M4.12354 7.8356L1.76002 7.84465M4.12354 7.8356L2.95585 9.75894M4.12354 7.8356L4.7723 6.76713" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,7 +0,0 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.51497 2.02702L1.92042 1.95067C1.69543 1.94589 1.57917 2.21756 1.73796 2.37702L6.24865 6.9068C6.42388 7.08277 6.72071 6.92326 6.67067 6.68002L5.75454 2.22659C5.73103 2.11231 5.63161 2.02949 5.51497 2.02702Z" fill="black" fill-opacity="0.5"/>
<path d="M8.05816 7.38492L12.1366 8.02844C12.3704 8.06532 12.5198 7.78697 12.3599 7.61255L7.30439 2.09814C7.13336 1.91159 6.82522 2.06811 6.87499 2.31624L7.852 7.18714C7.87257 7.28971 7.95483 7.36862 8.05816 7.38492Z" fill="black"/>
<path d="M9.0952 10.9797L11.3824 9.35081C11.564 9.22151 11.4983 8.93722 11.2785 8.90058L8.496 8.43683C8.31974 8.40746 8.17047 8.56712 8.21162 8.74101L8.70689 10.8337C8.74777 11.0064 8.95062 11.0827 9.0952 10.9797Z" fill="black" fill-opacity="0.5"/>
<path d="M5.10282 13.9632L7.59108 12.4532C7.68331 12.3972 7.72923 12.2884 7.70498 12.1832L6.75736 8.07484C6.699 7.8218 6.34133 7.81448 6.27266 8.06491L4.73201 13.6834C4.67223 13.9014 4.90954 14.0805 5.10282 13.9632Z" fill="black"/>
<path d="M11.3183 4.89351L13.1588 7.03149L15.535 6.14302C15.7099 6.07761 15.754 5.85043 15.6161 5.72438L13.7222 3.99219L11.4546 4.48614C11.2695 4.52645 11.1947 4.74995 11.3183 4.89351Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.92096 7.00668C7.87408 7.83549 10.0987 7.48203 10.9376 7.06254C12.8751 6.09381 13.9407 4.39379 12.6407 2.90629C11.0157 1.04692 6.24221 2.49998 4.89844 3.40625C3.55467 4.31252 2.67972 5.53126 2.89071 7.1719C3.1017 8.81254 4.68758 9.7422 6.03128 10.3203C5.38786 10.5616 3.8517 11.0388 3.3125 11.7188C2.71341 12.4742 3.04343 14 4.51577 14C7.15639 14 7.59539 11.1486 7.14847 10.4375C7.88773 10.1295 8.49597 9.96169 9.40138 9.77081C9.63831 9.72087 9.65457 9.46395 9.41295 9.44827C8.80252 9.40864 7.30567 9.8489 6.92096 9.97657C5.78909 9.35157 4.51016 7.93818 4.59378 6.87501C4.68676 5.6928 5.27676 5.07603 6.84508 4.21876C8.01705 3.57813 10.258 3.10695 11.25 3.62501C12.6563 4.35936 10.7875 5.75599 9.92969 6.32031C9.28179 6.74656 8.21971 6.77513 7.22979 6.61435C6.99371 6.576 6.74048 6.84974 6.92096 7.00668ZM5.6719 12.4643C6.35508 11.9894 6.45471 11.1076 6.29955 10.8844C5.76663 11.0874 4.36593 11.9102 4.75111 12.4643C4.90628 12.6875 5.31358 12.7134 5.6719 12.4643Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.25 12H11C10.794 12 10.6764 11.7648 10.8 11.6L11.925 10.1C11.9722 10.037 12.0463 10 12.125 10H12.75C12.8881 10 13 9.88807 13 9.75V6.25C13 6.11193 12.8881 6 12.75 6H12.4045C12.2187 6 12.0978 5.80442 12.1809 5.6382L12.9309 4.1382C12.9732 4.0535 13.0598 4 13.1545 4H14.25C14.3881 4 14.5 4.11193 14.5 4.25V11.75C14.5 11.8881 14.3881 12 14.25 12Z" fill="black"/>
<path d="M1.75 4H5C5.20601 4 5.32361 4.23519 5.2 4.4L4.075 5.9C4.02779 5.96295 3.95369 6 3.875 6H3.25C3.11193 6 3 6.11193 3 6.25V9.75C3 9.88807 3.11193 10 3.25 10H3.59549C3.78134 10 3.90221 10.1956 3.8191 10.3618L3.0691 11.8618C3.02675 11.9465 2.94018 12 2.84549 12H1.75C1.61193 12 1.5 11.8881 1.5 11.75V4.25C1.5 4.11193 1.61193 4 1.75 4Z" fill="black"/>
<path d="M7.55748 6H5.95006C5.74177 6 5.62482 5.76022 5.75306 5.59609L6.92493 4.09609C6.97231 4.03544 7.04498 4 7.12194 4H9.93075C9.97607 4 10.0205 3.98769 10.0594 3.96437L11.6408 3.0155C11.8641 2.88154 12.1179 3.13555 11.9837 3.3587L8.22612 9.6083C8.12629 9.77433 8.24508 9.98591 8.43881 9.98712L10.0039 9.9969C10.2092 9.99818 10.3255 10.2327 10.2023 10.3969L9.075 11.9C9.02779 11.963 8.95369 12 8.875 12H6.55383C6.51835 12 6.48328 12.0076 6.45094 12.0222L4.32473 12.9824C4.10122 13.0833 3.88113 12.8356 4.00771 12.6255L7.77161 6.37903C7.87201 6.2124 7.75202 6 7.55748 6Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -34,7 +34,7 @@
"cmd-]": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"cmd-alt-f7": "editor::FindAllReferences",
"cmd-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
"cmd-b": "editor::GoToDefinition",
"cmd-alt-b": "editor::GoToDefinitionSplit",
"cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
@@ -64,8 +64,7 @@
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-alt-o": "project_symbols::Toggle",
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
}

View File

@@ -803,7 +803,7 @@
/// You can override this to use a version of node that is not in $PATH with:
/// {
/// "node": {
/// "path": "/path/to/node"
/// "node_path": "/path/to/node"
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
/// }
/// }

View File

@@ -29,13 +29,13 @@ pub struct AnthropicModelCacheConfiguration {
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
#[default]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-latest")]
#[serde(rename = "claude-3-5-sonnet", alias = "claude-3-5-sonnet-20240620")]
Claude3_5Sonnet,
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
#[serde(rename = "claude-3-opus", alias = "claude-3-opus-20240229")]
Claude3Opus,
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
#[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-20240229")]
Claude3Sonnet,
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-latest")]
#[serde(rename = "claude-3-haiku", alias = "claude-3-haiku-20240307")]
Claude3Haiku,
#[serde(rename = "custom")]
Custom {
@@ -69,10 +69,10 @@ impl Model {
pub fn id(&self) -> &str {
match self {
Model::Claude3_5Sonnet => "claude-3-5-sonnet-latest",
Model::Claude3Opus => "claude-3-opus-latest",
Model::Claude3Sonnet => "claude-3-sonnet-latest",
Model::Claude3Haiku => "claude-3-haiku-latest",
Model::Claude3_5Sonnet => "claude-3-5-sonnet-20240620",
Model::Claude3Opus => "claude-3-opus-20240229",
Model::Claude3Sonnet => "claude-3-sonnet-20240229",
Model::Claude3Haiku => "claude-3-haiku-20240307",
Self::Custom { name, .. } => name,
}
}

View File

@@ -356,10 +356,8 @@ impl AssistantPanel {
let project = workspace.project().clone();
pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
let action = maybe!({
if project.read(cx).is_local() {
if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
}
if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
}
let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
@@ -2221,7 +2219,6 @@ impl ContextEditor {
merge_adjacent: false,
};
let should_refold;
if let Some(state) = self.patches.get_mut(&range) {
replaced_blocks.insert(state.footer_block_id, render_block);
if let Some(editor_state) = &state.editor {
@@ -2236,9 +2233,6 @@ impl ContextEditor {
});
}
}
should_refold =
snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
} else {
let block_ids = editor.insert_blocks(
[BlockProperties {
@@ -2272,14 +2266,10 @@ impl ContextEditor {
update_task: None,
},
);
should_refold = true;
}
if should_refold {
editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
}
editor.unfold_ranges([patch_start..patch_end], true, false, cx);
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
}
editor.remove_creases(removed_crease_ids, cx);

View File

@@ -7,7 +7,7 @@ use crate::{
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
SlashCommandOutput, SlashCommandOutputSection, SlashCommandRegistry,
};
use assistant_tool::ToolRegistry;
use client::{self, proto, telemetry::Telemetry};
@@ -1677,7 +1677,7 @@ impl Context {
pub fn insert_command_output(
&mut self,
command_range: Range<language::Anchor>,
output: Task<SlashCommandResult>,
output: Task<Result<SlashCommandOutput>>,
ensure_trailing_newline: bool,
expand_result: bool,
cx: &mut ModelContext<Self>,
@@ -2487,8 +2487,7 @@ impl Context {
request.messages.push(LanguageModelRequestMessage {
role: Role::User,
content: vec![
"Generate a concise 3-7 word title for this conversation, omitting punctuation"
.into(),
"Summarize the context into a short title without punctuation.".into(),
],
cache: false,
});

View File

@@ -6,7 +6,7 @@ use crate::{
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandRegistry, SlashCommandResult,
SlashCommandRegistry,
};
use collections::HashSet;
use fs::FakeFs;
@@ -1416,7 +1416,7 @@ impl SlashCommand for FakeSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
Task::ready(Ok(SlashCommandOutput {
text: format!("Executed fake command: {}", self.0),
sections: vec![],

View File

@@ -2256,7 +2256,6 @@ pub enum CodegenEvent {
pub struct Codegen {
alternatives: Vec<Model<CodegenAlternative>>,
active_alternative: usize,
seen_alternatives: HashSet<usize>,
subscriptions: Vec<Subscription>,
buffer: Model<MultiBuffer>,
range: Range<Anchor>,
@@ -2287,7 +2286,6 @@ impl Codegen {
let mut this = Self {
alternatives: vec![codegen],
active_alternative: 0,
seen_alternatives: HashSet::default(),
subscriptions: Vec::new(),
buffer,
range,
@@ -2340,7 +2338,6 @@ impl Codegen {
fn activate(&mut self, index: usize, cx: &mut ModelContext<Self>) {
self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(false, cx));
self.seen_alternatives.insert(index);
self.active_alternative = index;
self.active_alternative()
.update(cx, |codegen, cx| codegen.set_active(true, cx));
@@ -2470,8 +2467,6 @@ pub struct CodegenAlternative {
active: bool,
edits: Vec<(Range<Anchor>, String)>,
line_operations: Vec<LineOperation>,
request: Option<LanguageModelRequest>,
elapsed_time: Option<f64>,
}
enum CodegenStatus {
@@ -2543,8 +2538,6 @@ impl CodegenAlternative {
edits: Vec::new(),
line_operations: Vec::new(),
range,
request: None,
elapsed_time: None,
}
}
@@ -2641,7 +2634,6 @@ impl CodegenAlternative {
async { Ok(stream::empty().boxed()) }.boxed_local()
} else {
let request = self.build_request(user_prompt, assistant_panel_context, cx)?;
self.request = Some(request.clone());
let chunks = cx
.spawn(|_, cx| async move { model.stream_completion_text(request, &cx).await });
@@ -2715,7 +2707,6 @@ impl CodegenAlternative {
stream: impl 'static + Future<Output = Result<BoxStream<'static, Result<String>>>>,
cx: &mut ModelContext<Self>,
) {
let start_time = Instant::now();
let snapshot = self.snapshot.clone();
let selected_text = snapshot
.text_for_range(self.range.start..self.range.end)
@@ -2932,8 +2923,6 @@ impl CodegenAlternative {
};
let result = generate.await;
let elapsed_time = start_time.elapsed().as_secs_f64();
codegen
.update(&mut cx, |this, cx| {
this.last_equal_ranges.clear();
@@ -2942,7 +2931,6 @@ impl CodegenAlternative {
} else {
this.status = CodegenStatus::Done;
}
this.elapsed_time = Some(elapsed_time);
cx.emit(CodegenEvent::Finished);
cx.notify();
})
@@ -3289,10 +3277,6 @@ impl CodeActionProvider for AssistantCodeActionProvider {
range: Range<text::Anchor>,
cx: &mut WindowContext,
) -> Task<Result<Vec<CodeAction>>> {
if !AssistantSettings::get_global(cx).enabled {
return Task::ready(Ok(Vec::new()));
}
let snapshot = buffer.read(cx).snapshot();
let mut range = range.to_point(&snapshot);

View File

@@ -146,28 +146,12 @@ impl ResolvedEdit {
return false;
}
let other_offset_range = other_range.to_offset(buffer);
let offset_range = range.to_offset(buffer);
// If the other range is empty at the start of this edit's range, combine the new text
if other_offset_range.is_empty() && other_offset_range.start == offset_range.start {
self.new_text = format!("{}\n{}", other.new_text, self.new_text);
self.range.start = other_range.start;
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
*description = format!("{}\n{}", other_description, description)
}
} else {
if let Some((description, other_description)) =
self.description.as_mut().zip(other.description.as_ref())
{
if let Some(description) = &mut self.description {
if let Some(other_description) = &other.description {
description.push('\n');
description.push_str(other_description);
}
}
true
}
}
@@ -715,156 +699,6 @@ mod tests {
.unindent(),
cx,
);
// Ensure InsertBefore merges correctly with Update of the same text
assert_edits(
"
fn foo() {
}
"
.unindent(),
vec![
AssistantEditKind::InsertBefore {
old_text: "
fn foo() {"
.unindent(),
new_text: "
fn bar() {
qux();
}"
.unindent(),
description: "implement bar".into(),
},
AssistantEditKind::Update {
old_text: "
fn foo() {
}"
.unindent(),
new_text: "
fn foo() {
bar();
}"
.unindent(),
description: "call bar in foo".into(),
},
AssistantEditKind::InsertAfter {
old_text: "
fn foo() {
}
"
.unindent(),
new_text: "
fn qux() {
// todo
}
"
.unindent(),
description: "implement qux".into(),
},
],
"
fn bar() {
qux();
}
fn foo() {
bar();
}
fn qux() {
// todo
}
"
.unindent(),
cx,
);
// Correctly indent new text when replacing multiple adjacent indented blocks.
assert_edits(
"
impl Numbers {
fn one() {
1
}
fn two() {
2
}
fn three() {
3
}
}
"
.unindent(),
vec![
AssistantEditKind::Update {
old_text: "
fn one() {
1
}
"
.unindent(),
new_text: "
fn one() {
101
}
"
.unindent(),
description: "pick better number".into(),
},
AssistantEditKind::Update {
old_text: "
fn two() {
2
}
"
.unindent(),
new_text: "
fn two() {
102
}
"
.unindent(),
description: "pick better number".into(),
},
AssistantEditKind::Update {
old_text: "
fn three() {
3
}
"
.unindent(),
new_text: "
fn three() {
103
}
"
.unindent(),
description: "pick better number".into(),
},
],
"
impl Numbers {
fn one() {
101
}
fn two() {
102
}
fn three() {
103
}
}
"
.unindent(),
cx,
);
}
#[track_caller]

View File

@@ -1,8 +1,7 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
@@ -18,8 +17,6 @@ use ui::{BorrowAppContext, WindowContext};
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub struct AutoSlashCommandFeatureFlag;
impl FeatureFlag for AutoSlashCommandFeatureFlag {
@@ -95,7 +92,7 @@ impl SlashCommand for AutoCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};

View File

@@ -1,8 +1,6 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -125,7 +123,7 @@ impl SlashCommand for CargoWorkspaceSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let output = workspace.update(cx, |workspace, cx| {
let project = workspace.project().clone();
let fs = workspace.project().read(cx).fs().clone();

View File

@@ -1,7 +1,8 @@
use super::create_label_for_command;
use anyhow::{anyhow, Result};
use assistant_slash_command::{
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
SlashCommandOutputSection, SlashCommandResult,
SlashCommandOutputSection,
};
use collections::HashMap;
use context_servers::{
@@ -16,8 +17,6 @@ use text::LineEnding;
use ui::{IconName, SharedString};
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub struct ContextServerSlashCommand {
server_id: String,
prompt: Prompt,
@@ -129,7 +128,7 @@ impl SlashCommand for ContextServerSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let server_id = self.server_id.clone();
let prompt_name = self.prompt.name.clone();
@@ -146,28 +145,7 @@ impl SlashCommand for ContextServerSlashCommand {
return Err(anyhow!("Context server not initialized"));
};
let result = protocol.run_prompt(&prompt_name, prompt_args).await?;
// Check that there are only user roles
if result
.messages
.iter()
.any(|msg| !matches!(msg.role, context_servers::types::SamplingRole::User))
{
return Err(anyhow!(
"Prompt contains non-user roles, which is not supported"
));
}
// Extract text from user messages into a single prompt string
let mut prompt = result
.messages
.into_iter()
.filter_map(|msg| match msg.content {
context_servers::types::SamplingContent::Text { text } => Some(text),
_ => None,
})
.collect::<Vec<String>>()
.join("\n\n");
let mut prompt = result.prompt;
// We must normalize the line endings here, since servers might return CR characters.
LineEnding::normalize(&mut prompt);

View File

@@ -1,9 +1,7 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
@@ -50,7 +48,7 @@ impl SlashCommand for DefaultSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let store = PromptStore::global(cx);
cx.background_executor().spawn(async move {
let store = store.await?;

View File

@@ -2,7 +2,6 @@ use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use collections::HashSet;
use futures::future;
@@ -49,7 +48,7 @@ impl SlashCommand for DeltaSlashCommand {
workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let mut paths = HashSet::default();
let mut file_command_old_outputs = Vec::new();
let mut file_command_new_outputs = Vec::new();

View File

@@ -1,8 +1,6 @@
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
@@ -21,8 +19,6 @@ use util::paths::PathMatcher;
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
pub(crate) struct DiagnosticsSlashCommand;
impl DiagnosticsSlashCommand {
@@ -171,7 +167,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};

View File

@@ -6,7 +6,6 @@ use std::time::Duration;
use anyhow::{anyhow, bail, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, BackgroundExecutor, Model, Task, WeakView};
use indexed_docs::{
@@ -275,7 +274,7 @@ impl SlashCommand for DocsSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
if arguments.is_empty() {
return Task::ready(Err(anyhow!("missing an argument")));
};

View File

@@ -6,7 +6,6 @@ use std::sync::Arc;
use anyhow::{anyhow, bail, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use futures::AsyncReadExt;
use gpui::{Task, WeakView};
@@ -134,7 +133,7 @@ impl SlashCommand for FetchSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(argument) = arguments.first() else {
return Task::ready(Err(anyhow!("missing URL")));
};

View File

@@ -1,8 +1,6 @@
use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
AfterCompletion, ArgumentCompletion, SlashCommand, SlashCommandOutput,
SlashCommandOutputSection, SlashCommandResult,
};
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
@@ -18,8 +16,6 @@ use ui::prelude::*;
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::diagnostics_command::collect_buffer_diagnostics;
pub(crate) struct FileSlashCommand;
impl FileSlashCommand {
@@ -185,7 +181,7 @@ impl SlashCommand for FileSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -202,7 +198,7 @@ fn collect_files(
project: Model<Project>,
glob_inputs: &[String],
cx: &mut AppContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Ok(matchers) = glob_inputs
.into_iter()
.map(|glob_input| {

View File

@@ -4,7 +4,6 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use chrono::Local;
use gpui::{Task, WeakView};
@@ -49,7 +48,7 @@ impl SlashCommand for NowSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
_cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let now = Local::now();
let text = format!("Today is {now}.", now = now.to_rfc2822());
let range = 0..text.len();

View File

@@ -4,7 +4,7 @@ use super::{
};
use crate::PromptBuilder;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::{Anchor, CodeLabel, LspAdapterDelegate};
@@ -76,7 +76,7 @@ impl SlashCommand for ProjectSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let model_registry = LanguageModelRegistry::read_global(cx);
let current_model = model_registry.active_model();
let prompt_builder = self.prompt_builder.clone();

View File

@@ -1,9 +1,7 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
@@ -63,7 +61,7 @@ impl SlashCommand for PromptSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let title = arguments.to_owned().join(" ");
if title.trim().is_empty() {
return Task::ready(Err(anyhow!("missing prompt name")));

View File

@@ -1,8 +1,10 @@
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
use super::{
create_label_for_command,
file_command::{build_entry_output_section, codeblock_fence_for_path},
SlashCommand, SlashCommandOutput,
};
use anyhow::Result;
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
@@ -14,9 +16,6 @@ use std::{
use ui::{prelude::*, IconName};
use workspace::Workspace;
use crate::slash_command::create_label_for_command;
use crate::slash_command::file_command::{build_entry_output_section, codeblock_fence_for_path};
pub(crate) struct SearchSlashCommandFeatureFlag;
impl FeatureFlag for SearchSlashCommandFeatureFlag {
@@ -64,7 +63,7 @@ impl SlashCommand for SearchSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};

View File

@@ -1,8 +1,6 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use editor::Editor;
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
@@ -48,7 +46,7 @@ impl SlashCommand for OutlineSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let output = workspace.update(cx, |workspace, cx| {
let Some(active_item) = workspace.active_item(cx) else {
return Task::ready(Err(anyhow!("no active tab")));

View File

@@ -1,8 +1,6 @@
use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
use anyhow::{Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
@@ -16,8 +14,6 @@ use ui::{ActiveTheme, WindowContext};
use util::ResultExt;
use workspace::Workspace;
use crate::slash_command::file_command::append_buffer_to_output;
pub(crate) struct TabSlashCommand;
const ALL_TABS_COMPLETION_ITEM: &str = "all";
@@ -136,7 +132,7 @@ impl SlashCommand for TabSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let tab_items_search = tab_items_for_queries(
Some(workspace),
arguments,

View File

@@ -4,7 +4,6 @@ use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{AppContext, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
@@ -63,7 +62,7 @@ impl SlashCommand for TerminalSlashCommand {
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let Some(workspace) = workspace.upgrade() else {
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
};

View File

@@ -1,17 +1,17 @@
use std::sync::atomic::AtomicBool;
use crate::prompts::PromptBuilder;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use gpui::{Task, WeakView};
use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
use crate::prompts::PromptBuilder;
use workspace::Workspace;
pub(crate) struct WorkflowSlashCommand {
prompt_builder: Arc<PromptBuilder>,
@@ -60,7 +60,7 @@ impl SlashCommand for WorkflowSlashCommand {
_workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let prompt_builder = self.prompt_builder.clone();
cx.spawn(|_cx| async move {
let text = prompt_builder.generate_workflow_prompt()?;

View File

@@ -56,8 +56,6 @@ pub struct ArgumentCompletion {
pub replace_previous_arguments: bool,
}
pub type SlashCommandResult = Result<SlashCommandOutput>;
pub trait SlashCommand: 'static + Send + Sync {
fn name(&self) -> String;
fn label(&self, _cx: &AppContext) -> CodeLabel {
@@ -89,7 +87,7 @@ pub trait SlashCommand: 'static + Send + Sync {
// perhaps another kind of delegate is needed here.
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult>;
) -> Task<Result<SlashCommandOutput>>;
}
pub type RenderFoldPlaceholder = Arc<

View File

@@ -32,5 +32,4 @@ settings.workspace = true
smol.workspace = true
tempfile.workspace = true
util.workspace = true
which.workspace = true
workspace.workspace = true

View File

@@ -33,7 +33,6 @@ use std::{
};
use update_notification::UpdateNotification;
use util::ResultExt;
use which::which;
use workspace::notifications::NotificationId;
use workspace::Workspace;
@@ -474,39 +473,6 @@ impl AutoUpdater {
Ok(version_path)
}
pub async fn get_latest_remote_server_release_url(
os: &str,
arch: &str,
mut release_channel: ReleaseChannel,
cx: &mut AsyncAppContext,
) -> Result<(String, String)> {
let this = cx.update(|cx| {
cx.default_global::<GlobalAutoUpdate>()
.0
.clone()
.ok_or_else(|| anyhow!("auto-update not initialized"))
})??;
if release_channel == ReleaseChannel::Dev {
release_channel = ReleaseChannel::Nightly;
}
let release = Self::get_latest_release(
&this,
"zed-remote-server",
os,
arch,
Some(release_channel),
cx,
)
.await?;
let update_request_body = build_remote_server_update_request_body(cx)?;
let body = serde_json::to_string(&update_request_body)?;
Ok((release.url, body))
}
async fn get_latest_release(
this: &Model<Self>,
asset: &str,
@@ -594,12 +560,6 @@ impl AutoUpdater {
"linux" => Ok("zed.tar.gz"),
_ => Err(anyhow!("not supported: {:?}", OS)),
}?;
anyhow::ensure!(
which("rsync").is_ok(),
"Aborting. Could not find rsync which is required for auto-updates."
);
let downloaded_asset = temp_dir.path().join(filename);
download_release(&downloaded_asset, release, client, &cx).await?;
@@ -662,15 +622,6 @@ async fn download_remote_server_binary(
cx: &AsyncAppContext,
) -> Result<()> {
let mut target_file = File::create(&target_path).await?;
let update_request_body = build_remote_server_update_request_body(cx)?;
let request_body = AsyncBody::from(serde_json::to_string(&update_request_body)?);
let mut response = client.get(&release.url, request_body, true).await?;
smol::io::copy(response.body_mut(), &mut target_file).await?;
Ok(())
}
fn build_remote_server_update_request_body(cx: &AsyncAppContext) -> Result<UpdateRequestBody> {
let (installation_id, release_channel, telemetry_enabled, is_staff) = cx.update(|cx| {
let telemetry = Client::global(cx).telemetry().clone();
let is_staff = telemetry.is_staff();
@@ -686,14 +637,17 @@ fn build_remote_server_update_request_body(cx: &AsyncAppContext) -> Result<Updat
is_staff,
)
})?;
Ok(UpdateRequestBody {
let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
installation_id,
release_channel,
telemetry: telemetry_enabled,
is_staff,
destination: "remote",
})
})?);
let mut response = client.get(&release.url, request_body, true).await?;
smol::io::copy(response.body_mut(), &mut target_file).await?;
Ok(())
}
async fn download_release(

View File

@@ -11,8 +11,7 @@ CREATE TABLE "users" (
"metrics_id" TEXT,
"github_user_id" INTEGER NOT NULL,
"accepted_tos_at" TIMESTAMP WITHOUT TIME ZONE,
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE,
"custom_llm_monthly_allowance_in_cents" INTEGER
"github_user_created_at" TIMESTAMP WITHOUT TIME ZONE
);
CREATE UNIQUE INDEX "index_users_github_login" ON "users" ("github_login");
CREATE UNIQUE INDEX "index_invite_code_users" ON "users" ("invite_code");
@@ -79,10 +78,10 @@ CREATE TABLE "worktree_entries" (
"id" INTEGER NOT NULL,
"is_dir" BOOL NOT NULL,
"path" VARCHAR NOT NULL,
"canonical_path" TEXT,
"inode" INTEGER NOT NULL,
"mtime_seconds" INTEGER NOT NULL,
"mtime_nanos" INTEGER NOT NULL,
"is_symlink" BOOL NOT NULL,
"is_external" BOOL NOT NULL,
"is_ignored" BOOL NOT NULL,
"is_deleted" BOOL NOT NULL,

View File

@@ -1,2 +0,0 @@
ALTER TABLE worktree_entries ADD COLUMN canonical_path text;
ALTER TABLE worktree_entries ALTER COLUMN is_symlink SET DEFAULT false;

View File

@@ -1 +0,0 @@
alter table users add column custom_llm_monthly_allowance_in_cents integer;

View File

@@ -34,7 +34,7 @@ use crate::{
db::{billing_subscription::StripeSubscriptionStatus, UserId},
llm::db::LlmDatabase,
};
use crate::{AppState, Cents, Error, Result};
use crate::{AppState, Error, Result};
pub fn router() -> Router {
Router::new()
@@ -226,13 +226,6 @@ async fn create_billing_subscription(
))?
};
if app.db.has_active_billing_subscription(user.id).await? {
return Err(Error::http(
StatusCode::CONFLICT,
"user already has an active subscription".into(),
));
}
let customer_id =
if let Some(existing_customer) = app.db.get_billing_customer_by_user_id(user.id).await? {
CustomerId::from_str(&existing_customer.stripe_customer_id)
@@ -662,33 +655,6 @@ async fn handle_customer_subscription_event(
)
.await?;
} else {
// If the user already has an active billing subscription, ignore the
// event and return an `Ok` to signal that it was processed
// successfully.
//
// There is the possibility that this could cause us to not create a
// subscription in the following scenario:
//
// 1. User has an active subscription A
// 2. User cancels subscription A
// 3. User creates a new subscription B
// 4. We process the new subscription B before the cancellation of subscription A
// 5. User ends up with no subscriptions
//
// In theory this situation shouldn't arise as we try to process the events in the order they occur.
if app
.db
.has_active_billing_subscription(billing_customer.user_id)
.await?
{
log::info!(
"user {user_id} already has an active subscription, skipping creation of subscription {subscription_id}",
user_id = billing_customer.user_id,
subscription_id = subscription.id
);
return Ok(());
}
app.db
.create_billing_subscription(&CreateBillingSubscriptionParams {
billing_customer_id: billing_customer.id,
@@ -714,9 +680,7 @@ struct GetMonthlySpendParams {
#[derive(Debug, Serialize)]
struct GetMonthlySpendResponse {
monthly_free_tier_spend_in_cents: u32,
monthly_free_tier_allowance_in_cents: u32,
monthly_spend_in_cents: u32,
monthly_spend_in_cents: i32,
}
async fn get_monthly_spend(
@@ -736,22 +700,13 @@ async fn get_monthly_spend(
));
};
let free_tier = user
.custom_llm_monthly_allowance_in_cents
.map(|allowance| Cents(allowance as u32))
.unwrap_or(FREE_TIER_MONTHLY_SPENDING_LIMIT);
let spending_for_month = llm_db
let monthly_spend = llm_db
.get_user_spending_for_month(user.id, Utc::now())
.await?;
let free_tier_spend = Cents::min(spending_for_month, free_tier);
let monthly_spend = spending_for_month.saturating_sub(free_tier);
.await?
.saturating_sub(FREE_TIER_MONTHLY_SPENDING_LIMIT);
Ok(Json(GetMonthlySpendResponse {
monthly_free_tier_spend_in_cents: free_tier_spend.0,
monthly_free_tier_allowance_in_cents: free_tier.0,
monthly_spend_in_cents: monthly_spend.0,
monthly_spend_in_cents: monthly_spend.0 as i32,
}))
}

View File

@@ -317,7 +317,7 @@ impl Database {
inode: ActiveValue::set(entry.inode as i64),
mtime_seconds: ActiveValue::set(mtime.seconds as i64),
mtime_nanos: ActiveValue::set(mtime.nanos as i32),
canonical_path: ActiveValue::set(entry.canonical_path.clone()),
is_symlink: ActiveValue::set(entry.is_symlink),
is_ignored: ActiveValue::set(entry.is_ignored),
is_external: ActiveValue::set(entry.is_external),
git_status: ActiveValue::set(entry.git_status.map(|status| status as i64)),
@@ -338,7 +338,7 @@ impl Database {
worktree_entry::Column::Inode,
worktree_entry::Column::MtimeSeconds,
worktree_entry::Column::MtimeNanos,
worktree_entry::Column::CanonicalPath,
worktree_entry::Column::IsSymlink,
worktree_entry::Column::IsIgnored,
worktree_entry::Column::GitStatus,
worktree_entry::Column::ScanId,
@@ -735,7 +735,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
canonical_path: db_entry.canonical_path,
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -659,7 +659,7 @@ impl Database {
seconds: db_entry.mtime_seconds as u64,
nanos: db_entry.mtime_nanos as u32,
}),
canonical_path: db_entry.canonical_path,
is_symlink: db_entry.is_symlink,
is_ignored: db_entry.is_ignored,
is_external: db_entry.is_external,
git_status: db_entry.git_status.map(|status| status as i32),

View File

@@ -21,7 +21,6 @@ pub struct Model {
pub metrics_id: Uuid,
pub created_at: NaiveDateTime,
pub accepted_tos_at: Option<NaiveDateTime>,
pub custom_llm_monthly_allowance_in_cents: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -16,12 +16,12 @@ pub struct Model {
pub mtime_seconds: i64,
pub mtime_nanos: i32,
pub git_status: Option<i64>,
pub is_symlink: bool,
pub is_ignored: bool,
pub is_external: bool,
pub is_deleted: bool,
pub scan_id: i64,
pub is_fifo: bool,
pub canonical_path: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]

View File

@@ -198,6 +198,10 @@ impl Config {
}
}
pub fn is_llm_billing_enabled(&self) -> bool {
self.stripe_api_key.is_some()
}
#[cfg(test)]
pub fn test() -> Self {
Self {

View File

@@ -459,27 +459,30 @@ async fn check_usage_limit(
Utc::now(),
)
.await?;
let free_tier = claims.free_tier_monthly_spending_limit();
if usage.spending_this_month >= free_tier {
if !claims.has_llm_subscription {
return Err(Error::http(
StatusCode::PAYMENT_REQUIRED,
"Maximum spending limit reached for this month.".to_string(),
));
}
if state.config.is_llm_billing_enabled() {
if usage.spending_this_month >= FREE_TIER_MONTHLY_SPENDING_LIMIT {
if !claims.has_llm_subscription {
return Err(Error::http(
StatusCode::PAYMENT_REQUIRED,
"Maximum spending limit reached for this month.".to_string(),
));
}
if (usage.spending_this_month - free_tier) >= Cents(claims.max_monthly_spend_in_cents) {
return Err(Error::Http(
StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(),
[(
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
HeaderValue::from_static("true"),
)]
.into_iter()
.collect(),
));
if (usage.spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT)
>= Cents(claims.max_monthly_spend_in_cents)
{
return Err(Error::Http(
StatusCode::FORBIDDEN,
"Maximum spending limit reached for this month.".to_string(),
[(
HeaderName::from_static(MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME),
HeaderValue::from_static("true"),
)]
.into_iter()
.collect(),
));
}
}
}
@@ -624,6 +627,7 @@ where
impl<S> Drop for TokenCountingStream<S> {
fn drop(&mut self) {
let state = self.state.clone();
let is_llm_billing_enabled = state.config.is_llm_billing_enabled();
let claims = self.claims.clone();
let provider = self.provider;
let model = std::mem::take(&mut self.model);
@@ -637,9 +641,15 @@ impl<S> Drop for TokenCountingStream<S> {
provider,
&model,
tokens,
claims.has_llm_subscription,
// We're passing `false` here if LLM billing is not enabled
// so that we don't write any records to the
// `billing_events` table until we're ready to bill users.
if is_llm_billing_enabled {
claims.has_llm_subscription
} else {
false
},
Cents(claims.max_monthly_spend_in_cents),
claims.free_tier_monthly_spending_limit(),
Utc::now(),
)
.await

View File

@@ -1,5 +1,5 @@
use crate::db::UserId;
use crate::llm::Cents;
use crate::{db::UserId, llm::FREE_TIER_MONTHLY_SPENDING_LIMIT};
use chrono::{Datelike, Duration};
use futures::StreamExt as _;
use rpc::LanguageModelProvider;
@@ -299,7 +299,6 @@ impl LlmDatabase {
tokens: TokenUsage,
has_llm_subscription: bool,
max_monthly_spend: Cents,
free_tier_monthly_spending_limit: Cents,
now: DateTimeUtc,
) -> Result<Usage> {
self.transaction(|tx| async move {
@@ -411,9 +410,9 @@ impl LlmDatabase {
);
if !is_staff
&& spending_this_month > free_tier_monthly_spending_limit
&& spending_this_month > FREE_TIER_MONTHLY_SPENDING_LIMIT
&& has_llm_subscription
&& (spending_this_month - free_tier_monthly_spending_limit) <= max_monthly_spend
&& (spending_this_month - FREE_TIER_MONTHLY_SPENDING_LIMIT) <= max_monthly_spend
{
billing_event::ActiveModel {
id: ActiveValue::not_set(),

View File

@@ -66,7 +66,6 @@ async fn test_billing_limit_exceeded(db: &mut LlmDatabase) {
usage,
true,
max_monthly_spend,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -104,7 +103,6 @@ async fn test_billing_limit_exceeded(db: &mut LlmDatabase) {
usage_2,
true,
max_monthly_spend,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -134,7 +132,6 @@ async fn test_billing_limit_exceeded(db: &mut LlmDatabase) {
model,
usage_exceeding,
true,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
max_monthly_spend,
now,
)

View File

@@ -1,4 +1,3 @@
use crate::llm::FREE_TIER_MONTHLY_SPENDING_LIMIT;
use crate::{
db::UserId,
llm::db::{
@@ -50,7 +49,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -70,7 +68,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -127,7 +124,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -184,7 +180,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -227,7 +222,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await
@@ -265,7 +259,6 @@ async fn test_tracking_usage(db: &mut LlmDatabase) {
},
false,
Cents::ZERO,
FREE_TIER_MONTHLY_SPENDING_LIMIT,
now,
)
.await

View File

@@ -1,7 +1,8 @@
use crate::db::user;
use crate::llm::{DEFAULT_MAX_MONTHLY_SPEND, FREE_TIER_MONTHLY_SPENDING_LIMIT};
use crate::Cents;
use crate::{db::billing_preference, Config};
use crate::llm::DEFAULT_MAX_MONTHLY_SPEND;
use crate::{
db::{billing_preference, UserId},
Config,
};
use anyhow::{anyhow, Result};
use chrono::Utc;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
@@ -21,7 +22,6 @@ pub struct LlmTokenClaims {
pub has_llm_closed_beta_feature_flag: bool,
pub has_llm_subscription: bool,
pub max_monthly_spend_in_cents: u32,
pub custom_llm_monthly_allowance_in_cents: Option<u32>,
pub plan: rpc::proto::Plan,
}
@@ -30,7 +30,8 @@ const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
impl LlmTokenClaims {
#[allow(clippy::too_many_arguments)]
pub fn create(
user: &user::Model,
user_id: UserId,
github_user_login: String,
is_staff: bool,
billing_preferences: Option<billing_preference::Model>,
has_llm_closed_beta_feature_flag: bool,
@@ -48,8 +49,8 @@ impl LlmTokenClaims {
iat: now.timestamp() as u64,
exp: (now + LLM_TOKEN_LIFETIME).timestamp() as u64,
jti: uuid::Uuid::new_v4().to_string(),
user_id: user.id.to_proto(),
github_user_login: user.github_login.clone(),
user_id: user_id.to_proto(),
github_user_login,
is_staff,
has_llm_closed_beta_feature_flag,
has_llm_subscription,
@@ -57,9 +58,6 @@ impl LlmTokenClaims {
.map_or(DEFAULT_MAX_MONTHLY_SPEND.0, |preferences| {
preferences.max_monthly_llm_usage_spending_in_cents as u32
}),
custom_llm_monthly_allowance_in_cents: user
.custom_llm_monthly_allowance_in_cents
.map(|allowance| allowance as u32),
plan,
};
@@ -91,12 +89,6 @@ impl LlmTokenClaims {
}
}
}
pub fn free_tier_monthly_spending_limit(&self) -> Cents {
self.custom_llm_monthly_allowance_in_cents
.map(Cents)
.unwrap_or(FREE_TIER_MONTHLY_SPENDING_LIMIT)
}
}
#[derive(Error, Debug)]

View File

@@ -2237,7 +2237,7 @@ fn join_project_internal(
worktree_id: worktree.id,
path: settings_file.path,
content: Some(settings_file.content),
kind: Some(settings_file.kind.to_proto() as i32),
kind: Some(proto::update_user_settings::Kind::Settings.into()),
},
)?;
}
@@ -4930,7 +4930,8 @@ async fn get_llm_api_token(
let billing_preferences = db.get_billing_preferences(user.id).await?;
let token = LlmTokenClaims::create(
&user,
user.id,
user.github_login.clone(),
session.is_staff(),
billing_preferences,
has_llm_closed_beta_feature_flag,

View File

@@ -12,7 +12,6 @@ use editor::{
test::editor_test_context::{AssertionContextManager, EditorTestContext},
Editor,
};
use fs::Fs;
use futures::StreamExt;
use gpui::{TestAppContext, UpdateGlobal, VisualContext, VisualTestContext};
use indoc::indoc;
@@ -31,7 +30,7 @@ use serde_json::json;
use settings::SettingsStore;
use std::{
ops::Range,
path::{Path, PathBuf},
path::Path,
sync::{
atomic::{self, AtomicBool, AtomicUsize},
Arc,
@@ -61,7 +60,7 @@ async fn test_host_disconnect(
.fs()
.insert_tree(
"/a",
json!({
serde_json::json!({
"a.txt": "a-contents",
"b.txt": "b-contents",
}),
@@ -2153,295 +2152,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
});
}
#[gpui::test(iterations = 30)]
async fn test_collaborating_with_editorconfig(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(cx_a.executor()).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
let active_call_a = cx_a.read(ActiveCall::global);
cx_b.update(editor::init);
// Set up a fake language server.
client_a.language_registry().add(rust_lang());
client_a
.fs()
.insert_tree(
"/a",
json!({
"src": {
"main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
"other_mod": {
"other.rs": "pub fn foo() -> usize {\n 4\n}",
".editorconfig": "",
},
},
".editorconfig": "[*]\ntab_width = 2\n",
}),
)
.await;
let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
let project_id = active_call_a
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
.await
.unwrap();
let main_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_a = project_a
.update(cx_a, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_a = cx_a.add_empty_window();
let main_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(main_buffer_a, Some(project_a.clone()), cx));
let other_editor_a =
cx_a.new_view(|cx| Editor::for_buffer(other_buffer_a, Some(project_a), cx));
let mut main_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: main_editor_a,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_a = EditorTestContext {
cx: cx_a.clone(),
window: cx_a.handle(),
editor: other_editor_a,
assertion_cx: AssertionContextManager::new(),
};
// Join the project as client B.
let project_b = client_b.join_remote_project(project_id, cx_b).await;
let main_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/main.rs"), cx)
})
.await
.unwrap();
let other_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/other.rs"), cx)
})
.await
.unwrap();
let cx_b = cx_b.add_empty_window();
let main_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(main_buffer_b, Some(project_b.clone()), cx));
let other_editor_b =
cx_b.new_view(|cx| Editor::for_buffer(other_buffer_b, Some(project_b.clone()), cx));
let mut main_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: main_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let mut other_editor_cx_b = EditorTestContext {
cx: cx_b.clone(),
window: cx_b.handle(),
editor: other_editor_b,
assertion_cx: AssertionContextManager::new(),
};
let initial_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
let initial_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
let first_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
first_tabbed_main,
false,
);
let first_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
first_tabbed_other,
false,
);
client_a
.fs()
.atomic_write(
PathBuf::from("/a/src/.editorconfig"),
"[*]\ntab_width = 3\n".to_owned(),
)
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
let second_tabbed_main = indoc! {"
ˇmod other;
fn main() { let foo = other::foo(); }"};
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let second_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
second_tabbed_other,
false,
);
let editorconfig_buffer_b = project_b
.update(cx_b, |p, cx| {
p.open_buffer((worktree_id, "src/other_mod/.editorconfig"), cx)
})
.await
.unwrap();
editorconfig_buffer_b.update(cx_b, |buffer, cx| {
buffer.set_text("[*.rs]\ntab_width = 6\n", cx);
});
project_b
.update(cx_b, |project, cx| {
project.save_buffer(editorconfig_buffer_b.clone(), cx)
})
.await
.unwrap();
cx_a.run_until_parked();
cx_b.run_until_parked();
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
true,
);
tab_undo_assert(
&mut main_editor_cx_a,
&mut main_editor_cx_b,
initial_main,
second_tabbed_main,
false,
);
let third_tabbed_other = indoc! {"
ˇpub fn foo() -> usize {
4
}"};
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
true,
);
tab_undo_assert(
&mut other_editor_cx_a,
&mut other_editor_cx_b,
initial_other,
third_tabbed_other,
false,
);
}
#[track_caller]
fn tab_undo_assert(
cx_a: &mut EditorTestContext,
cx_b: &mut EditorTestContext,
expected_initial: &str,
expected_tabbed: &str,
a_tabs: bool,
) {
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.tab(&editor::actions::Tab, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_tabbed);
cx_b.assert_editor_state(expected_tabbed);
if a_tabs {
cx_a.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
} else {
cx_b.update_editor(|editor, cx| {
editor.undo(&editor::actions::Undo, cx);
});
}
cx_a.run_until_parked();
cx_b.run_until_parked();
cx_a.assert_editor_state(expected_initial);
cx_b.assert_editor_state(expected_initial);
}
fn extract_hint_labels(editor: &Editor) -> Vec<String> {
let mut labels = Vec::new();
for hint in editor.inlay_hint_cache().hints() {

View File

@@ -34,7 +34,7 @@ use project::{
};
use rand::prelude::*;
use serde_json::json;
use settings::SettingsStore;
use settings::{LocalSettingsKind, SettingsStore};
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -3328,8 +3328,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":2}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
]
)
});
@@ -3347,8 +3355,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("").into(), r#"{}"#.to_string()),
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(
Path::new("").into(),
LocalSettingsKind::Settings,
r#"{}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
]
)
});
@@ -3376,8 +3392,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(
Path::new("b").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":4}"#.to_string()
),
]
)
});
@@ -3407,7 +3431,11 @@ async fn test_local_settings(
store
.local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
&[(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"hard_tabs":true}"#.to_string()
),]
)
});
}

View File

@@ -3,7 +3,7 @@ use call::ActiveCall;
use fs::{FakeFs, Fs as _};
use gpui::{Context as _, TestAppContext};
use http_client::BlockedHttpClient;
use language::{language_settings::language_settings, LanguageRegistry};
use language::{language_settings::all_language_settings, LanguageRegistry};
use node_runtime::NodeRuntime;
use project::ProjectPath;
use remote::SshRemoteClient;
@@ -26,7 +26,7 @@ async fn test_sharing_an_ssh_remote_project(
.await;
// Set up project on remote FS
let (port, server_ssh) = SshRemoteClient::fake_server(cx_a, server_cx);
let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
let remote_fs = FakeFs::new(server_cx.executor());
remote_fs
.insert_tree(
@@ -67,7 +67,6 @@ async fn test_sharing_an_ssh_remote_project(
)
});
let client_ssh = SshRemoteClient::fake_client(port, cx_a).await;
let (project_a, worktree_id) = client_a
.build_ssh_project("/code/project1", client_ssh, cx_a)
.await;
@@ -135,7 +134,9 @@ async fn test_sharing_an_ssh_remote_project(
cx_b.read(|cx| {
let file = buffer_b.read(cx).file();
assert_eq!(
language_settings(Some("Rust".into()), file, cx).language_servers,
all_language_settings(file, cx)
.language(Some(&("Rust".into())))
.language_servers,
["override-rust-analyzer".to_string()]
)
});

View File

@@ -11,7 +11,7 @@ use collections::HashMap;
use crate::client::Client;
use crate::types;
const PROTOCOL_VERSION: &str = "2024-10-07";
const PROTOCOL_VERSION: u32 = 1;
pub struct ModelContextProtocol {
inner: Client,
@@ -22,19 +22,12 @@ impl ModelContextProtocol {
Self { inner }
}
fn supported_protocols() -> Vec<types::ProtocolVersion> {
vec![
types::ProtocolVersion::VersionString(PROTOCOL_VERSION.to_string()),
types::ProtocolVersion::VersionNumber(1),
]
}
pub async fn initialize(
self,
client_info: types::Implementation,
) -> Result<InitializedContextServerProtocol> {
let params = types::InitializeParams {
protocol_version: types::ProtocolVersion::VersionString(PROTOCOL_VERSION.to_string()),
protocol_version: PROTOCOL_VERSION,
capabilities: types::ClientCapabilities {
experimental: None,
sampling: None,
@@ -47,13 +40,6 @@ impl ModelContextProtocol {
.request(types::RequestType::Initialize.as_str(), params)
.await?;
if !Self::supported_protocols().contains(&response.protocol_version) {
return Err(anyhow::anyhow!(
"Unsupported protocol version: {:?}",
response.protocol_version
));
}
log::trace!("mcp server info {:?}", response.server_info);
self.inner.notify(

View File

@@ -36,17 +36,10 @@ impl RequestType {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ProtocolVersion {
VersionString(String),
VersionNumber(u32),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeParams {
pub protocol_version: ProtocolVersion,
pub protocol_version: u32,
pub capabilities: ClientCapabilities,
pub client_info: Implementation,
}
@@ -138,7 +131,7 @@ pub struct CompletionArgument {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResponse {
pub protocol_version: ProtocolVersion,
pub protocol_version: u32,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
}
@@ -152,9 +145,10 @@ pub struct ResourcesReadResponse {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourcesListResponse {
pub resources: Vec<Resource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
pub resource_templates: Option<Vec<ResourceTemplate>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<Vec<Resource>>,
}
#[derive(Debug, Serialize, Deserialize)]
@@ -185,15 +179,13 @@ pub enum SamplingContent {
pub struct PromptsGetResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub messages: Vec<SamplingMessage>,
pub prompt: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PromptsListResponse {
pub prompts: Vec<Prompt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
}
#[derive(Debug, Deserialize)]

View File

@@ -864,11 +864,7 @@ impl Copilot {
let buffer = buffer.read(cx);
let uri = registered_buffer.uri.clone();
let position = position.to_point_utf16(buffer);
let settings = language_settings(
buffer.language_at(position).map(|l| l.name()),
buffer.file(),
cx,
);
let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx);
let tab_size = settings.tab_size;
let hard_tabs = settings.hard_tabs;
let relative_path = buffer

View File

@@ -77,7 +77,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
let file = buffer.file();
let language = buffer.language_at(cursor_position);
let settings = all_language_settings(file, cx);
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
}
fn refresh(
@@ -209,7 +209,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
) {
let settings = AllLanguageSettings::get_global(cx);
let copilot_enabled = settings.inline_completions_enabled(None, None, cx);
let copilot_enabled = settings.inline_completions_enabled(None, None);
if !copilot_enabled {
return;

View File

@@ -962,6 +962,7 @@ fn random_diagnostic(
const FILE_HEADER: &str = "file header";
const EXCERPT_HEADER: &str = "excerpt header";
const EXCERPT_FOOTER: &str = "excerpt footer";
fn editor_blocks(
editor: &View<Editor>,
@@ -997,7 +998,7 @@ fn editor_blocks(
.ok()?
}
Block::ExcerptBoundary {
Block::ExcerptHeader {
starts_new_buffer, ..
} => {
if *starts_new_buffer {
@@ -1006,6 +1007,7 @@ fn editor_blocks(
EXCERPT_HEADER.into()
}
}
Block::ExcerptFooter { .. } => EXCERPT_FOOTER.into(),
};
Some((row, name))

View File

@@ -81,7 +81,6 @@ ui.workspace = true
url.workspace = true
util.workspace = true
workspace.workspace = true
unicode-segmentation.workspace = true
[dev-dependencies]
ctor.workspace = true

View File

@@ -8,7 +8,7 @@
//! of several smaller structures that form a hierarchy (starting at the bottom):
//! - [`InlayMap`] that decides where the [`Inlay`]s should be displayed.
//! - [`FoldMap`] that decides where the fold indicators should be; it also tracks parts of a source file that are currently folded.
//! - [`CharMap`] that replaces tabs and non-printable characters
//! - [`TabMap`] that keeps track of hard tabs in a buffer.
//! - [`WrapMap`] that handles soft wrapping.
//! - [`BlockMap`] that tracks custom blocks such as diagnostics that should be displayed within buffer.
//! - [`DisplayMap`] that adds background highlights to the regions of text.
@@ -18,11 +18,10 @@
//! [EditorElement]: crate::element::EditorElement
mod block_map;
mod char_map;
mod crease_map;
mod fold_map;
mod inlay_map;
mod invisibles;
mod tab_map;
mod wrap_map;
use crate::{
@@ -33,7 +32,6 @@ pub use block_map::{
BlockMap, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
};
use block_map::{BlockRow, BlockSnapshot};
use char_map::{CharMap, CharSnapshot};
use collections::{HashMap, HashSet};
pub use crease_map::*;
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
@@ -44,7 +42,6 @@ use gpui::{
pub(crate) use inlay_map::Inlay;
use inlay_map::{InlayMap, InlaySnapshot};
pub use inlay_map::{InlayOffset, InlayPoint};
pub use invisibles::is_invisible;
use language::{
language_settings::language_settings, ChunkRenderer, OffsetUtf16, Point,
Subscription as BufferSubscription,
@@ -64,9 +61,9 @@ use std::{
sync::Arc,
};
use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot};
use text::LineIndent;
use ui::{px, WindowContext};
use unicode_segmentation::UnicodeSegmentation;
use ui::WindowContext;
use wrap_map::{WrapMap, WrapSnapshot};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -97,7 +94,7 @@ pub struct DisplayMap {
/// Decides where the fold indicators should be and tracks parts of a source file that are currently folded.
fold_map: FoldMap,
/// Keeps track of hard tabs in a buffer.
char_map: CharMap,
tab_map: TabMap,
/// Handles soft wrapping.
wrap_map: Model<WrapMap>,
/// Tracks custom blocks such as diagnostics that should be displayed within buffer.
@@ -134,7 +131,7 @@ impl DisplayMap {
let crease_map = CreaseMap::new(&buffer_snapshot);
let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot);
let (fold_map, snapshot) = FoldMap::new(snapshot);
let (char_map, snapshot) = CharMap::new(snapshot, tab_size);
let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx);
let block_map = BlockMap::new(
snapshot,
@@ -151,7 +148,7 @@ impl DisplayMap {
buffer_subscription,
fold_map,
inlay_map,
char_map,
tab_map,
wrap_map,
block_map,
crease_map,
@@ -169,17 +166,17 @@ impl DisplayMap {
let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (char_snapshot, edits) = self.char_map.sync(fold_snapshot.clone(), 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(char_snapshot.clone(), edits, cx));
.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,
char_snapshot,
tab_snapshot,
wrap_snapshot,
block_snapshot,
crease_snapshot: self.crease_map.snapshot(),
@@ -215,13 +212,13 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = fold_map.fold(ranges);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -239,13 +236,13 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -280,7 +277,7 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -298,7 +295,7 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -316,7 +313,7 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -334,7 +331,7 @@ impl DisplayMap {
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -410,7 +407,7 @@ impl DisplayMap {
let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -418,7 +415,7 @@ impl DisplayMap {
let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits) = self.char_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -426,12 +423,11 @@ impl DisplayMap {
}
fn tab_size(buffer: &Model<MultiBuffer>, cx: &mut ModelContext<Self>) -> NonZeroU32 {
let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx));
let language = buffer
.and_then(|buffer| buffer.language())
.map(|l| l.name());
let file = buffer.and_then(|buffer| buffer.file());
language_settings(language, file, cx).tab_size
.read(cx)
.as_singleton()
.and_then(|buffer| buffer.read(cx).language());
language_settings(language, None, cx).tab_size
}
#[cfg(test)]
@@ -470,7 +466,7 @@ pub struct DisplaySnapshot {
pub fold_snapshot: FoldSnapshot,
pub crease_snapshot: CreaseSnapshot,
inlay_snapshot: InlaySnapshot,
char_snapshot: CharSnapshot,
tab_snapshot: TabSnapshot,
wrap_snapshot: WrapSnapshot,
block_snapshot: BlockSnapshot,
text_highlights: TextHighlights,
@@ -570,8 +566,8 @@ impl DisplaySnapshot {
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 char_point = self.char_snapshot.to_char_point(fold_point);
let wrap_point = self.wrap_snapshot.char_point_to_wrap_point(char_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)
}
@@ -599,21 +595,21 @@ impl DisplaySnapshot {
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);
let char_point = self.wrap_snapshot.to_char_point(wrap_point);
let fold_point = self.char_snapshot.to_fold_point(char_point, bias).0;
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);
let char_point = self.wrap_snapshot.to_char_point(wrap_point);
self.char_snapshot.to_fold_point(char_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 char_point = self.char_snapshot.to_char_point(fold_point);
let wrap_point = self.wrap_snapshot.char_point_to_wrap_point(char_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)
}
@@ -691,23 +687,6 @@ impl DisplaySnapshot {
}
}
if chunk.is_invisible {
let invisible_highlight = HighlightStyle {
background_color: Some(editor_style.status.hint_background),
underline: Some(UnderlineStyle {
color: Some(editor_style.status.hint),
thickness: px(1.),
wavy: false,
}),
..Default::default()
};
if let Some(highlight_style) = highlight_style.as_mut() {
highlight_style.highlight(invisible_highlight);
} else {
highlight_style = Some(invisible_highlight);
}
}
let mut diagnostic_highlight = HighlightStyle::default();
if chunk.is_unnecessary {
@@ -804,11 +783,12 @@ impl DisplaySnapshot {
layout_line.closest_index_for_x(x) as u32
}
pub fn grapheme_at(&self, mut point: DisplayPoint) -> Option<String> {
pub fn display_chars_at(
&self,
mut point: DisplayPoint,
) -> impl Iterator<Item = (char, DisplayPoint)> + '_ {
point = DisplayPoint(self.block_snapshot.clip_point(point.0, Bias::Left));
let chars = self
.text_chunks(point.row())
self.text_chunks(point.row())
.flat_map(str::chars)
.skip_while({
let mut column = 0;
@@ -818,21 +798,16 @@ impl DisplaySnapshot {
!at_point
}
})
.take_while({
let mut prev = false;
move |char| {
let now = char.is_ascii();
let end = char.is_ascii() && (char.is_ascii_whitespace() || prev);
prev = now;
!end
.map(move |ch| {
let result = (ch, point);
if ch == '\n' {
*point.row_mut() += 1;
*point.column_mut() = 0;
} else {
*point.column_mut() += ch.len_utf8() as u32;
}
});
chars
.collect::<String>()
.graphemes(true)
.next()
.map(|s| s.to_owned())
result
})
}
pub fn buffer_chars_at(&self, mut offset: usize) -> impl Iterator<Item = (char, usize)> + '_ {
@@ -1144,8 +1119,8 @@ impl DisplayPoint {
pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
let wrap_point = map.block_snapshot.to_wrap_point(self.0);
let char_point = map.wrap_snapshot.to_char_point(wrap_point);
let fold_point = map.char_snapshot.to_fold_point(char_point, bias).0;
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))
@@ -1252,7 +1227,7 @@ pub mod tests {
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!("char text: {:?}", snapshot.char_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());
@@ -1369,7 +1344,7 @@ pub mod tests {
fold_count = snapshot.fold_count();
log::info!("buffer text: {:?}", snapshot.buffer_snapshot.text());
log::info!("fold text: {:?}", snapshot.fold_snapshot.text());
log::info!("char text: {:?}", snapshot.char_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());

View File

@@ -5,8 +5,8 @@ use super::{
use crate::{EditorStyle, GutterDimensions};
use collections::{Bound, HashMap, HashSet};
use gpui::{AnyElement, EntityId, Pixels, WindowContext};
use language::{Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptInfo, MultiBufferRow, ToPoint as _};
use language::{BufferSnapshot, Chunk, Patch, Point};
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, MultiBufferRow, ToPoint as _};
use parking_lot::Mutex;
use std::{
cell::RefCell,
@@ -128,17 +128,26 @@ pub struct BlockContext<'a, 'b> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockId {
Custom(CustomBlockId),
ExcerptBoundary(Option<ExcerptId>),
ExcerptHeader(ExcerptId),
ExcerptFooter(ExcerptId),
}
impl From<BlockId> for EntityId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => EntityId::from(id as u64),
BlockId::ExcerptHeader(id) => id.into(),
BlockId::ExcerptFooter(id) => id.into(),
}
}
}
impl From<BlockId> for ElementId {
fn from(value: BlockId) -> Self {
match value {
BlockId::Custom(CustomBlockId(id)) => ("Block", id).into(),
BlockId::ExcerptBoundary(next_excerpt) => match next_excerpt {
Some(id) => ("ExcerptBoundary", EntityId::from(id)).into(),
None => "LastExcerptBoundary".into(),
},
BlockId::ExcerptHeader(id) => ("ExcerptHeader", EntityId::from(id)).into(),
BlockId::ExcerptFooter(id) => ("ExcerptFooter", EntityId::from(id)).into(),
}
}
}
@@ -147,7 +156,8 @@ impl std::fmt::Display for BlockId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(id) => write!(f, "Block({id:?})"),
Self::ExcerptBoundary(id) => write!(f, "ExcerptHeader({id:?})"),
Self::ExcerptHeader(id) => write!(f, "ExcerptHeader({id:?})"),
Self::ExcerptFooter(id) => write!(f, "ExcerptFooter({id:?})"),
}
}
}
@@ -167,7 +177,8 @@ struct Transform {
pub(crate) enum BlockType {
Custom(CustomBlockId),
ExcerptBoundary,
Header,
Footer,
}
pub(crate) trait BlockLike {
@@ -180,20 +191,27 @@ pub(crate) trait BlockLike {
#[derive(Clone)]
pub enum Block {
Custom(Arc<CustomBlock>),
ExcerptBoundary {
prev_excerpt: Option<ExcerptInfo>,
next_excerpt: Option<ExcerptInfo>,
ExcerptHeader {
id: ExcerptId,
buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>,
height: u32,
starts_new_buffer: bool,
show_excerpt_controls: bool,
},
ExcerptFooter {
id: ExcerptId,
disposition: BlockDisposition,
height: u32,
},
}
impl BlockLike for Block {
fn block_type(&self) -> BlockType {
match self {
Block::Custom(block) => BlockType::Custom(block.id),
Block::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
Block::ExcerptHeader { .. } => BlockType::Header,
Block::ExcerptFooter { .. } => BlockType::Footer,
}
}
@@ -204,7 +222,8 @@ impl BlockLike for Block {
fn priority(&self) -> usize {
match self {
Block::Custom(block) => block.priority,
Block::ExcerptBoundary { .. } => usize::MAX,
Block::ExcerptHeader { .. } => usize::MAX,
Block::ExcerptFooter { .. } => 0,
}
}
}
@@ -213,36 +232,32 @@ impl Block {
pub fn id(&self) -> BlockId {
match self {
Block::Custom(block) => BlockId::Custom(block.id),
Block::ExcerptBoundary { next_excerpt, .. } => {
BlockId::ExcerptBoundary(next_excerpt.as_ref().map(|info| info.id))
}
Block::ExcerptHeader { id, .. } => BlockId::ExcerptHeader(*id),
Block::ExcerptFooter { id, .. } => BlockId::ExcerptFooter(*id),
}
}
fn disposition(&self) -> BlockDisposition {
match self {
Block::Custom(block) => block.disposition,
Block::ExcerptBoundary { next_excerpt, .. } => {
if next_excerpt.is_some() {
BlockDisposition::Above
} else {
BlockDisposition::Below
}
}
Block::ExcerptHeader { .. } => BlockDisposition::Above,
Block::ExcerptFooter { disposition, .. } => *disposition,
}
}
pub fn height(&self) -> u32 {
match self {
Block::Custom(block) => block.height,
Block::ExcerptBoundary { height, .. } => *height,
Block::ExcerptHeader { height, .. } => *height,
Block::ExcerptFooter { height, .. } => *height,
}
}
pub fn style(&self) -> BlockStyle {
match self {
Block::Custom(block) => block.style,
Block::ExcerptBoundary { .. } => BlockStyle::Sticky,
Block::ExcerptHeader { .. } => BlockStyle::Sticky,
Block::ExcerptFooter { .. } => BlockStyle::Sticky,
}
}
}
@@ -251,17 +266,24 @@ impl Debug for Block {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Custom(block) => f.debug_struct("Custom").field("block", block).finish(),
Self::ExcerptBoundary {
Self::ExcerptHeader {
buffer,
starts_new_buffer,
next_excerpt,
prev_excerpt,
id,
..
} => f
.debug_struct("ExcerptBoundary")
.field("prev_excerpt", &prev_excerpt)
.field("next_excerpt", &next_excerpt)
.debug_struct("ExcerptHeader")
.field("id", &id)
.field("path", &buffer.file().map(|f| f.path()))
.field("starts_new_buffer", &starts_new_buffer)
.finish(),
Block::ExcerptFooter {
id, disposition, ..
} => f
.debug_struct("ExcerptFooter")
.field("id", &id)
.field("disposition", &disposition)
.finish(),
}
}
}
@@ -573,62 +595,66 @@ impl BlockMap {
{
buffer
.excerpt_boundaries_in_range(range)
.filter_map(move |excerpt_boundary| {
let wrap_row;
if excerpt_boundary.next.is_some() {
wrap_row = wrap_snapshot
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
.row();
} else {
wrap_row = wrap_snapshot
.make_wrap_point(
Point::new(
excerpt_boundary.row.0,
buffer.line_len(excerpt_boundary.row),
),
Bias::Left,
.flat_map(move |excerpt_boundary| {
let mut wrap_row = wrap_snapshot
.make_wrap_point(Point::new(excerpt_boundary.row.0, 0), Bias::Left)
.row();
[
show_excerpt_controls
.then(|| {
let disposition;
if excerpt_boundary.next.is_some() {
disposition = BlockDisposition::Above;
} else {
wrap_row = wrap_snapshot
.make_wrap_point(
Point::new(
excerpt_boundary.row.0,
buffer.line_len(excerpt_boundary.row),
),
Bias::Left,
)
.row();
disposition = BlockDisposition::Below;
}
excerpt_boundary.prev.as_ref().map(|prev| {
(
wrap_row,
Block::ExcerptFooter {
id: prev.id,
height: excerpt_footer_height,
disposition,
},
)
})
})
.flatten(),
excerpt_boundary.next.map(|next| {
let starts_new_buffer = excerpt_boundary
.prev
.map_or(true, |prev| prev.buffer_id != next.buffer_id);
(
wrap_row,
Block::ExcerptHeader {
id: next.id,
buffer: next.buffer,
range: next.range,
height: if starts_new_buffer {
buffer_header_height
} else {
excerpt_header_height
},
starts_new_buffer,
show_excerpt_controls,
},
)
.row();
}
let starts_new_buffer = match (&excerpt_boundary.prev, &excerpt_boundary.next) {
(_, None) => false,
(None, Some(_)) => true,
(Some(prev), Some(next)) => prev.buffer_id != next.buffer_id,
};
let mut height = 0;
if excerpt_boundary.prev.is_some() {
if show_excerpt_controls {
height += excerpt_footer_height;
}
}
if excerpt_boundary.next.is_some() {
if starts_new_buffer {
height += buffer_header_height;
if show_excerpt_controls {
height += excerpt_header_height;
}
} else {
height += excerpt_header_height;
}
}
if height == 0 {
return None;
}
Some((
wrap_row,
Block::ExcerptBoundary {
prev_excerpt: excerpt_boundary.prev,
next_excerpt: excerpt_boundary.next,
height,
starts_new_buffer,
show_excerpt_controls,
},
))
}),
]
})
.flatten()
}
pub(crate) fn sort_blocks<B: BlockLike>(blocks: &mut [(u32, B)]) {
@@ -639,9 +665,12 @@ impl BlockMap {
.disposition()
.cmp(&block_b.disposition())
.then_with(|| match ((block_a.block_type()), (block_b.block_type())) {
(BlockType::ExcerptBoundary, BlockType::ExcerptBoundary) => Ordering::Equal,
(BlockType::ExcerptBoundary, _) => Ordering::Less,
(_, BlockType::ExcerptBoundary) => Ordering::Greater,
(BlockType::Footer, BlockType::Footer) => Ordering::Equal,
(BlockType::Footer, _) => Ordering::Less,
(_, BlockType::Footer) => Ordering::Greater,
(BlockType::Header, BlockType::Header) => Ordering::Equal,
(BlockType::Header, _) => Ordering::Less,
(_, BlockType::Header) => Ordering::Greater,
(BlockType::Custom(a_id), BlockType::Custom(b_id)) => block_b
.priority()
.cmp(&block_a.priority())
@@ -1016,19 +1045,33 @@ impl BlockSnapshot {
let custom_block = self.custom_blocks_by_id.get(&custom_block_id)?;
Some(Block::Custom(custom_block.clone()))
}
BlockId::ExcerptBoundary(next_excerpt_id) => {
let wrap_point;
if let Some(next_excerpt_id) = next_excerpt_id {
let excerpt_range = buffer.range_for_excerpt::<Point>(next_excerpt_id)?;
wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left);
} else {
wrap_point = self
.wrap_snapshot
.make_wrap_point(buffer.max_point(), Bias::Left);
BlockId::ExcerptHeader(excerpt_id) => {
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
let wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.start, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
if let Some(block) = transform.block.as_ref() {
if block.id() == block_id {
return Some(block.clone());
}
} else if cursor.start().0 > WrapRow(wrap_point.row()) {
break;
}
cursor.next(&());
}
None
}
BlockId::ExcerptFooter(excerpt_id) => {
let excerpt_range = buffer.range_for_excerpt::<Point>(excerpt_id)?;
let wrap_point = self
.wrap_snapshot
.make_wrap_point(excerpt_range.end, Bias::Left);
let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(&());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Left, &());
while let Some(transform) = cursor.item() {
@@ -1421,11 +1464,11 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) {
mod tests {
use super::*;
use crate::display_map::{
char_map::CharMap, fold_map::FoldMap, inlay_map::InlayMap, wrap_map::WrapMap,
fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap,
};
use gpui::{div, font, px, AppContext, Context as _, Element};
use language::{Buffer, Capability};
use multi_buffer::{ExcerptRange, MultiBuffer};
use multi_buffer::MultiBuffer;
use rand::prelude::*;
use settings::SettingsStore;
use std::env;
@@ -1456,9 +1499,9 @@ mod tests {
let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut char_map, char_snapshot) = CharMap::new(fold_snapshot, 1.try_into().unwrap());
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), px(14.0), None, cx));
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
@@ -1609,10 +1652,10 @@ mod tests {
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot, subscription.consume().into_inner());
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (char_snapshot, tab_edits) =
char_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap());
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
wrap_map.sync(char_snapshot, tab_edits, cx)
wrap_map.sync(tab_snapshot, tab_edits, cx)
});
let snapshot = block_map.read(wraps_snapshot, wrap_edits);
assert_eq!(snapshot.text(), "aaa\n\nb!!!\n\n\nbb\nccc\nddd\n\n\n");
@@ -1672,9 +1715,8 @@ mod tests {
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) =
WrapMap::new(char_snapshot, font, font_size, Some(wrap_width), cx);
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx);
let block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 1);
let snapshot = block_map.read(wraps_snapshot, Default::default());
@@ -1682,20 +1724,22 @@ mod tests {
// Each excerpt has a header above and footer below. Excerpts are also *separated* by a newline.
assert_eq!(
snapshot.text(),
"\n\nBuff\ner 1\n\n\n\nBuff\ner 2\n\n\n\nBuff\ner 3\n"
"\nBuff\ner 1\n\n\nBuff\ner 2\n\n\nBuff\ner 3\n"
);
let blocks: Vec<_> = snapshot
.blocks_in_range(0..u32::MAX)
.map(|(row, block)| (row..row + block.height(), block.id()))
.map(|(row, block)| (row, block.id()))
.collect();
assert_eq!(
blocks,
vec![
(0..2, BlockId::ExcerptBoundary(Some(excerpt_ids[0]))), // path, header
(4..7, BlockId::ExcerptBoundary(Some(excerpt_ids[1]))), // footer, path, header
(9..12, BlockId::ExcerptBoundary(Some(excerpt_ids[2]))), // footer, path, header
(14..15, BlockId::ExcerptBoundary(None)), // footer
(0, BlockId::ExcerptHeader(excerpt_ids[0])),
(3, BlockId::ExcerptFooter(excerpt_ids[0])),
(4, BlockId::ExcerptHeader(excerpt_ids[1])),
(7, BlockId::ExcerptFooter(excerpt_ids[1])),
(8, BlockId::ExcerptHeader(excerpt_ids[2])),
(11, BlockId::ExcerptFooter(excerpt_ids[2]))
]
);
}
@@ -1711,9 +1755,9 @@ mod tests {
let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_char_map, char_snapshot) = CharMap::new(fold_snapshot, 1.try_into().unwrap());
let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap());
let (_wrap_map, wraps_snapshot) =
cx.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), px(14.0), None, cx));
cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx));
let mut block_map = BlockMap::new(wraps_snapshot.clone(), false, 1, 1, 0);
let mut writer = block_map.write(wraps_snapshot.clone(), Default::default());
@@ -1816,15 +1860,9 @@ mod tests {
let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, wraps_snapshot) = cx.update(|cx| {
WrapMap::new(
char_snapshot,
font("Helvetica"),
px(14.0),
Some(px(60.)),
cx,
)
WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx)
});
let mut block_map = BlockMap::new(wraps_snapshot.clone(), true, 1, 1, 0);
@@ -1892,9 +1930,9 @@ mod tests {
let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx));
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (mut char_map, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
let (wrap_map, wraps_snapshot) = cx
.update(|cx| WrapMap::new(char_snapshot, font("Helvetica"), font_size, wrap_width, cx));
.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx));
let mut block_map = BlockMap::new(
wraps_snapshot,
true,
@@ -1951,10 +1989,10 @@ mod tests {
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), vec![]);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (char_snapshot, tab_edits) =
char_map.sync(fold_snapshot, fold_edits, tab_size);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
wrap_map.sync(char_snapshot, tab_edits, cx)
wrap_map.sync(tab_snapshot, tab_edits, cx)
});
let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
let block_ids =
@@ -1983,10 +2021,10 @@ mod tests {
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), vec![]);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (char_snapshot, tab_edits) =
char_map.sync(fold_snapshot, fold_edits, tab_size);
let (tab_snapshot, tab_edits) =
tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
wrap_map.sync(char_snapshot, tab_edits, cx)
wrap_map.sync(tab_snapshot, tab_edits, cx)
});
let mut block_map = block_map.write(wraps_snapshot, wrap_edits);
block_map.remove(block_ids_to_remove);
@@ -2006,9 +2044,9 @@ mod tests {
let (inlay_snapshot, inlay_edits) =
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (char_snapshot, tab_edits) = char_map.sync(fold_snapshot, fold_edits, tab_size);
let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
wrap_map.sync(char_snapshot, tab_edits, cx)
wrap_map.sync(tab_snapshot, tab_edits, cx)
});
let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits);
assert_eq!(
@@ -2091,10 +2129,7 @@ mod tests {
}
}
let soft_wrapped = wraps_snapshot
.to_char_point(WrapPoint::new(row, 0))
.column()
> 0;
let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0;
expected_buffer_rows.push(if soft_wrapped { None } else { buffer_row });
expected_text.push_str(input_line);
@@ -2248,10 +2283,13 @@ mod tests {
#[derive(Debug, Eq, PartialEq)]
enum ExpectedBlock {
ExcerptBoundary {
ExcerptHeader {
height: u32,
starts_new_buffer: bool,
is_last: bool,
},
ExcerptFooter {
height: u32,
disposition: BlockDisposition,
},
Custom {
disposition: BlockDisposition,
@@ -2265,7 +2303,8 @@ mod tests {
fn block_type(&self) -> BlockType {
match self {
ExpectedBlock::Custom { id, .. } => BlockType::Custom(*id),
ExpectedBlock::ExcerptBoundary { .. } => BlockType::ExcerptBoundary,
ExpectedBlock::ExcerptHeader { .. } => BlockType::Header,
ExpectedBlock::ExcerptFooter { .. } => BlockType::Footer,
}
}
@@ -2276,7 +2315,8 @@ mod tests {
fn priority(&self) -> usize {
match self {
ExpectedBlock::Custom { priority, .. } => *priority,
ExpectedBlock::ExcerptBoundary { .. } => usize::MAX,
ExpectedBlock::ExcerptHeader { .. } => usize::MAX,
ExpectedBlock::ExcerptFooter { .. } => 0,
}
}
}
@@ -2284,21 +2324,17 @@ mod tests {
impl ExpectedBlock {
fn height(&self) -> u32 {
match self {
ExpectedBlock::ExcerptBoundary { height, .. } => *height,
ExpectedBlock::ExcerptHeader { height, .. } => *height,
ExpectedBlock::Custom { height, .. } => *height,
ExpectedBlock::ExcerptFooter { height, .. } => *height,
}
}
fn disposition(&self) -> BlockDisposition {
match self {
ExpectedBlock::ExcerptBoundary { is_last, .. } => {
if *is_last {
BlockDisposition::Below
} else {
BlockDisposition::Above
}
}
ExpectedBlock::ExcerptHeader { .. } => BlockDisposition::Above,
ExpectedBlock::Custom { disposition, .. } => *disposition,
ExpectedBlock::ExcerptFooter { disposition, .. } => *disposition,
}
}
}
@@ -2312,15 +2348,21 @@ mod tests {
height: block.height,
priority: block.priority,
},
Block::ExcerptBoundary {
Block::ExcerptHeader {
height,
starts_new_buffer,
next_excerpt,
..
} => ExpectedBlock::ExcerptBoundary {
} => ExpectedBlock::ExcerptHeader {
height,
starts_new_buffer,
is_last: next_excerpt.is_none(),
},
Block::ExcerptFooter {
height,
disposition,
..
} => ExpectedBlock::ExcerptFooter {
height,
disposition,
},
}
}
@@ -2338,7 +2380,8 @@ mod tests {
fn as_custom(&self) -> Option<&CustomBlock> {
match self {
Block::Custom(block) => Some(block),
Block::ExcerptBoundary { .. } => None,
Block::ExcerptHeader { .. } => None,
Block::ExcerptFooter { .. } => None,
}
}
}

View File

@@ -1,157 +0,0 @@
use std::sync::LazyLock;
use collections::HashMap;
// Invisibility in a Unicode context is not well defined, so we have to guess.
//
// We highlight all ASCII control codes, and unicode whitespace because they are likely
// confused with a normal space (U+0020).
//
// We also highlight the handful of blank non-space characters:
// U+2800 BRAILLE PATTERN BLANK - Category: So
// U+115F HANGUL CHOSEONG FILLER - Category: Lo
// U+1160 HANGUL CHOSEONG FILLER - Category: Lo
// U+3164 HANGUL FILLER - Category: Lo
// U+FFA0 HALFWIDTH HANGUL FILLER - Category: Lo
// U+FFFC OBJECT REPLACEMENT CHARACTER - Category: So
//
// For the rest of Unicode, invisibility happens for two reasons:
// * A Format character (like a byte order mark or right-to-left override)
// * An invisible Nonspacing Mark character (like U+034F, or variation selectors)
//
// We don't consider unassigned codepoints invisible as the font renderer already shows
// a replacement character in that case (and there are a *lot* of them)
//
// Control characters are mostly fine to highlight; except:
// * U+E0020..=U+E007F are used in emoji flags. We don't highlight them right now, but we could if we tightened our heuristics.
// * U+200D is used to join characters. We highlight this but don't replace it. As our font system ignores mid-glyph highlights this mostly works to highlight unexpected uses.
//
// Nonspacing marks are handled like U+200D. This means that mid-glyph we ignore them, but
// probably causes issues with end-of-glyph usage.
//
// ref: https://invisible-characters.com
// ref: https://www.compart.com/en/unicode/category/Cf
// ref: https://gist.github.com/ConradIrwin/f759e1fc29267143c4c7895aa495dca5?h=1
// ref: https://unicode.org/Public/emoji/13.0/emoji-test.txt
// https://github.com/bits/UTF-8-Unicode-Test-Documents/blob/master/UTF-8_sequence_separated/utf8_sequence_0-0x10ffff_assigned_including-unprintable-asis.txt
pub fn is_invisible(c: char) -> bool {
if c <= '\u{1f}' {
c != '\t' && c != '\n' && c != '\r'
} else if c >= '\u{7f}' {
c <= '\u{9f}' || c.is_whitespace() || contains(c, &FORMAT) || contains(c, &OTHER)
} else {
false
}
}
pub(crate) fn replacement(c: char) -> Option<&'static str> {
if !is_invisible(c) {
return None;
}
if c <= '\x7f' {
REPLACEMENTS.get(&c).copied()
} else if contains(c, &PRESERVE) {
None
} else {
Some(" ")
}
}
const REPLACEMENTS: LazyLock<HashMap<char, &'static str>> = LazyLock::new(|| {
[
('\x00', ""),
('\x01', ""),
('\x02', ""),
('\x03', ""),
('\x04', ""),
('\x05', ""),
('\x06', ""),
('\x07', ""),
('\x08', ""),
('\x0B', ""),
('\x0C', ""),
('\x0D', ""),
('\x0E', ""),
('\x0F', ""),
('\x10', ""),
('\x11', ""),
('\x12', ""),
('\x13', ""),
('\x14', ""),
('\x15', ""),
('\x16', ""),
('\x17', ""),
('\x18', ""),
('\x19', ""),
('\x1A', ""),
('\x1B', ""),
('\x1C', ""),
('\x1D', ""),
('\x1E', ""),
('\x1F', ""),
('\u{007F}', ""),
]
.into_iter()
.collect()
});
// generated using ucd-generate: ucd-generate general-category --include Format --chars ucd-16.0.0
pub const FORMAT: &'static [(char, char)] = &[
('\u{ad}', '\u{ad}'),
('\u{600}', '\u{605}'),
('\u{61c}', '\u{61c}'),
('\u{6dd}', '\u{6dd}'),
('\u{70f}', '\u{70f}'),
('\u{890}', '\u{891}'),
('\u{8e2}', '\u{8e2}'),
('\u{180e}', '\u{180e}'),
('\u{200b}', '\u{200f}'),
('\u{202a}', '\u{202e}'),
('\u{2060}', '\u{2064}'),
('\u{2066}', '\u{206f}'),
('\u{feff}', '\u{feff}'),
('\u{fff9}', '\u{fffb}'),
('\u{110bd}', '\u{110bd}'),
('\u{110cd}', '\u{110cd}'),
('\u{13430}', '\u{1343f}'),
('\u{1bca0}', '\u{1bca3}'),
('\u{1d173}', '\u{1d17a}'),
('\u{e0001}', '\u{e0001}'),
('\u{e0020}', '\u{e007f}'),
];
// hand-made base on https://invisible-characters.com (Excluding Cf)
pub const OTHER: &'static [(char, char)] = &[
('\u{034f}', '\u{034f}'),
('\u{115F}', '\u{1160}'),
('\u{17b4}', '\u{17b5}'),
('\u{180b}', '\u{180d}'),
('\u{2800}', '\u{2800}'),
('\u{3164}', '\u{3164}'),
('\u{fe00}', '\u{fe0d}'),
('\u{ffa0}', '\u{ffa0}'),
('\u{fffc}', '\u{fffc}'),
('\u{e0100}', '\u{e01ef}'),
];
// a subset of FORMAT/OTHER that may appear within glyphs
const PRESERVE: &'static [(char, char)] = &[
('\u{034f}', '\u{034f}'),
('\u{200d}', '\u{200d}'),
('\u{17b4}', '\u{17b5}'),
('\u{180b}', '\u{180d}'),
('\u{e0061}', '\u{e007a}'),
('\u{e007f}', '\u{e007f}'),
];
fn contains(c: char, list: &[(char, char)]) -> bool {
for (start, end) in list {
if c < *start {
return false;
}
if c <= *end {
return true;
}
}
false
}

View File

@@ -1,6 +1,5 @@
use super::{
fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot},
invisibles::{is_invisible, replacement},
Highlights,
};
use language::{Chunk, Point};
@@ -10,14 +9,14 @@ use sum_tree::Bias;
const MAX_EXPANSION_COLUMN: u32 = 256;
/// Keeps track of hard tabs and non-printable characters in a text buffer.
/// Keeps track of hard tabs in a text buffer.
///
/// See the [`display_map` module documentation](crate::display_map) for more information.
pub struct CharMap(CharSnapshot);
pub struct TabMap(TabSnapshot);
impl CharMap {
pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, CharSnapshot) {
let snapshot = CharSnapshot {
impl TabMap {
pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
let snapshot = TabSnapshot {
fold_snapshot,
tab_size,
max_expansion_column: MAX_EXPANSION_COLUMN,
@@ -27,7 +26,7 @@ impl CharMap {
}
#[cfg(test)]
pub fn set_max_expansion_column(&mut self, column: u32) -> CharSnapshot {
pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot {
self.0.max_expansion_column = column;
self.0.clone()
}
@@ -37,9 +36,9 @@ impl CharMap {
fold_snapshot: FoldSnapshot,
mut fold_edits: Vec<FoldEdit>,
tab_size: NonZeroU32,
) -> (CharSnapshot, Vec<TabEdit>) {
) -> (TabSnapshot, Vec<TabEdit>) {
let old_snapshot = &mut self.0;
let mut new_snapshot = CharSnapshot {
let mut new_snapshot = TabSnapshot {
fold_snapshot,
tab_size,
max_expansion_column: old_snapshot.max_expansion_column,
@@ -138,15 +137,15 @@ impl CharMap {
let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
tab_edits.push(TabEdit {
old: old_snapshot.to_char_point(old_start)..old_snapshot.to_char_point(old_end),
new: new_snapshot.to_char_point(new_start)..new_snapshot.to_char_point(new_end),
old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
});
}
} else {
new_snapshot.version += 1;
tab_edits.push(TabEdit {
old: CharPoint::zero()..old_snapshot.max_point(),
new: CharPoint::zero()..new_snapshot.max_point(),
old: TabPoint::zero()..old_snapshot.max_point(),
new: TabPoint::zero()..new_snapshot.max_point(),
});
}
@@ -156,14 +155,14 @@ impl CharMap {
}
#[derive(Clone)]
pub struct CharSnapshot {
pub struct TabSnapshot {
pub fold_snapshot: FoldSnapshot,
pub tab_size: NonZeroU32,
pub max_expansion_column: u32,
pub version: usize,
}
impl CharSnapshot {
impl TabSnapshot {
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
&self.fold_snapshot.inlay_snapshot.buffer
}
@@ -171,7 +170,7 @@ impl CharSnapshot {
pub fn line_len(&self, row: u32) -> u32 {
let max_point = self.max_point();
if row < max_point.row() {
self.to_char_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row)))
.0
.column
} else {
@@ -180,10 +179,10 @@ impl CharSnapshot {
}
pub fn text_summary(&self) -> TextSummary {
self.text_summary_for_range(CharPoint::zero()..self.max_point())
self.text_summary_for_range(TabPoint::zero()..self.max_point())
}
pub fn text_summary_for_range(&self, range: Range<CharPoint>) -> TextSummary {
pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
let input_start = self.to_fold_point(range.start, Bias::Left).0;
let input_end = self.to_fold_point(range.end, Bias::Right).0;
let input_summary = self
@@ -212,7 +211,7 @@ impl CharSnapshot {
} else {
for _ in self
.chunks(
CharPoint::new(range.end.row(), 0)..range.end,
TabPoint::new(range.end.row(), 0)..range.end,
false,
Highlights::default(),
)
@@ -233,7 +232,7 @@ impl CharSnapshot {
pub fn chunks<'a>(
&'a self,
range: Range<CharPoint>,
range: Range<TabPoint>,
language_aware: bool,
highlights: Highlights<'a>,
) -> TabChunks<'a> {
@@ -279,7 +278,7 @@ impl CharSnapshot {
#[cfg(test)]
pub fn text(&self) -> String {
self.chunks(
CharPoint::zero()..self.max_point(),
TabPoint::zero()..self.max_point(),
false,
Highlights::default(),
)
@@ -287,24 +286,24 @@ impl CharSnapshot {
.collect()
}
pub fn max_point(&self) -> CharPoint {
self.to_char_point(self.fold_snapshot.max_point())
pub fn max_point(&self) -> TabPoint {
self.to_tab_point(self.fold_snapshot.max_point())
}
pub fn clip_point(&self, point: CharPoint, bias: Bias) -> CharPoint {
self.to_char_point(
pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
self.to_tab_point(
self.fold_snapshot
.clip_point(self.to_fold_point(point, bias).0, bias),
)
}
pub fn to_char_point(&self, input: FoldPoint) -> CharPoint {
pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
let expanded = self.expand_tabs(chars, input.column());
CharPoint::new(input.row(), expanded)
TabPoint::new(input.row(), expanded)
}
pub fn to_fold_point(&self, output: CharPoint, bias: Bias) -> (FoldPoint, u32, u32) {
pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) {
let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
let expanded = output.column();
let (collapsed, expanded_char_column, to_next_stop) =
@@ -316,13 +315,13 @@ impl CharSnapshot {
)
}
pub fn make_char_point(&self, point: Point, bias: Bias) -> CharPoint {
pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point);
let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias);
self.to_char_point(fold_point)
self.to_tab_point(fold_point)
}
pub fn to_point(&self, point: CharPoint, bias: Bias) -> Point {
pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
let fold_point = self.to_fold_point(point, bias).0;
let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot);
self.fold_snapshot
@@ -345,9 +344,6 @@ impl CharSnapshot {
let tab_len = tab_size - expanded_chars % tab_size;
expanded_bytes += tab_len;
expanded_chars += tab_len;
} else if let Some(replacement) = replacement(c) {
expanded_chars += replacement.chars().count() as u32;
expanded_bytes += replacement.len() as u32;
} else {
expanded_bytes += c.len_utf8() as u32;
expanded_chars += 1;
@@ -387,9 +383,6 @@ impl CharSnapshot {
Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
};
}
} else if let Some(replacement) = replacement(c) {
expanded_chars += replacement.chars().count() as u32;
expanded_bytes += replacement.len() as u32;
} else {
expanded_chars += 1;
expanded_bytes += c.len_utf8() as u32;
@@ -411,9 +404,9 @@ impl CharSnapshot {
}
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct CharPoint(pub Point);
pub struct TabPoint(pub Point);
impl CharPoint {
impl TabPoint {
pub fn new(row: u32, column: u32) -> Self {
Self(Point::new(row, column))
}
@@ -431,13 +424,13 @@ impl CharPoint {
}
}
impl From<Point> for CharPoint {
impl From<Point> for TabPoint {
fn from(point: Point) -> Self {
Self(point)
}
}
pub type TabEdit = text::Edit<CharPoint>;
pub type TabEdit = text::Edit<TabPoint>;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TextSummary {
@@ -558,37 +551,6 @@ impl<'a> Iterator for TabChunks<'a> {
self.input_column = 0;
self.output_position += Point::new(1, 0);
}
_ if is_invisible(c) => {
if ix > 0 {
let (prefix, suffix) = self.chunk.text.split_at(ix);
self.chunk.text = suffix;
return Some(Chunk {
text: prefix,
is_invisible: false,
..self.chunk.clone()
});
}
let c_len = c.len_utf8();
let replacement = replacement(c).unwrap_or(&self.chunk.text[..c_len]);
if self.chunk.text.len() >= c_len {
self.chunk.text = &self.chunk.text[c_len..];
} else {
self.chunk.text = "";
}
let len = replacement.chars().count() as u32;
let next_output_position = cmp::min(
self.output_position + Point::new(0, len),
self.max_output_position,
);
self.column += len;
self.input_column += 1;
self.output_position = next_output_position;
return Some(Chunk {
text: replacement,
is_invisible: true,
..self.chunk.clone()
});
}
_ => {
self.column += 1;
if !self.inside_leading_tab {
@@ -618,11 +580,11 @@ mod tests {
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
assert_eq!(char_snapshot.expand_tabs("\t".chars(), 0), 0);
assert_eq!(char_snapshot.expand_tabs("\t".chars(), 1), 4);
assert_eq!(char_snapshot.expand_tabs("\ta".chars(), 2), 5);
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0);
assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4);
assert_eq!(tab_snapshot.expand_tabs("\ta".chars(), 2), 5);
}
#[gpui::test]
@@ -635,16 +597,16 @@ mod tests {
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, mut char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
char_snapshot.max_expansion_column = max_expansion_column;
assert_eq!(char_snapshot.text(), output);
tab_snapshot.max_expansion_column = max_expansion_column;
assert_eq!(tab_snapshot.text(), output);
for (ix, c) in input.char_indices() {
assert_eq!(
char_snapshot
tab_snapshot
.chunks(
CharPoint::new(0, ix as u32)..char_snapshot.max_point(),
TabPoint::new(0, ix as u32)..tab_snapshot.max_point(),
false,
Highlights::default(),
)
@@ -658,13 +620,13 @@ mod tests {
let input_point = Point::new(0, ix as u32);
let output_point = Point::new(0, output.find(c).unwrap() as u32);
assert_eq!(
char_snapshot.to_char_point(FoldPoint(input_point)),
CharPoint(output_point),
"to_char_point({input_point:?})"
tab_snapshot.to_tab_point(FoldPoint(input_point)),
TabPoint(output_point),
"to_tab_point({input_point:?})"
);
assert_eq!(
char_snapshot
.to_fold_point(CharPoint(output_point), Bias::Left)
tab_snapshot
.to_fold_point(TabPoint(output_point), Bias::Left)
.0,
FoldPoint(input_point),
"to_fold_point({output_point:?})"
@@ -682,10 +644,10 @@ mod tests {
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, mut char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
char_snapshot.max_expansion_column = max_expansion_column;
assert_eq!(char_snapshot.text(), input);
tab_snapshot.max_expansion_column = max_expansion_column;
assert_eq!(tab_snapshot.text(), input);
}
#[gpui::test]
@@ -696,10 +658,10 @@ mod tests {
let buffer_snapshot = buffer.read(cx).snapshot(cx);
let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
let (_, fold_snapshot) = FoldMap::new(inlay_snapshot);
let (_, char_snapshot) = CharMap::new(fold_snapshot, 4.try_into().unwrap());
let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap());
assert_eq!(
chunks(&char_snapshot, CharPoint::zero()),
chunks(&tab_snapshot, TabPoint::zero()),
vec![
(" ".to_string(), true),
(" ".to_string(), false),
@@ -708,7 +670,7 @@ mod tests {
]
);
assert_eq!(
chunks(&char_snapshot, CharPoint::new(0, 2)),
chunks(&tab_snapshot, TabPoint::new(0, 2)),
vec![
(" ".to_string(), true),
(" ".to_string(), false),
@@ -717,7 +679,7 @@ mod tests {
]
);
fn chunks(snapshot: &CharSnapshot, start: CharPoint) -> Vec<(String, bool)> {
fn chunks(snapshot: &TabSnapshot, start: TabPoint) -> Vec<(String, bool)> {
let mut chunks = Vec::new();
let mut was_tab = false;
let mut text = String::new();
@@ -763,12 +725,12 @@ mod tests {
let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng);
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (mut char_map, _) = CharMap::new(fold_snapshot.clone(), tab_size);
let tabs_snapshot = char_map.set_max_expansion_column(32);
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
let tabs_snapshot = tab_map.set_max_expansion_column(32);
let text = text::Rope::from(tabs_snapshot.text().as_str());
log::info!(
"CharMap text (tab size: {}): {:?}",
"TabMap text (tab size: {}): {:?}",
tab_size,
tabs_snapshot.text(),
);
@@ -776,11 +738,11 @@ mod tests {
for _ in 0..5 {
let end_row = rng.gen_range(0..=text.max_point().row);
let end_column = rng.gen_range(0..=text.line_len(end_row));
let mut end = CharPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
let start_row = rng.gen_range(0..=text.max_point().row);
let start_column = rng.gen_range(0..=text.line_len(start_row));
let mut start =
CharPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
if start > end {
mem::swap(&mut start, &mut end);
}

View File

@@ -1,6 +1,6 @@
use super::{
char_map::{self, CharPoint, CharSnapshot, TabEdit},
fold_map::FoldBufferRows,
tab_map::{self, TabEdit, TabPoint, TabSnapshot},
Highlights,
};
use gpui::{AppContext, Context, Font, LineWrapper, Model, ModelContext, Pixels, Task};
@@ -12,7 +12,7 @@ use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
use sum_tree::{Bias, Cursor, SumTree};
use text::Patch;
pub use super::char_map::TextSummary;
pub use super::tab_map::TextSummary;
pub type WrapEdit = text::Edit<u32>;
/// Handles soft wrapping of text.
@@ -20,7 +20,7 @@ pub type WrapEdit = text::Edit<u32>;
/// See the [`display_map` module documentation](crate::display_map) for more information.
pub struct WrapMap {
snapshot: WrapSnapshot,
pending_edits: VecDeque<(CharSnapshot, Vec<TabEdit>)>,
pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
interpolated_edits: Patch<u32>,
edits_since_sync: Patch<u32>,
wrap_width: Option<Pixels>,
@@ -30,7 +30,7 @@ pub struct WrapMap {
#[derive(Clone)]
pub struct WrapSnapshot {
char_snapshot: CharSnapshot,
tab_snapshot: TabSnapshot,
transforms: SumTree<Transform>,
interpolated: bool,
}
@@ -51,11 +51,11 @@ struct TransformSummary {
pub struct WrapPoint(pub Point);
pub struct WrapChunks<'a> {
input_chunks: char_map::TabChunks<'a>,
input_chunks: tab_map::TabChunks<'a>,
input_chunk: Chunk<'a>,
output_position: WrapPoint,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
}
#[derive(Clone)]
@@ -65,12 +65,12 @@ pub struct WrapBufferRows<'a> {
output_row: u32,
soft_wrapped: bool,
max_output_row: u32,
transforms: Cursor<'a, Transform, (WrapPoint, CharPoint)>,
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
}
impl WrapMap {
pub fn new(
char_snapshot: CharSnapshot,
tab_snapshot: TabSnapshot,
font: Font,
font_size: Pixels,
wrap_width: Option<Pixels>,
@@ -83,7 +83,7 @@ impl WrapMap {
pending_edits: Default::default(),
interpolated_edits: Default::default(),
edits_since_sync: Default::default(),
snapshot: WrapSnapshot::new(char_snapshot),
snapshot: WrapSnapshot::new(tab_snapshot),
background_task: None,
};
this.set_wrap_width(wrap_width, cx);
@@ -101,17 +101,17 @@ impl WrapMap {
pub fn sync(
&mut self,
char_snapshot: CharSnapshot,
tab_snapshot: TabSnapshot,
edits: Vec<TabEdit>,
cx: &mut ModelContext<Self>,
) -> (WrapSnapshot, Patch<u32>) {
if self.wrap_width.is_some() {
self.pending_edits.push_back((char_snapshot, edits));
self.pending_edits.push_back((tab_snapshot, edits));
self.flush_edits(cx);
} else {
self.edits_since_sync = self
.edits_since_sync
.compose(self.snapshot.interpolate(char_snapshot, &edits));
.compose(self.snapshot.interpolate(tab_snapshot, &edits));
self.snapshot.interpolated = false;
}
@@ -161,11 +161,11 @@ impl WrapMap {
let (font, font_size) = self.font_with_size.clone();
let task = cx.background_executor().spawn(async move {
let mut line_wrapper = text_system.line_wrapper(font, font_size);
let char_snapshot = new_snapshot.char_snapshot.clone();
let range = CharPoint::zero()..char_snapshot.max_point();
let tab_snapshot = new_snapshot.tab_snapshot.clone();
let range = TabPoint::zero()..tab_snapshot.max_point();
let edits = new_snapshot
.update(
char_snapshot,
tab_snapshot,
&[TabEdit {
old: range.clone(),
new: range.clone(),
@@ -205,7 +205,7 @@ impl WrapMap {
} else {
let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
self.snapshot.transforms = SumTree::default();
let summary = self.snapshot.char_snapshot.text_summary();
let summary = self.snapshot.tab_snapshot.text_summary();
if !summary.lines.is_zero() {
self.snapshot
.transforms
@@ -223,8 +223,8 @@ impl WrapMap {
fn flush_edits(&mut self, cx: &mut ModelContext<Self>) {
if !self.snapshot.interpolated {
let mut to_remove_len = 0;
for (char_snapshot, _) in &self.pending_edits {
if char_snapshot.version <= self.snapshot.char_snapshot.version {
for (tab_snapshot, _) in &self.pending_edits {
if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
to_remove_len += 1;
} else {
break;
@@ -246,9 +246,9 @@ impl WrapMap {
let update_task = cx.background_executor().spawn(async move {
let mut edits = Patch::default();
let mut line_wrapper = text_system.line_wrapper(font, font_size);
for (char_snapshot, tab_edits) in pending_edits {
for (tab_snapshot, tab_edits) in pending_edits {
let wrap_edits = snapshot
.update(char_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
.update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
.await;
edits = edits.compose(&wrap_edits);
}
@@ -285,11 +285,11 @@ impl WrapMap {
let was_interpolated = self.snapshot.interpolated;
let mut to_remove_len = 0;
for (char_snapshot, edits) in &self.pending_edits {
if char_snapshot.version <= self.snapshot.char_snapshot.version {
for (tab_snapshot, edits) in &self.pending_edits {
if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
to_remove_len += 1;
} else {
let interpolated_edits = self.snapshot.interpolate(char_snapshot.clone(), edits);
let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits);
self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
}
@@ -302,49 +302,45 @@ impl WrapMap {
}
impl WrapSnapshot {
fn new(char_snapshot: CharSnapshot) -> Self {
fn new(tab_snapshot: TabSnapshot) -> Self {
let mut transforms = SumTree::default();
let extent = char_snapshot.text_summary();
let extent = tab_snapshot.text_summary();
if !extent.lines.is_zero() {
transforms.push(Transform::isomorphic(extent), &());
}
Self {
transforms,
char_snapshot,
tab_snapshot,
interpolated: true,
}
}
pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
self.char_snapshot.buffer_snapshot()
self.tab_snapshot.buffer_snapshot()
}
fn interpolate(
&mut self,
new_char_snapshot: CharSnapshot,
tab_edits: &[TabEdit],
) -> Patch<u32> {
fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch<u32> {
let mut new_transforms;
if tab_edits.is_empty() {
new_transforms = self.transforms.clone();
} else {
let mut old_cursor = self.transforms.cursor::<CharPoint>(&());
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
let mut tab_edits_iter = tab_edits.iter().peekable();
new_transforms =
old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right, &());
while let Some(edit) = tab_edits_iter.next() {
if edit.new.start > CharPoint::from(new_transforms.summary().input.lines) {
let summary = new_char_snapshot.text_summary_for_range(
CharPoint::from(new_transforms.summary().input.lines)..edit.new.start,
if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) {
let summary = new_tab_snapshot.text_summary_for_range(
TabPoint::from(new_transforms.summary().input.lines)..edit.new.start,
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
if !edit.new.is_empty() {
new_transforms.push_or_extend(Transform::isomorphic(
new_char_snapshot.text_summary_for_range(edit.new.clone()),
new_tab_snapshot.text_summary_for_range(edit.new.clone()),
));
}
@@ -353,7 +349,7 @@ impl WrapSnapshot {
if next_edit.old.start > old_cursor.end(&()) {
if old_cursor.end(&()) > edit.old.end {
let summary = self
.char_snapshot
.tab_snapshot
.text_summary_for_range(edit.old.end..old_cursor.end(&()));
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
@@ -367,7 +363,7 @@ impl WrapSnapshot {
} else {
if old_cursor.end(&()) > edit.old.end {
let summary = self
.char_snapshot
.tab_snapshot
.text_summary_for_range(edit.old.end..old_cursor.end(&()));
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
@@ -380,7 +376,7 @@ impl WrapSnapshot {
let old_snapshot = mem::replace(
self,
WrapSnapshot {
char_snapshot: new_char_snapshot,
tab_snapshot: new_tab_snapshot,
transforms: new_transforms,
interpolated: true,
},
@@ -391,7 +387,7 @@ impl WrapSnapshot {
async fn update(
&mut self,
new_char_snapshot: CharSnapshot,
new_tab_snapshot: TabSnapshot,
tab_edits: &[TabEdit],
wrap_width: Pixels,
line_wrapper: &mut LineWrapper,
@@ -428,27 +424,27 @@ impl WrapSnapshot {
new_transforms = self.transforms.clone();
} else {
let mut row_edits = row_edits.into_iter().peekable();
let mut old_cursor = self.transforms.cursor::<CharPoint>(&());
let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
new_transforms = old_cursor.slice(
&CharPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
&TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
Bias::Right,
&(),
);
while let Some(edit) = row_edits.next() {
if edit.new_rows.start > new_transforms.summary().input.lines.row {
let summary = new_char_snapshot.text_summary_for_range(
CharPoint(new_transforms.summary().input.lines)
..CharPoint::new(edit.new_rows.start, 0),
let summary = new_tab_snapshot.text_summary_for_range(
TabPoint(new_transforms.summary().input.lines)
..TabPoint::new(edit.new_rows.start, 0),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
let mut line = String::new();
let mut remaining = None;
let mut chunks = new_char_snapshot.chunks(
CharPoint::new(edit.new_rows.start, 0)..new_char_snapshot.max_point(),
let mut chunks = new_tab_snapshot.chunks(
TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
false,
Highlights::default(),
);
@@ -495,19 +491,19 @@ impl WrapSnapshot {
}
new_transforms.extend(edit_transforms, &());
old_cursor.seek_forward(&CharPoint::new(edit.old_rows.end, 0), Bias::Right, &());
old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &());
if let Some(next_edit) = row_edits.peek() {
if next_edit.old_rows.start > old_cursor.end(&()).row() {
if old_cursor.end(&()) > CharPoint::new(edit.old_rows.end, 0) {
let summary = self.char_snapshot.text_summary_for_range(
CharPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
let summary = self.tab_snapshot.text_summary_for_range(
TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
old_cursor.next(&());
new_transforms.append(
old_cursor.slice(
&CharPoint::new(next_edit.old_rows.start, 0),
&TabPoint::new(next_edit.old_rows.start, 0),
Bias::Right,
&(),
),
@@ -515,9 +511,9 @@ impl WrapSnapshot {
);
}
} else {
if old_cursor.end(&()) > CharPoint::new(edit.old_rows.end, 0) {
let summary = self.char_snapshot.text_summary_for_range(
CharPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
if old_cursor.end(&()) > TabPoint::new(edit.old_rows.end, 0) {
let summary = self.tab_snapshot.text_summary_for_range(
TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(&()),
);
new_transforms.push_or_extend(Transform::isomorphic(summary));
}
@@ -530,7 +526,7 @@ impl WrapSnapshot {
let old_snapshot = mem::replace(
self,
WrapSnapshot {
char_snapshot: new_char_snapshot,
tab_snapshot: new_tab_snapshot,
transforms: new_transforms,
interpolated: false,
},
@@ -583,17 +579,17 @@ impl WrapSnapshot {
) -> WrapChunks<'a> {
let output_start = WrapPoint::new(rows.start, 0);
let output_end = WrapPoint::new(rows.end, 0);
let mut transforms = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&output_start, Bias::Right, &());
let mut input_start = CharPoint(transforms.start().1 .0);
let mut input_start = TabPoint(transforms.start().1 .0);
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_start.0 += output_start.0 - transforms.start().0 .0;
}
let input_end = self
.to_char_point(output_end)
.min(self.char_snapshot.max_point());
.to_tab_point(output_end)
.min(self.tab_snapshot.max_point());
WrapChunks {
input_chunks: self.char_snapshot.chunks(
input_chunks: self.tab_snapshot.chunks(
input_start..input_end,
language_aware,
highlights,
@@ -610,7 +606,7 @@ impl WrapSnapshot {
}
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left, &());
if cursor
.item()
@@ -618,7 +614,7 @@ impl WrapSnapshot {
{
let overshoot = row - cursor.start().0.row();
let tab_row = cursor.start().1.row() + overshoot;
let tab_line_len = self.char_snapshot.line_len(tab_row);
let tab_line_len = self.tab_snapshot.line_len(tab_row);
if overshoot == 0 {
cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
} else {
@@ -646,14 +642,14 @@ impl WrapSnapshot {
}
pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows {
let mut transforms = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
let mut input_row = transforms.start().1.row();
if transforms.item().map_or(false, |t| t.is_isomorphic()) {
input_row += start_row - transforms.start().0.row();
}
let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
let mut input_buffer_rows = self.char_snapshot.buffer_rows(input_row);
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row);
let input_buffer_row = input_buffer_rows.next().unwrap();
WrapBufferRows {
transforms,
@@ -665,26 +661,26 @@ impl WrapSnapshot {
}
}
pub fn to_char_point(&self, point: WrapPoint) -> CharPoint {
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
let mut char_point = cursor.start().1 .0;
let mut tab_point = cursor.start().1 .0;
if cursor.item().map_or(false, |t| t.is_isomorphic()) {
char_point += point.0 - cursor.start().0 .0;
tab_point += point.0 - cursor.start().0 .0;
}
CharPoint(char_point)
TabPoint(tab_point)
}
pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
self.char_snapshot.to_point(self.to_char_point(point), bias)
self.tab_snapshot.to_point(self.to_tab_point(point), bias)
}
pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
self.char_point_to_wrap_point(self.char_snapshot.make_char_point(point, bias))
self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
}
pub fn char_point_to_wrap_point(&self, point: CharPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(CharPoint, WrapPoint)>(&());
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(&());
cursor.seek(&point, Bias::Right, &());
WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0))
}
@@ -699,10 +695,7 @@ impl WrapSnapshot {
}
}
self.char_point_to_wrap_point(
self.char_snapshot
.clip_point(self.to_char_point(point), bias),
)
self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
}
pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 {
@@ -712,7 +705,7 @@ impl WrapSnapshot {
*point.column_mut() = 0;
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
if cursor.item().is_none() {
cursor.prev(&());
@@ -732,7 +725,7 @@ impl WrapSnapshot {
pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
point.0 += Point::new(1, 0);
let mut cursor = self.transforms.cursor::<(WrapPoint, CharPoint)>(&());
let mut cursor = self.transforms.cursor::<(WrapPoint, TabPoint)>(&());
cursor.seek(&point, Bias::Right, &());
while let Some(transform) = cursor.item() {
if transform.is_isomorphic() && cursor.start().1.column() == 0 {
@@ -749,8 +742,8 @@ impl WrapSnapshot {
#[cfg(test)]
{
assert_eq!(
CharPoint::from(self.transforms.summary().input.lines),
self.char_snapshot.max_point()
TabPoint::from(self.transforms.summary().input.lines),
self.tab_snapshot.max_point()
);
{
@@ -763,18 +756,18 @@ impl WrapSnapshot {
}
let text = language::Rope::from(self.text().as_str());
let mut input_buffer_rows = self.char_snapshot.buffer_rows(0);
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
let mut expected_buffer_rows = Vec::new();
let mut prev_tab_row = 0;
for display_row in 0..=self.max_point().row() {
let char_point = self.to_char_point(WrapPoint::new(display_row, 0));
if char_point.row() == prev_tab_row && display_row != 0 {
let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
if tab_point.row() == prev_tab_row && display_row != 0 {
expected_buffer_rows.push(None);
} else {
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
}
prev_tab_row = char_point.row();
prev_tab_row = tab_point.row();
assert_eq!(self.line_len(display_row), text.line_len(display_row));
}
@@ -838,11 +831,13 @@ impl<'a> Iterator for WrapChunks<'a> {
} else {
*self.output_position.column_mut() += char_len as u32;
}
if self.output_position >= transform_end {
self.transforms.next(&());
break;
}
}
let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
self.input_chunk.text = suffix;
Some(Chunk {
@@ -997,7 +992,7 @@ impl sum_tree::Summary for TransformSummary {
}
}
impl<'a> sum_tree::Dimension<'a, TransformSummary> for CharPoint {
impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
fn zero(_cx: &()) -> Self {
Default::default()
}
@@ -1007,7 +1002,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for CharPoint {
}
}
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for CharPoint {
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
Ord::cmp(&self.0, &cursor_location.input.lines)
}
@@ -1055,7 +1050,7 @@ fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
mod tests {
use super::*;
use crate::{
display_map::{char_map::CharMap, fold_map::FoldMap, inlay_map::InlayMap},
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
MultiBuffer,
};
use gpui::{font, px, test::observe};
@@ -1107,9 +1102,9 @@ mod tests {
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
log::info!("FoldMap text: {:?}", fold_snapshot.text());
let (mut char_map, _) = CharMap::new(fold_snapshot.clone(), tab_size);
let tabs_snapshot = char_map.set_max_expansion_column(32);
log::info!("CharMap text: {:?}", tabs_snapshot.text());
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
let tabs_snapshot = tab_map.set_max_expansion_column(32);
log::info!("TabMap text: {:?}", tabs_snapshot.text());
let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size);
let unwrapped_text = tabs_snapshot.text();
@@ -1155,7 +1150,7 @@ mod tests {
20..=39 => {
for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
let (tabs_snapshot, tab_edits) =
char_map.sync(fold_snapshot, fold_edits, tab_size);
tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (mut snapshot, wrap_edits) =
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
snapshot.check_invariants();
@@ -1168,7 +1163,7 @@ mod tests {
inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
let (tabs_snapshot, tab_edits) =
char_map.sync(fold_snapshot, fold_edits, tab_size);
tab_map.sync(fold_snapshot, fold_edits, tab_size);
let (mut snapshot, wrap_edits) =
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
snapshot.check_invariants();
@@ -1192,8 +1187,8 @@ mod tests {
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
log::info!("FoldMap text: {:?}", fold_snapshot.text());
let (tabs_snapshot, tab_edits) = char_map.sync(fold_snapshot, fold_edits, tab_size);
log::info!("CharMap text: {:?}", tabs_snapshot.text());
let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
log::info!("TabMap text: {:?}", tabs_snapshot.text());
let unwrapped_text = tabs_snapshot.text();
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
@@ -1239,7 +1234,7 @@ mod tests {
if tab_size.get() == 1
|| !wrapped_snapshot
.char_snapshot
.tab_snapshot
.fold_snapshot
.text()
.contains('\t')

View File

@@ -73,12 +73,12 @@ use git::blame::GitBlame;
use gpui::{
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler,
VisualContext, WeakFocusHandle, WeakView, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -90,7 +90,7 @@ pub use inline_completion_provider::*;
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
@@ -171,7 +171,7 @@ use workspace::{Item as WorkspaceItem, OpenInTerminal, OpenTerminal, TabBarSetti
use crate::hover_links::find_url;
use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
pub const FILE_HEADER_HEIGHT: u32 = 2;
pub const FILE_HEADER_HEIGHT: u32 = 1;
pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
pub const MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT: u32 = 1;
pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
@@ -428,7 +428,8 @@ impl Default for EditorStyle {
}
pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle {
let show_background = language_settings::language_settings(None, None, cx)
let show_background = all_language_settings(None, cx)
.language(None)
.inlay_hints
.show_background;
@@ -546,7 +547,6 @@ pub struct Editor {
ime_transaction: Option<TransactionId>,
active_diagnostics: Option<ActiveDiagnosticGroup>,
soft_wrap_mode_override: Option<language_settings::SoftWrap>,
project: Option<Model<Project>>,
semantics_provider: Option<Rc<dyn SemanticsProvider>>,
completion_provider: Option<Box<dyn CompletionProvider>>,
@@ -616,7 +616,6 @@ pub struct Editor {
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
gutter_dimensions: GutterDimensions,
style: Option<EditorStyle>,
text_style_refinement: Option<TextStyleRefinement>,
next_editor_action_id: EditorActionId,
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
use_autoclose: bool,
@@ -641,6 +640,7 @@ pub struct Editor {
tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
tasks_update_task: Option<Task<()>>,
previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
file_header_size: u32,
breadcrumb_header: Option<String>,
focused_block: Option<FocusedBlock>,
next_scroll_position: NextScrollCursorCenterTopBottom,
@@ -1846,6 +1846,7 @@ impl Editor {
}),
merge_adjacent: true,
};
let file_header_size = if show_excerpt_controls { 3 } else { 2 };
let display_map = cx.new_model(|cx| {
DisplayMap::new(
buffer.clone(),
@@ -1853,7 +1854,7 @@ impl Editor {
font_size,
None,
show_excerpt_controls,
FILE_HEADER_HEIGHT,
file_header_size,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
MULTI_BUFFER_EXCERPT_FOOTER_HEIGHT,
fold_placeholder,
@@ -2037,6 +2038,7 @@ impl Editor {
.restore_unsaved_buffers,
blame: None,
blame_subscription: None,
file_header_size,
tasks: Default::default(),
_subscriptions: vec![
cx.observe(&buffer, Self::on_buffer_changed),
@@ -2064,7 +2066,6 @@ impl Editor {
next_scroll_position: NextScrollCursorCenterTopBottom::default(),
addons: HashMap::default(),
_scroll_cursor_center_top_bottom_task: Task::ready(()),
text_style_refinement: None,
};
this.tasks_update_task = Some(this.refresh_runnables(cx));
this._subscriptions.extend(project_subscriptions);
@@ -4250,10 +4251,7 @@ impl Editor {
.text_anchor_for_position(position, cx)?;
let settings = language_settings::language_settings(
buffer
.read(cx)
.language_at(buffer_position)
.map(|l| l.name()),
buffer.read(cx).language_at(buffer_position).as_ref(),
buffer.read(cx).file(),
cx,
);
@@ -6260,14 +6258,38 @@ impl Editor {
}
}
fn apply_selected_diff_hunks(&mut self, _: &ApplyDiffHunk, cx: &mut ViewContext<Self>) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
let mut ranges_by_buffer = HashMap::default();
self.transact(cx, |editor, cx| {
for hunk in hunks {
if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
ranges_by_buffer
.entry(buffer.clone())
.or_insert_with(Vec::new)
.push(hunk.buffer_range.to_offset(buffer.read(cx)));
}
}
for (buffer, ranges) in ranges_by_buffer {
buffer.update(cx, |buffer, cx| {
buffer.merge_into_base(ranges, cx);
});
}
});
}
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
let project_path = buffer.read(cx).project_path(cx)?;
let project = self.project.as_ref()?.read(cx);
let entry = project.entry_for_path(&project_path, cx)?;
let parent = match &entry.canonical_path {
Some(canonical_path) => canonical_path.to_path_buf(),
None => project.absolute_path(&project_path, cx)?,
let abs_path = project.absolute_path(&project_path, cx)?;
let parent = if entry.is_symlink {
abs_path.canonicalize().ok()?
} else {
abs_path
}
.parent()?
.to_path_buf();
@@ -11161,12 +11183,7 @@ impl Editor {
cx.notify();
}
pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
self.text_style_refinement = Some(style);
}
/// called by the Element so we know what style we were most recently rendered with.
pub(crate) fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
pub fn set_style(&mut self, style: EditorStyle, cx: &mut ViewContext<Self>) {
let rem_size = cx.rem_size();
self.display_map.update(cx, |map, cx| {
map.set_font(
@@ -12791,7 +12808,7 @@ impl Editor {
}
pub fn file_header_size(&self) -> u32 {
FILE_HEADER_HEIGHT
self.file_header_size
}
pub fn revert(
@@ -13362,8 +13379,11 @@ fn inlay_hint_settings(
cx: &mut ViewContext<'_, Editor>,
) -> InlayHintSettings {
let file = snapshot.file_at(location);
let language = snapshot.language_at(location).map(|l| l.name());
language_settings(language, file, cx).inlay_hints
let language = snapshot.language_at(location);
let settings = all_language_settings(file, cx);
settings
.language(language.map(|l| l.name()).as_ref())
.inlay_hints
}
fn consume_contiguous_rows(
@@ -13662,7 +13682,7 @@ impl Render for Editor {
fn render<'a>(&mut self, cx: &mut ViewContext<'a, Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let mut text_style = match self.mode {
let text_style = match self.mode {
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
@@ -13684,9 +13704,6 @@ impl Render for Editor {
..Default::default()
},
};
if let Some(text_style_refinement) = &self.text_style_refinement {
text_style.refine(text_style_refinement)
}
let background = match self.mode {
EditorMode::SingleLine { .. } => cx.theme().system().transparent,
@@ -14103,7 +14120,7 @@ pub fn diagnostic_block_renderer(
let multi_line_diagnostic = diagnostic.message.contains('\n');
let buttons = |diagnostic: &Diagnostic| {
let buttons = |diagnostic: &Diagnostic, block_id: BlockId| {
if multi_line_diagnostic {
v_flex()
} else {
@@ -14111,7 +14128,7 @@ pub fn diagnostic_block_renderer(
}
.when(allow_closing, |div| {
div.children(diagnostic.is_primary.then(|| {
IconButton::new("close-block", IconName::XCircle)
IconButton::new(("close-block", EntityId::from(block_id)), IconName::XCircle)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
@@ -14121,7 +14138,7 @@ pub fn diagnostic_block_renderer(
}))
})
.child(
IconButton::new("copy-block", IconName::Copy)
IconButton::new(("copy-block", EntityId::from(block_id)), IconName::Copy)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.style(ButtonStyle::Transparent)
@@ -14136,7 +14153,7 @@ pub fn diagnostic_block_renderer(
)
};
let icon_size = buttons(&diagnostic)
let icon_size = buttons(&diagnostic, cx.block_id)
.into_any_element()
.layout_as_root(AvailableSpace::min_size(), cx);
@@ -14153,7 +14170,7 @@ pub fn diagnostic_block_renderer(
.w(cx.anchor_x - cx.gutter_dimensions.width - icon_size.width)
.flex_shrink(),
)
.child(buttons(&diagnostic))
.child(buttons(&diagnostic, cx.block_id))
.child(div().flex().flex_shrink_0().child(
StyledText::new(text_without_backticks.clone()).with_highlights(
&text_style,

View File

@@ -21,8 +21,7 @@ use crate::{
EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, LineDown, LineUp, OpenExcerpts, PageDown,
PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
CURSORS_VISIBLE_FOR, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
};
use client::ParticipantIndex;
use collections::{BTreeMap, HashMap};
@@ -32,7 +31,7 @@ use gpui::{
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
ContentMask, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity,
FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
EntityId, FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size,
StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View,
@@ -47,7 +46,7 @@ use language::{
ChunkRendererContext,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, ExcerptId, ExpandExcerptDirection, MultiBufferPoint, MultiBufferRow};
use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
use project::{
project_settings::{GitGutterSetting, ProjectSettings},
ProjectPath,
@@ -68,7 +67,6 @@ use sum_tree::Bias;
use theme::{ActiveTheme, Appearance, PlayerColor};
use ui::prelude::*;
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
use unicode_segmentation::UnicodeSegmentation;
use util::RangeExt;
use util::ResultExt;
use workspace::{item::Item, Workspace};
@@ -1026,21 +1024,23 @@ impl EditorElement {
}
let block_text = if let CursorShape::Block = selection.cursor_shape {
snapshot
.grapheme_at(cursor_position)
.display_chars_at(cursor_position)
.next()
.or_else(|| {
if cursor_column == 0 {
snapshot.placeholder_text().and_then(|s| {
s.graphemes(true).next().map(|s| s.to_owned())
})
snapshot
.placeholder_text()
.and_then(|s| s.chars().next())
.map(|c| (c, cursor_position))
} else {
None
}
})
.and_then(|grapheme| {
let text = if grapheme == "\n" {
.and_then(|(character, _)| {
let text = if character == '\n' {
SharedString::from(" ")
} else {
SharedString::from(grapheme)
SharedString::from(character.to_string())
};
let len = text.len();
@@ -1632,7 +1632,7 @@ impl EditorElement {
let mut block_offset = 0;
let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(prev_line..row_range.start) {
if matches!(block, Block::ExcerptBoundary { .. }) {
if matches!(block, Block::ExcerptHeader { .. }) {
found_excerpt_header = true;
break;
}
@@ -1649,7 +1649,7 @@ impl EditorElement {
let mut block_height = 0;
let mut found_excerpt_header = false;
for (_, block) in snapshot.blocks_in_range(row_range.end..cons_line) {
if matches!(block, Block::ExcerptBoundary { .. }) {
if matches!(block, Block::ExcerptHeader { .. }) {
found_excerpt_header = true;
}
block_height += block.height();
@@ -2100,14 +2100,23 @@ impl EditorElement {
.into_any_element()
}
Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
show_excerpt_controls,
Block::ExcerptHeader {
buffer,
range,
starts_new_buffer,
height,
id,
show_excerpt_controls,
..
} => {
let include_root = self
.editor
.read(cx)
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
#[derive(Clone)]
struct JumpData {
position: Point,
@@ -2116,227 +2125,233 @@ impl EditorElement {
line_offset_from_top: u32,
}
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let excerpt_start = range.context.start;
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row =
language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top =
block_row_start.0 + *height + offset_from_excerpt_start
- snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)
.y as u32;
JumpData {
position: jump_position,
anchor: jump_anchor,
path: jump_path,
line_offset_from_top,
}
});
let icon_offset = gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
let header_padding = px(6.0);
let mut result = v_flex().id(block_id).w_full();
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
result = result.child(
h_flex()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.flex_none()
.justify_end()
.child(self.render_expand_excerpt_button(
prev_excerpt.id,
ExpandExcerptDirection::Down,
IconName::ArrowDownFromLine,
cx,
)),
);
let element = if *starts_new_buffer {
let path = buffer.resolve_file_path(cx, include_root);
let mut filename = None;
let mut parent_path = None;
// Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if let Some(path) = path {
filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path = path
.parent()
.map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
}
}
if let Some(next_excerpt) = next_excerpt {
let buffer = &next_excerpt.buffer;
let range = &next_excerpt.range;
let jump_data = project::File::from_dyn(buffer.file()).map(|file| {
let jump_path = ProjectPath {
worktree_id: file.worktree_id(cx),
path: file.path.clone(),
};
let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
let header_padding = px(6.0);
let excerpt_start = range.context.start;
let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
let offset_from_excerpt_start = if jump_anchor == excerpt_start {
0
} else {
let excerpt_start_row =
language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
let line_offset_from_top =
block_row_start.0 + *height + offset_from_excerpt_start
- snapshot
.scroll_anchor
.scroll_position(&snapshot.display_snapshot)
.y as u32;
JumpData {
position: jump_position,
anchor: jump_anchor,
path: jump_path,
line_offset_from_top,
}
});
if *starts_new_buffer {
let include_root = self
.editor
.read(cx)
.project
.as_ref()
.map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
.unwrap_or_default();
let path = buffer.resolve_file_path(cx, include_root);
let filename = path
.as_ref()
.and_then(|path| Some(path.file_name()?.to_string_lossy().to_string()));
let parent_path = path.as_ref().and_then(|path| {
Some(path.parent()?.to_string_lossy().to_string() + "/")
});
result = result.child(
div()
.px(header_padding)
.pt(header_padding)
.w_full()
.h(FILE_HEADER_HEIGHT as f32 * cx.line_height())
.child(
h_flex()
.id("path header block")
.size_full()
.flex_basis(Length::Definite(DefiniteLength::Fraction(
0.667,
)))
.px(gpui::px(12.))
.rounded_md()
.shadow_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_subheader_background)
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.child(
h_flex().gap_3().child(
h_flex()
.gap_2()
.child(
filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
)
.when_some(parent_path, |then, path| {
then.child(div().child(path).text_color(
cx.theme().colors().text_muted,
))
}),
),
)
.when_some(jump_data, |el, jump_data| {
el.child(Icon::new(IconName::ArrowUpRight))
.cursor_pointer()
.tooltip(|cx| {
Tooltip::for_action(
"Jump to File",
&OpenExcerpts,
cx,
)
})
.on_mouse_down(MouseButton::Left, |_, cx| {
cx.stop_propagation()
})
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.jump(
jump_data.path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
}),
),
);
if *show_excerpt_controls {
result = result.child(
h_flex()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.flex_none()
.justify_end()
.child(self.render_expand_excerpt_button(
next_excerpt.id,
ExpandExcerptDirection::Up,
IconName::ArrowUpFromLine,
cx,
)),
);
}
} else {
result = result.child(
v_flex()
.id(("path excerpt header", EntityId::from(block_id)))
.w_full()
.px(header_padding)
.pt(header_padding)
.child(
h_flex()
.id("excerpt header block")
.group("excerpt-jump-action")
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.id("path header block")
.h(2. * cx.line_height())
.px(gpui::px(12.))
.rounded_md()
.shadow_md()
.border_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_subheader_background)
.justify_between()
.hover(|style| style.bg(cx.theme().colors().element_hover))
.child(
h_flex().gap_3().child(
h_flex()
.gap_2()
.child(
filename
.map(SharedString::from)
.unwrap_or_else(|| "untitled".into()),
)
.when_some(parent_path, |then, path| {
then.child(
div()
.child(path)
.text_color(cx.theme().colors().text_muted),
)
}),
),
)
.when_some(jump_data.clone(), |el, jump_data| {
el.child(Icon::new(IconName::ArrowUpRight))
.cursor_pointer()
.tooltip(|cx| {
Tooltip::for_action("Jump to File", &OpenExcerpts, cx)
})
.on_mouse_down(MouseButton::Left, |_, cx| {
cx.stop_propagation()
})
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.jump(
jump_data.path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
}),
)
.children(show_excerpt_controls.then(|| {
h_flex()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.333)))
.h(1. * cx.line_height())
.pt_1()
.justify_end()
.flex_none()
.w(icon_offset - header_padding)
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowUpFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme()
.colors()
.editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Up,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
}),
)
}))
} else {
v_flex()
.id(("excerpt header", EntityId::from(block_id)))
.w_full()
.h(snapshot.excerpt_header_height() as f32 * cx.line_height())
.child(
div()
.flex()
.v_flex()
.justify_start()
.w_full()
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.relative()
.id("jump to collapsed context")
.w(relative(1.0))
.h_full()
.child(
div()
.top(px(0.))
.absolute()
.w_full()
.h_px()
.w_full()
.bg(cx.theme().colors().border_variant)
.group_hover("excerpt-jump-action", |style| {
style.bg(cx.theme().colors().border)
}),
)
.cursor_pointer()
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
),
)
.child(
h_flex()
.justify_end()
.flex_none()
.w(icon_offset)
.h_full()
.child(
h_flex()
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32
* cx.line_height())
.flex_none()
.justify_end()
.child(if *show_excerpt_controls {
self.render_expand_excerpt_button(
next_excerpt.id,
ExpandExcerptDirection::Up,
IconName::ArrowUpFromLine,
cx,
)
} else {
show_excerpt_controls
.then(|| {
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowUpFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(
cx.theme().colors().editor_line_number,
)
.group("")
.hover(|style| {
style.text_color(
cx.theme()
.colors()
.editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Up,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
})
})
.unwrap_or_else(|| {
ButtonLike::new("jump-icon")
.style(ButtonStyle::Transparent)
.child(
@@ -2346,6 +2361,7 @@ impl EditorElement {
.text_color(
cx.theme().colors().border_variant,
)
.group("excerpt-jump-action")
.group_hover(
"excerpt-jump-action",
|style| {
@@ -2355,13 +2371,118 @@ impl EditorElement {
},
),
)
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
}),
),
);
}
}
)
.group("excerpt-jump-action")
.cursor_pointer()
.when_some(jump_data.clone(), |this, jump_data| {
this.on_click(cx.listener_for(&self.editor, {
let path = jump_data.path.clone();
move |editor, _, cx| {
cx.stop_propagation();
result.into_any()
editor.jump(
path.clone(),
jump_data.position,
jump_data.anchor,
jump_data.line_offset_from_top,
cx,
);
}
}))
.tooltip(move |cx| {
Tooltip::for_action(
format!(
"Jump to {}:L{}",
jump_data.path.path.display(),
jump_data.position.row + 1
),
&OpenExcerpts,
cx,
)
})
})
};
element.into_any()
}
Block::ExcerptFooter { id, .. } => {
let element = v_flex()
.id(("excerpt footer", EntityId::from(block_id)))
.w_full()
.h(snapshot.excerpt_footer_height() as f32 * cx.line_height())
.child(
h_flex()
.justify_end()
.flex_none()
.w(gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin))
.h_full()
.child(
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(IconName::ArrowDownFromLine.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| {
style.text_color(
cx.theme().colors().editor_active_line_number,
)
}),
)
.on_click(cx.listener_for(&self.editor, {
let id = *id;
move |editor, _, cx| {
editor.expand_excerpt(
id,
multi_buffer::ExpandExcerptDirection::Down,
cx,
);
}
}))
.tooltip({
move |cx| {
Tooltip::for_action(
"Expand Excerpt",
&ExpandExcerpts { lines: 0 },
cx,
)
}
}),
),
);
element.into_any()
}
};
@@ -2388,33 +2509,6 @@ impl EditorElement {
(element, final_size)
}
fn render_expand_excerpt_button(
&self,
excerpt_id: ExcerptId,
direction: ExpandExcerptDirection,
icon: IconName,
cx: &mut WindowContext,
) -> ButtonLike {
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
svg()
.path(icon.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
.group("")
.hover(|style| style.text_color(cx.theme().colors().editor_active_line_number)),
)
.on_click(cx.listener_for(&self.editor, {
move |editor, _, cx| {
editor.expand_excerpt(excerpt_id, direction, cx);
}
}))
.tooltip({
move |cx| Tooltip::for_action("Expand Excerpt", &ExpandExcerpts { lines: 0 }, cx)
})
}
#[allow(clippy::too_many_arguments)]
fn render_blocks(
&self,
@@ -3273,7 +3367,7 @@ impl EditorElement {
let end_row_in_current_excerpt = snapshot
.blocks_in_range(start_row..end_row)
.find_map(|(start_row, block)| {
if matches!(block, Block::ExcerptBoundary { .. }) {
if matches!(block, Block::ExcerptHeader { .. }) {
Some(start_row)
} else {
None

View File

@@ -1,7 +1,6 @@
use crate::{
display_map::{InlayOffset, ToDisplayPoint},
hover_links::{InlayHighlight, RangeInEditor},
is_invisible,
scroll::ScrollAmount,
Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, EditorSnapshot,
Hover, RangeToAnchorExt,
@@ -12,7 +11,7 @@ use gpui::{
StyleRefinement, Styled, Task, TextStyleRefinement, View, ViewContext,
};
use itertools::Itertools;
use language::{Diagnostic, DiagnosticEntry, Language, LanguageRegistry};
use language::{DiagnosticEntry, Language, LanguageRegistry};
use lsp::DiagnosticSeverity;
use markdown::{Markdown, MarkdownStyle};
use multi_buffer::ToOffset;
@@ -200,6 +199,7 @@ fn show_hover(
if editor.pending_rename.is_some() {
return None;
}
let snapshot = editor.snapshot(cx);
let (buffer, buffer_position) = editor
@@ -259,7 +259,7 @@ fn show_hover(
}
// If there's a diagnostic, assign it on the hover state and notify
let mut local_diagnostic = snapshot
let local_diagnostic = snapshot
.buffer_snapshot
.diagnostics_in_range::<_, usize>(anchor..anchor, false)
// Find the entry with the most specific range
@@ -281,42 +281,6 @@ fn show_hover(
})
});
if let Some(invisible) = 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(),
);
local_diagnostic = Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
..Default::default()
},
range: anchor..after,
})
} else if let Some(invisible) = 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(),
);
local_diagnostic = Some(DiagnosticEntry {
diagnostic: Diagnostic {
severity: DiagnosticSeverity::HINT,
message: format!("Unicode character U+{:02X}", invisible as u32),
..Default::default()
},
range: before..anchor,
})
}
let diagnostic_popover = if let Some(local_diagnostic) = local_diagnostic {
let text = match local_diagnostic.diagnostic.source {
Some(ref source) => {
@@ -324,6 +288,7 @@ fn show_hover(
}
None => local_diagnostic.diagnostic.message.clone(),
};
let mut border_color: Option<Hsla> = None;
let mut background_color: Option<Hsla> = None;
@@ -379,6 +344,7 @@ fn show_hover(
Markdown::new_text(text, markdown_style.clone(), None, cx, None)
})
.ok();
Some(DiagnosticPopover {
local_diagnostic,
primary_diagnostic,
@@ -466,6 +432,7 @@ fn show_hover(
cx.notify();
cx.refresh();
})?;
anyhow::Ok(())
}
.log_err()

View File

@@ -7,13 +7,11 @@ use multi_buffer::{
MultiBufferSnapshot, ToPoint,
};
use std::{ops::Range, sync::Arc};
use text::OffsetRangeExt;
use ui::{
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
ParentElement, PopoverMenu, Styled, Tooltip, ViewContext, VisualContext,
};
use util::RangeExt;
use workspace::Item;
use crate::{
editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyDiffHunk,
@@ -329,7 +327,7 @@ impl Editor {
Some(())
}
fn apply_diff_hunks_in_range(
fn apply_changes_in_range(
&mut self,
range: Range<Anchor>,
cx: &mut ViewContext<'_, Editor>,
@@ -345,54 +343,16 @@ impl Editor {
branch_buffer.merge_into_base(vec![range], cx);
});
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
}
None
}
pub(crate) fn apply_all_diff_hunks(&mut self, cx: &mut ViewContext<Self>) {
pub(crate) fn apply_all_changes(&self, cx: &mut ViewContext<Self>) {
let buffers = self.buffer.read(cx).all_buffers();
for branch_buffer in buffers {
branch_buffer.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(Vec::new(), cx);
});
}
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
}
}
pub(crate) fn apply_selected_diff_hunks(
&mut self,
_: &ApplyDiffHunk,
cx: &mut ViewContext<Self>,
) {
let snapshot = self.buffer.read(cx).snapshot(cx);
let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
let mut ranges_by_buffer = HashMap::default();
self.transact(cx, |editor, cx| {
for hunk in hunks {
if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
ranges_by_buffer
.entry(buffer.clone())
.or_insert_with(Vec::new)
.push(hunk.buffer_range.to_offset(buffer.read(cx)));
}
}
for (buffer, ranges) in ranges_by_buffer {
buffer.update(cx, |buffer, cx| {
buffer.merge_into_base(ranges, cx);
});
}
});
if let Some(project) = self.project.clone() {
self.save(true, project, cx).detach_and_log_err(cx);
}
}
fn hunk_header_block(
@@ -458,7 +418,7 @@ impl Editor {
h_flex()
.px_6()
.size_full()
.justify_end()
.justify_between()
.child(
h_flex()
.gap_1()
@@ -588,12 +548,11 @@ impl Editor {
let hunk = hunk.clone();
move |_event, cx| {
editor.update(cx, |editor, cx| {
editor
.apply_diff_hunks_in_range(
hunk.multi_buffer_range
.clone(),
cx,
);
editor.apply_changes_in_range(
hunk.multi_buffer_range
.clone(),
cx,
);
});
}
}),

View File

@@ -39,13 +39,9 @@ impl Editor {
) -> Option<Vec<MultiBufferIndentGuide>> {
let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
language_settings(
buffer.read(cx).language().map(|l| l.name()),
buffer.read(cx).file(),
cx,
)
.indent_guides
.enabled
language_settings(buffer.read(cx).language(), buffer.read(cx).file(), cx)
.indent_guides
.enabled
} else {
true
}

View File

@@ -720,10 +720,6 @@ impl Item for Editor {
) -> Task<Result<()>> {
self.report_editor_event("save", None, cx);
let buffers = self.buffer().clone().read(cx).all_buffers();
let buffers = buffers
.into_iter()
.map(|handle| handle.read(cx).diff_base_buffer().unwrap_or(handle.clone()))
.collect::<HashSet<_>>();
cx.spawn(|this, mut cx| async move {
if format {
this.update(&mut cx, |editor, cx| {
@@ -936,7 +932,7 @@ impl SerializableItem for Editor {
fn deserialize(
project: Model<Project>,
workspace: WeakView<Workspace>,
_workspace: WeakView<Workspace>,
workspace_id: workspace::WorkspaceId,
item_id: ItemId,
cx: &mut ViewContext<Pane>,
@@ -953,7 +949,7 @@ impl SerializableItem for Editor {
serialized_editor
} else {
SerializedEditor {
abs_path: serialized_editor.abs_path,
path: serialized_editor.path,
contents: None,
language: None,
mtime: None,
@@ -968,13 +964,13 @@ impl SerializableItem for Editor {
}
};
match serialized_editor {
let buffer_task = match serialized_editor {
SerializedEditor {
abs_path: None,
path: None,
contents: Some(contents),
language,
..
} => cx.spawn(|pane, mut cx| {
} => cx.spawn(|_, mut cx| {
let project = project.clone();
async move {
let language = if let Some(language_name) = language {
@@ -1001,34 +997,30 @@ impl SerializableItem for Editor {
buffer.set_text(contents, cx);
})?;
pane.update(&mut cx, |_, cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
})
})
anyhow::Ok(buffer)
}
}),
SerializedEditor {
abs_path: Some(abs_path),
path: Some(path),
contents,
mtime,
..
} => {
let project_item = project.update(cx, |project, cx| {
let (worktree, path) = project.find_worktree(&abs_path, cx)?;
let (worktree, path) = project
.find_worktree(&path, cx)
.with_context(|| format!("No worktree for path: {path:?}"))?;
let project_path = ProjectPath {
worktree_id: worktree.read(cx).id(),
path: path.into(),
};
Some(project.open_path(project_path, cx))
Ok(project.open_path(project_path, cx))
});
match project_item {
Some(project_item) => {
cx.spawn(|pane, mut cx| async move {
project_item
.map(|project_item| {
cx.spawn(|_, mut cx| async move {
let (_, project_item) = project_item.await?;
let buffer = project_item.downcast::<Buffer>().map_err(|_| {
anyhow!("Project item at stored path was not a buffer")
@@ -1055,36 +1047,26 @@ impl SerializableItem for Editor {
})?;
}
pane.update(&mut cx, |_, cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
})
})
Ok(buffer)
})
}
None => {
let open_by_abs_path = workspace.update(cx, |workspace, cx| {
workspace.open_abs_path(abs_path.clone(), false, cx)
});
cx.spawn(|_, mut cx| async move {
let editor = open_by_abs_path?.await?.downcast::<Editor>().with_context(|| format!("Failed to downcast to Editor after opening abs path {abs_path:?}"))?;
editor.update(&mut cx, |editor, cx| {
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
})?;
Ok(editor)
})
}
}
})
.unwrap_or_else(|error| Task::ready(Err(error)))
}
SerializedEditor {
abs_path: None,
contents: None,
..
} => Task::ready(Err(anyhow!("No path or contents found for buffer"))),
}
_ => return Task::ready(Err(anyhow!("No path or contents found for buffer"))),
};
cx.spawn(|pane, mut cx| async move {
let buffer = buffer_task.await?;
pane.update(&mut cx, |_, cx| {
cx.new_view(|cx| {
let mut editor = Editor::for_buffer(buffer, Some(project), cx);
editor.read_scroll_position_from_db(item_id, workspace_id, cx);
editor
})
})
})
}
fn serialize(
@@ -1110,19 +1092,12 @@ impl SerializableItem for Editor {
let workspace_id = workspace.database_id()?;
let buffer = self.buffer().read(cx).as_singleton()?;
let abs_path = buffer.read(cx).file().and_then(|file| {
let worktree_id = file.worktree_id(cx);
project
.read(cx)
.worktree_for_id(worktree_id, cx)
.and_then(|worktree| worktree.read(cx).absolutize(&file.path()).ok())
.or_else(|| {
let full_path = file.full_path(cx);
let project_path = project.read(cx).find_project_path(&full_path, cx)?;
project.read(cx).absolute_path(&project_path, cx)
})
});
let path = buffer
.read(cx)
.file()
.map(|file| file.full_path(cx))
.and_then(|full_path| project.read(cx).find_project_path(&full_path, cx))
.and_then(|project_path| project.read(cx).absolute_path(&project_path, cx));
let is_dirty = buffer.read(cx).is_dirty();
let mtime = buffer.read(cx).saved_mtime();
@@ -1141,7 +1116,7 @@ impl SerializableItem for Editor {
};
let editor = SerializedEditor {
abs_path,
path,
contents,
language,
mtime,
@@ -1654,7 +1629,7 @@ mod tests {
let item_id = 1234 as ItemId;
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")),
path: Some(PathBuf::from("/file.rs")),
contents: Some("fn main() {}".to_string()),
language: Some("Rust".to_string()),
mtime: Some(now),
@@ -1685,7 +1660,7 @@ mod tests {
let item_id = 5678 as ItemId;
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")),
path: Some(PathBuf::from("/file.rs")),
contents: None,
language: None,
mtime: None,
@@ -1720,7 +1695,7 @@ mod tests {
let item_id = 9012 as ItemId;
let serialized_editor = SerializedEditor {
abs_path: None,
path: None,
contents: Some("hello".to_string()),
language: Some("Rust".to_string()),
mtime: None,
@@ -1758,7 +1733,7 @@ mod tests {
.checked_sub(std::time::Duration::from_secs(60 * 60 * 24))
.unwrap();
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("/file.rs")),
path: Some(PathBuf::from("/file.rs")),
contents: Some("fn main() {}".to_string()),
language: Some("Rust".to_string()),
mtime: Some(old_mtime),

View File

@@ -952,7 +952,7 @@ mod tests {
px(14.0),
None,
true,
0,
2,
2,
0,
FoldPlaceholder::test(),

View File

@@ -11,7 +11,7 @@ use workspace::{ItemId, WorkspaceDb, WorkspaceId};
#[derive(Clone, Debug, PartialEq, Default)]
pub(crate) struct SerializedEditor {
pub(crate) abs_path: Option<PathBuf>,
pub(crate) path: Option<PathBuf>,
pub(crate) contents: Option<String>,
pub(crate) language: Option<String>,
pub(crate) mtime: Option<SystemTime>,
@@ -25,7 +25,7 @@ impl StaticColumnCount for SerializedEditor {
impl Bind for SerializedEditor {
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
let start_index = statement.bind(&self.abs_path, start_index)?;
let start_index = statement.bind(&self.path, start_index)?;
let start_index = statement.bind(&self.contents, start_index)?;
let start_index = statement.bind(&self.language, start_index)?;
@@ -51,8 +51,7 @@ impl Bind for SerializedEditor {
impl Column for SerializedEditor {
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
let (abs_path, start_index): (Option<PathBuf>, i32) =
Column::column(statement, start_index)?;
let (path, start_index): (Option<PathBuf>, i32) = Column::column(statement, start_index)?;
let (contents, start_index): (Option<String>, i32) =
Column::column(statement, start_index)?;
let (language, start_index): (Option<String>, i32) =
@@ -67,7 +66,7 @@ impl Column for SerializedEditor {
.map(|(seconds, nanos)| UNIX_EPOCH + Duration::new(seconds as u64, nanos as u32));
let editor = Self {
abs_path,
path,
contents,
language,
mtime,
@@ -227,7 +226,7 @@ mod tests {
let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap();
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("testing.txt")),
path: Some(PathBuf::from("testing.txt")),
contents: None,
language: None,
mtime: None,
@@ -245,7 +244,7 @@ mod tests {
// Now update contents and language
let serialized_editor = SerializedEditor {
abs_path: Some(PathBuf::from("testing.txt")),
path: Some(PathBuf::from("testing.txt")),
contents: Some("Test".to_owned()),
language: Some("Go".to_owned()),
mtime: None,
@@ -263,7 +262,7 @@ mod tests {
// Now set all the fields to NULL
let serialized_editor = SerializedEditor {
abs_path: None,
path: None,
contents: None,
language: None,
mtime: None,
@@ -282,7 +281,7 @@ mod tests {
// Storing and retrieving mtime
let now = SystemTime::now();
let serialized_editor = SerializedEditor {
abs_path: None,
path: None,
contents: None,
language: None,
mtime: Some(now),

View File

@@ -298,20 +298,6 @@ impl Item for ProposedChangesEditor {
Item::set_nav_history(editor, nav_history, cx)
});
}
fn can_save(&self, cx: &AppContext) -> bool {
self.editor.read(cx).can_save(cx)
}
fn save(
&mut self,
format: bool,
project: Model<Project>,
cx: &mut ViewContext<Self>,
) -> Task<gpui::Result<()>> {
self.editor
.update(cx, |editor, cx| Item::save(editor, format, project, cx))
}
}
impl ProposedChangesEditorToolbar {
@@ -337,7 +323,7 @@ impl Render for ProposedChangesEditorToolbar {
if let Some(editor) = &editor {
editor.update(cx, |editor, cx| {
editor.editor.update(cx, |editor, cx| {
editor.apply_all_diff_hunks(cx);
editor.apply_all_changes(cx);
})
});
}

View File

@@ -3,7 +3,6 @@ use std::sync::{atomic::AtomicBool, Arc};
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
SlashCommandResult,
};
use futures::FutureExt;
use gpui::{Task, WeakView, WindowContext};
@@ -88,7 +87,7 @@ impl SlashCommand for ExtensionSlashCommand {
_workspace: WeakView<Workspace>,
delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<SlashCommandResult> {
) -> Task<Result<SlashCommandOutput>> {
let arguments = arguments.to_owned();
let output = cx.background_executor().spawn(async move {
self.extension

View File

@@ -356,11 +356,8 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() {
"language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings = AllLanguageSettings::get(location, cx).language(
location,
key.as_ref(),
cx,
);
let settings =
AllLanguageSettings::get(location, cx).language(key.as_ref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)

View File

@@ -402,11 +402,8 @@ impl ExtensionImports for WasmState {
cx.update(|cx| match category.as_str() {
"language" => {
let key = key.map(|k| LanguageName::new(&k));
let settings = AllLanguageSettings::get(location, cx).language(
location,
key.as_ref(),
cx,
);
let settings =
AllLanguageSettings::get(location, cx).language(key.as_ref());
Ok(serde_json::to_string(&settings::LanguageSettings {
tab_size: settings.tab_size,
})?)

View File

@@ -146,7 +146,7 @@ impl Keystroke {
"space" => Some(" ".into()),
"tab" => Some("\t".into()),
"enter" => Some("\n".into()),
key if !is_printable_key(key) || key.is_empty() => None,
key if !is_printable_key(key) => None,
key => {
if self.modifiers.shift {
Some(key.to_uppercase())

View File

@@ -381,11 +381,6 @@ impl MacPlatform {
}
item.setSubmenu_(submenu);
item.setTitle_(ns_string(&name));
if name == "Services" {
let app: id = msg_send![APP_CLASS, sharedApplication];
app.setServicesMenu_(item);
}
item
}
}

View File

@@ -1,7 +1,6 @@
use crate::{
black, fill, point, px, size, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result,
SharedString, StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary,
WrappedLineLayout,
black, fill, point, px, size, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
StrikethroughStyle, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
use derive_more::{Deref, DerefMut};
use smallvec::SmallVec;
@@ -130,9 +129,8 @@ fn paint_line(
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
let mut max_glyph_size = size(px(0.), px(0.));
for (run_ix, run) in layout.runs.iter().enumerate() {
max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
@@ -141,9 +139,6 @@ fn paint_line(
wraps.next();
if let Some((background_origin, background_color)) = current_background.as_mut()
{
if glyph_origin.x == background_origin.x {
background_origin.x -= max_glyph_size.width.half()
}
cx.paint_quad(fill(
Bounds {
origin: *background_origin,
@@ -155,9 +150,6 @@ fn paint_line(
background_origin.y += line_height;
}
if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
if glyph_origin.x == underline_origin.x {
underline_origin.x -= max_glyph_size.width.half();
};
cx.paint_underline(
*underline_origin,
glyph_origin.x - underline_origin.x,
@@ -169,9 +161,6 @@ fn paint_line(
if let Some((strikethrough_origin, strikethrough_style)) =
current_strikethrough.as_mut()
{
if glyph_origin.x == strikethrough_origin.x {
strikethrough_origin.x -= max_glyph_size.width.half();
};
cx.paint_strikethrough(
*strikethrough_origin,
glyph_origin.x - strikethrough_origin.x,
@@ -190,18 +179,7 @@ fn paint_line(
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
if glyph.index >= run_end {
let mut style_run = decoration_runs.next();
// ignore style runs that apply to a partial glyph
while let Some(run) = style_run {
if glyph.index < run_end + (run.len as usize) {
break;
}
run_end += run.len as usize;
style_run = decoration_runs.next();
}
if let Some(style_run) = style_run {
if let Some(style_run) = decoration_runs.next() {
if let Some((_, background_color)) = &mut current_background {
if style_run.background_color.as_ref() != Some(background_color) {
finished_background = current_background.take();
@@ -262,14 +240,10 @@ fn paint_line(
}
if let Some((background_origin, background_color)) = finished_background {
let mut width = glyph_origin.x - background_origin.x;
if width == px(0.) {
width = px(5.)
};
cx.paint_quad(fill(
Bounds {
origin: background_origin,
size: size(width, line_height),
size: size(glyph_origin.x - background_origin.x, line_height),
},
background_color,
));
@@ -325,10 +299,7 @@ fn paint_line(
last_line_end_x -= glyph.position.x;
}
if let Some((mut background_origin, background_color)) = current_background.take() {
if last_line_end_x == background_origin.x {
background_origin.x -= max_glyph_size.width.half()
};
if let Some((background_origin, background_color)) = current_background.take() {
cx.paint_quad(fill(
Bounds {
origin: background_origin,
@@ -338,10 +309,7 @@ fn paint_line(
));
}
if let Some((mut underline_start, underline_style)) = current_underline.take() {
if last_line_end_x == underline_start.x {
underline_start.x -= max_glyph_size.width.half()
};
if let Some((underline_start, underline_style)) = current_underline.take() {
cx.paint_underline(
underline_start,
last_line_end_x - underline_start.x,
@@ -349,10 +317,7 @@ fn paint_line(
);
}
if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
if last_line_end_x == strikethrough_start.x {
strikethrough_start.x -= max_glyph_size.width.half()
};
if let Some((strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
cx.paint_strikethrough(
strikethrough_start,
last_line_end_x - strikethrough_start.x,

View File

@@ -62,7 +62,7 @@ impl Render for InlineCompletionButton {
let status = copilot.read(cx).status();
let enabled = self.editor_enabled.unwrap_or_else(|| {
all_language_settings.inline_completions_enabled(None, None, cx)
all_language_settings.inline_completions_enabled(None, None)
});
let icon = match status {
@@ -248,9 +248,8 @@ impl InlineCompletionButton {
if let Some(language) = self.language.clone() {
let fs = fs.clone();
let language_enabled =
language_settings::language_settings(Some(language.name()), None, cx)
.show_inline_completions;
let language_enabled = language_settings::language_settings(Some(&language), None, cx)
.show_inline_completions;
menu = menu.entry(
format!(
@@ -293,7 +292,7 @@ impl InlineCompletionButton {
);
}
let globally_enabled = settings.inline_completions_enabled(None, None, cx);
let globally_enabled = settings.inline_completions_enabled(None, None);
menu.entry(
if globally_enabled {
"Hide Inline Completions for All Files"
@@ -341,7 +340,6 @@ impl InlineCompletionButton {
&& all_language_settings(file, cx).inline_completions_enabled(
language,
file.map(|file| file.path().as_ref()),
cx,
),
)
};
@@ -444,7 +442,7 @@ async fn configure_disabled_globs(
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(None, None, cx);
all_language_settings(None, cx).inline_completions_enabled(None, None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_inline_completions = Some(!show_inline_completions)
});
@@ -468,7 +466,7 @@ fn toggle_inline_completions_for_language(
cx: &mut AppContext,
) {
let show_inline_completions =
all_language_settings(None, cx).inline_completions_enabled(Some(&language), None, cx);
all_language_settings(None, cx).inline_completions_enabled(Some(&language), None);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.languages
.entry(language.name())

View File

@@ -30,7 +30,6 @@ async-trait.workspace = true
async-watch.workspace = true
clock.workspace = true
collections.workspace = true
ec4rs.workspace = true
futures.workspace = true
fuzzy.workspace = true
git.workspace = true
@@ -71,7 +70,6 @@ env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
indoc.workspace = true
lsp = { workspace = true, features = ["test-support"] }
pretty_assertions.workspace = true
rand.workspace = true
settings = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }

View File

@@ -37,7 +37,6 @@ use smallvec::SmallVec;
use smol::future::yield_now;
use std::{
any::Any,
borrow::Cow,
cell::Cell,
cmp::{self, Ordering, Reverse},
collections::BTreeMap,
@@ -336,8 +335,6 @@ pub enum BufferEvent {
FileHandleChanged,
/// The buffer was reloaded.
Reloaded,
/// The buffer is in need of a reload
ReloadNeeded,
/// The buffer's diff_base changed.
DiffBaseChanged,
/// Buffer's excerpts for a certain diff base were recalculated.
@@ -442,7 +439,7 @@ struct AutoindentRequest {
is_block_mode: bool,
}
#[derive(Debug, Clone)]
#[derive(Clone)]
struct AutoindentRequestEntry {
/// A range of the buffer whose indentation should be adjusted.
range: Range<Anchor>,
@@ -501,8 +498,6 @@ pub struct Chunk<'a> {
pub is_unnecessary: bool,
/// Whether this chunk of text was originally a tab character.
pub is_tab: bool,
/// Whether this chunk of text is an invisible character.
pub is_invisible: bool,
/// An optional recipe for how the chunk should be presented.
pub renderer: Option<ChunkRenderer>,
}
@@ -1081,7 +1076,7 @@ impl Buffer {
file_changed = true;
if !self.is_dirty() {
cx.emit(BufferEvent::ReloadNeeded);
self.reload(cx).close();
}
}
}
@@ -1422,17 +1417,24 @@ impl Buffer {
yield_now().await;
}
// In block mode, only compute indentation suggestions for the first line
// of each insertion. Otherwise, compute suggestions for every inserted line.
let new_edited_row_ranges = contiguous_ranges(
row_ranges.iter().flat_map(|(range, _)| {
if request.is_block_mode {
range.start..range.start + 1
} else {
range.clone()
}
}),
max_rows_between_yields,
);
// Compute new suggestions for each line, but only include them in the result
// if they differ from the old suggestion for that line.
let mut language_indent_sizes = language_indent_sizes_by_new_row.iter().peekable();
let mut language_indent_size = IndentSize::default();
for (row_range, original_indent_column) in row_ranges {
let new_edited_row_range = if request.is_block_mode {
row_range.start..row_range.start + 1
} else {
row_range.clone()
};
for new_edited_row_range in new_edited_row_ranges {
let suggestions = snapshot
.suggest_autoindents(new_edited_row_range.clone())
.into_iter()
@@ -1466,9 +1468,22 @@ impl Buffer {
}
}
}
yield_now().await;
}
if let (true, Some(original_indent_column)) =
(request.is_block_mode, original_indent_column)
// For each block of inserted text, adjust the indentation of the remaining
// lines of the block by the same amount as the first line was adjusted.
if request.is_block_mode {
for (row_range, original_indent_column) in
row_ranges
.into_iter()
.filter_map(|(range, original_indent_column)| {
if range.len() > 1 {
Some((range, original_indent_column?))
} else {
None
}
})
{
let new_indent = indent_sizes
.get(&row_range.start)
@@ -1493,8 +1508,6 @@ impl Buffer {
}
}
}
yield_now().await;
}
}
@@ -2477,11 +2490,7 @@ impl BufferSnapshot {
/// Returns [`IndentSize`] for a given position that respects user settings
/// and language preferences.
pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
let settings = language_settings(
self.language_at(position).map(|l| l.name()),
self.file(),
cx,
);
let settings = language_settings(self.language_at(position), self.file(), cx);
if settings.hard_tabs {
IndentSize::tab()
} else {
@@ -2814,15 +2823,11 @@ impl BufferSnapshot {
/// Returns the settings for the language at the given location.
pub fn settings_at<'a, D: ToOffset>(
&'a self,
&self,
position: D,
cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> {
language_settings(
self.language_at(position).map(|l| l.name()),
self.file.as_ref(),
cx,
)
) -> &'a LanguageSettings {
language_settings(self.language_at(position), self.file.as_ref(), cx)
}
pub fn char_classifier_at<T: ToOffset>(&self, point: T) -> CharClassifier {
@@ -3524,8 +3529,7 @@ impl BufferSnapshot {
ignore_disabled_for_language: bool,
cx: &AppContext,
) -> Vec<IndentGuide> {
let language_settings =
language_settings(self.language().map(|l| l.name()), self.file.as_ref(), cx);
let language_settings = language_settings(self.language(), self.file.as_ref(), cx);
let settings = language_settings.indent_guides;
if !ignore_disabled_for_language && !settings.enabled {
return Vec::new();
@@ -4213,6 +4217,7 @@ impl<'a> Iterator for BufferChunks<'a> {
if self.range.start == self.chunks.offset() + chunk.len() {
self.chunks.next().unwrap();
}
Some(Chunk {
text: slice,
syntax_highlight_id: highlight_id,

View File

@@ -1658,69 +1658,6 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
});
}
#[gpui::test]
fn test_autoindent_block_mode_multiple_adjacent_ranges(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.new_model(|cx| {
let (text, ranges_to_replace) = marked_text_ranges(
&"
mod numbers {
«fn one() {
1
}
»
«fn two() {
2
}
»
«fn three() {
3
}
»}
"
.unindent(),
false,
);
let mut buffer = Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[
(ranges_to_replace[0].clone(), "fn one() {\n 101\n}\n"),
(ranges_to_replace[1].clone(), "fn two() {\n 102\n}\n"),
(ranges_to_replace[2].clone(), "fn three() {\n 103\n}\n"),
],
Some(AutoindentMode::Block {
original_indent_columns: vec![0, 0, 0],
}),
cx,
);
pretty_assertions::assert_eq!(
buffer.text(),
"
mod numbers {
fn one() {
101
}
fn two() {
102
}
fn three() {
103
}
}
"
.unindent()
);
buffer
});
}
#[gpui::test]
fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
init_settings(cx, |_| {});

View File

@@ -15,6 +15,7 @@ mod outline;
pub mod proto;
mod syntax_map;
mod task_context;
mod toolchain;
#[cfg(test)]
pub mod buffer_tests;
@@ -61,6 +62,7 @@ use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange};
use theme::SyntaxTheme;
pub use toolchain::{Toolchain, ToolchainList, ToolchainLister};
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
use util::serde::default_true;

View File

@@ -4,10 +4,6 @@ use crate::{File, Language, LanguageName, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
use core::slice;
use ec4rs::{
property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
Properties as EditorconfigProperties,
};
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::AppContext;
use itertools::{Either, Itertools};
@@ -20,10 +16,8 @@ use serde::{
Deserialize, Deserializer, Serialize,
};
use serde_json::Value;
use settings::{
add_references_to_properties, Settings, SettingsLocation, SettingsSources, SettingsStore,
};
use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
use settings::{add_references_to_properties, Settings, SettingsLocation, SettingsSources};
use std::{num::NonZeroU32, path::Path, sync::Arc};
use util::serde::default_true;
/// Initializes the language settings.
@@ -33,20 +27,17 @@ pub fn init(cx: &mut AppContext) {
/// Returns the settings for the specified language from the provided file.
pub fn language_settings<'a>(
language: Option<LanguageName>,
file: Option<&'a Arc<dyn File>>,
language: Option<&Arc<Language>>,
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> {
let location = file.map(|f| SettingsLocation {
worktree_id: f.worktree_id(cx),
path: f.path().as_ref(),
});
AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx)
) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name());
all_language_settings(file, cx).language(language_name.as_ref())
}
/// Returns the settings for all languages from the provided file.
pub fn all_language_settings<'a>(
file: Option<&'a Arc<dyn File>>,
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a AllLanguageSettings {
let location = file.map(|f| SettingsLocation {
@@ -819,27 +810,13 @@ impl InlayHintSettings {
impl AllLanguageSettings {
/// Returns the [`LanguageSettings`] for the language with the specified name.
pub fn language<'a>(
&'a self,
location: Option<SettingsLocation<'a>>,
language_name: Option<&LanguageName>,
cx: &'a AppContext,
) -> Cow<'a, LanguageSettings> {
let settings = language_name
.and_then(|name| self.languages.get(name))
.unwrap_or(&self.defaults);
let editorconfig_properties = location.and_then(|location| {
cx.global::<SettingsStore>()
.editorconfig_properties(location.worktree_id, location.path)
});
if let Some(editorconfig_properties) = editorconfig_properties {
let mut settings = settings.clone();
merge_with_editorconfig(&mut settings, &editorconfig_properties);
Cow::Owned(settings)
} else {
Cow::Borrowed(settings)
pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
return overrides;
}
}
&self.defaults
}
/// Returns whether inline completions are enabled for the given path.
@@ -856,7 +833,6 @@ impl AllLanguageSettings {
&self,
language: Option<&Arc<Language>>,
path: Option<&Path>,
cx: &AppContext,
) -> bool {
if let Some(path) = path {
if !self.inline_completions_enabled_for_path(path) {
@@ -864,64 +840,11 @@ impl AllLanguageSettings {
}
}
self.language(None, language.map(|l| l.name()).as_ref(), cx)
self.language(language.map(|l| l.name()).as_ref())
.show_inline_completions
}
}
fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
let max_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
MaxLineLen::Value(u) => Some(u as u32),
MaxLineLen::Off => None,
});
let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
IndentSize::Value(u) => NonZeroU32::new(u as u32),
IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
TabWidth::Value(u) => NonZeroU32::new(u as u32),
}),
});
let hard_tabs = cfg
.get::<IndentStyle>()
.map(|v| v.eq(&IndentStyle::Tabs))
.ok();
let ensure_final_newline_on_save = cfg
.get::<FinalNewline>()
.map(|v| match v {
FinalNewline::Value(b) => b,
})
.ok();
let remove_trailing_whitespace_on_save = cfg
.get::<TrimTrailingWs>()
.map(|v| match v {
TrimTrailingWs::Value(b) => b,
})
.ok();
let preferred_line_length = max_line_length;
let soft_wrap = if max_line_length.is_some() {
Some(SoftWrap::PreferredLineLength)
} else {
None
};
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}
merge(&mut settings.tab_size, tab_size);
merge(&mut settings.hard_tabs, hard_tabs);
merge(
&mut settings.remove_trailing_whitespace_on_save,
remove_trailing_whitespace_on_save,
);
merge(
&mut settings.ensure_final_newline_on_save,
ensure_final_newline_on_save,
);
merge(&mut settings.preferred_line_length, preferred_line_length);
merge(&mut settings.soft_wrap, soft_wrap);
}
/// The kind of an inlay hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlayHintKind {

View File

@@ -0,0 +1,32 @@
//! Provides support for language toolchains.
//!
//! A language can have associated toolchains,
//! which is a set of tools used to interact with the projects written in said language.
//! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
use async_trait::async_trait;
use gpui::SharedString;
/// Represents a single toolchain.
pub struct Toolchain {
/// User-facing label
pub label: SharedString,
}
#[async_trait]
pub trait ToolchainLister {
async fn list(&self) -> ToolchainList;
async fn activate(&self, _: Toolchain);
}
type DefaultIndex = usize;
pub struct ToolchainList {
toolchains: Vec<Toolchain>,
default: Option<DefaultIndex>,
}
impl ToolchainList {
pub fn toolchains(&self) -> &[Toolchain] {
&self.toolchains
}
}

View File

@@ -40,7 +40,7 @@ pub struct AnthropicSettings {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AvailableModel {
/// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc
/// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-20240620
pub name: String,
/// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel.
pub display_name: Option<String>,

View File

@@ -9,8 +9,7 @@ use gpui::{
};
use language::{LanguageServerId, LanguageServerName};
use lsp::{
notification::SetTrace, IoKind, LanguageServer, MessageType, ServerCapabilities,
SetTraceParams, TraceValue,
notification::SetTrace, IoKind, LanguageServer, MessageType, SetTraceParams, TraceValue,
};
use project::{search::SearchQuery, Project, WorktreeId};
use std::{borrow::Cow, sync::Arc};
@@ -108,7 +107,6 @@ struct LanguageServerState {
rpc_state: Option<LanguageServerRpcState>,
trace_level: TraceValue,
log_level: MessageType,
capabilities: ServerCapabilities,
io_logs_subscription: Option<lsp::Subscription>,
}
@@ -178,7 +176,6 @@ pub enum LogKind {
Trace,
#[default]
Logs,
Capabilities,
}
impl LogKind {
@@ -187,7 +184,6 @@ impl LogKind {
LogKind::Rpc => RPC_MESSAGES,
LogKind::Trace => SERVER_TRACE,
LogKind::Logs => SERVER_LOGS,
LogKind::Capabilities => SERVER_CAPABILITIES,
}
}
}
@@ -378,7 +374,6 @@ impl LogStore {
trace_level: TraceValue::Off,
log_level: MessageType::LOG,
io_logs_subscription: None,
capabilities: ServerCapabilities::default(),
}
});
@@ -389,10 +384,7 @@ impl LogStore {
server_state.worktree_id = Some(worktree_id);
}
if let Some(server) = server
.clone()
.filter(|_| server_state.io_logs_subscription.is_none())
{
if let Some(server) = server.filter(|_| server_state.io_logs_subscription.is_none()) {
let io_tx = self.io_tx.clone();
let server_id = server.server_id();
server_state.io_logs_subscription = Some(server.on_io(move |io_kind, message| {
@@ -401,11 +393,6 @@ impl LogStore {
.ok();
}));
}
if let Some(server) = server {
server_state.capabilities = server.capabilities();
}
Some(server_state)
}
@@ -490,10 +477,6 @@ impl LogStore {
Some(&self.language_servers.get(&server_id)?.trace_messages)
}
fn server_capabilities(&self, server_id: LanguageServerId) -> Option<&ServerCapabilities> {
Some(&self.language_servers.get(&server_id)?.capabilities)
}
fn server_ids_for_project<'a>(
&'a self,
lookup_project: &'a WeakModel<Project>,
@@ -619,9 +602,6 @@ impl LspLogView {
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
LogKind::Trace => this.show_trace_for_server(server_id, cx),
LogKind::Logs => this.show_logs_for_server(server_id, cx),
LogKind::Capabilities => {
this.show_capabilities_for_server(server_id, cx)
}
}
} else {
this.current_server_id = None;
@@ -638,7 +618,6 @@ impl LspLogView {
LogKind::Rpc => this.show_rpc_trace_for_server(server_id, cx),
LogKind::Trace => this.show_trace_for_server(server_id, cx),
LogKind::Logs => this.show_logs_for_server(server_id, cx),
LogKind::Capabilities => this.show_capabilities_for_server(server_id, cx),
}
}
@@ -716,33 +695,6 @@ impl LspLogView {
(editor, vec![editor_subscription, search_subscription])
}
fn editor_for_capabilities(
capabilities: ServerCapabilities,
cx: &mut ViewContext<Self>,
) -> (View<Editor>, Vec<Subscription>) {
let editor = cx.new_view(|cx| {
let mut editor = Editor::multi_line(cx);
editor.set_text(serde_json::to_string_pretty(&capabilities).unwrap(), cx);
editor.move_to_end(&MoveToEnd, cx);
editor.set_read_only(true);
editor.set_show_inline_completions(Some(false), cx);
editor
});
let editor_subscription = cx.subscribe(
&editor,
|_, _, event: &EditorEvent, cx: &mut ViewContext<'_, LspLogView>| {
cx.emit(event.clone())
},
);
let search_subscription = cx.subscribe(
&editor,
|_, _, event: &SearchEvent, cx: &mut ViewContext<'_, LspLogView>| {
cx.emit(event.clone())
},
);
(editor, vec![editor_subscription, search_subscription])
}
pub(crate) fn menu_items<'a>(&'a self, cx: &'a AppContext) -> Option<Vec<LogMenuItem>> {
let log_store = self.log_store.read(cx);
@@ -929,7 +881,6 @@ impl LspLogView {
cx.notify();
}
}
fn update_trace_level(
&self,
server_id: LanguageServerId,
@@ -948,25 +899,6 @@ impl LspLogView {
.ok();
}
}
fn show_capabilities_for_server(
&mut self,
server_id: LanguageServerId,
cx: &mut ViewContext<Self>,
) {
let capabilities = self.log_store.read(cx).server_capabilities(server_id);
if let Some(capabilities) = capabilities {
self.current_server_id = Some(server_id);
self.active_entry_kind = LogKind::Capabilities;
let (editor, editor_subscriptions) =
Self::editor_for_capabilities(capabilities.clone(), cx);
self.editor = editor;
self.editor_subscriptions = editor_subscriptions;
cx.notify();
}
cx.focus(&self.focus_handle);
}
}
fn log_filter<T: Message>(line: &T, cmp: <T as Message>::Level) -> Option<&str> {
@@ -1035,7 +967,6 @@ impl Item for LspLogView {
LogKind::Rpc => new_view.show_rpc_trace_for_server(server_id, cx),
LogKind::Trace => new_view.show_trace_for_server(server_id, cx),
LogKind::Logs => new_view.show_logs_for_server(server_id, cx),
LogKind::Capabilities => new_view.show_capabilities_for_server(server_id, cx),
}
}
new_view
@@ -1237,13 +1168,6 @@ impl Render for LspLogToolbarItemView {
view.show_rpc_trace_for_server(row.server_id, cx);
}),
);
menu = menu.entry(
SERVER_CAPABILITIES,
None,
cx.handler_for(&log_view, move |view, cx| {
view.show_capabilities_for_server(row.server_id, cx);
}),
);
if server_selected && row.selected_entry == LogKind::Rpc {
let selected_ix = menu.select_last();
debug_assert_eq!(
@@ -1393,7 +1317,6 @@ impl Render for LspLogToolbarItemView {
const RPC_MESSAGES: &str = "RPC Messages";
const SERVER_LOGS: &str = "Server Logs";
const SERVER_TRACE: &str = "Server Trace";
const SERVER_CAPABILITIES: &str = "Server Capabilities";
impl Default for LspLogToolbarItemView {
fn default() -> Self {

View File

@@ -4,7 +4,7 @@
declarator: (function_declarator
declarator: (identifier) @run
)
) @_c-main
) @c-main
(#eq? @run "main")
(#set! tag c-main)
)

View File

@@ -1,6 +1,6 @@
name = "CSS"
grammar = "css"
path_suffixes = ["css", "postcss"]
path_suffixes = ["css"]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },

View File

@@ -405,6 +405,7 @@ fn adjust_runs(
runs
}
#[derive(Default)]
pub(crate) struct GoContextProvider;
const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowed("GO_PACKAGE"));

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