Remove 2 suffix for lsp, language, fuzzy

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-01-03 11:58:02 -08:00
parent c5a1950522
commit 9f99e58834
73 changed files with 652 additions and 15589 deletions

206
Cargo.lock generated
View File

@ -11,7 +11,7 @@ dependencies = [
"editor",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"project",
"settings2",
"smallvec",
@ -84,7 +84,7 @@ dependencies = [
"futures 0.3.28",
"gpui2",
"isahc",
"language2",
"language",
"lazy_static",
"log",
"matrixmultiply",
@ -315,7 +315,7 @@ dependencies = [
"gpui2",
"indoc",
"isahc",
"language2",
"language",
"log",
"menu2",
"multi_buffer",
@ -1025,7 +1025,7 @@ dependencies = [
"editor",
"gpui2",
"itertools 0.10.5",
"language2",
"language",
"outline",
"project",
"search",
@ -1152,7 +1152,7 @@ dependencies = [
"futures 0.3.28",
"gpui2",
"image",
"language2",
"language",
"live_kit_client2",
"log",
"media",
@ -1278,7 +1278,7 @@ dependencies = [
"futures 0.3.28",
"gpui2",
"image",
"language2",
"language",
"lazy_static",
"log",
"parking_lot 0.11.2",
@ -1659,13 +1659,13 @@ dependencies = [
"gpui2",
"hyper",
"indoc",
"language2",
"language",
"lazy_static",
"lipsum",
"live_kit_client2",
"live_kit_server",
"log",
"lsp2",
"lsp",
"nanoid",
"node_runtime",
"notifications2",
@ -1719,9 +1719,9 @@ dependencies = [
"feature_flags",
"feedback",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"language",
"lazy_static",
"log",
"menu2",
@ -1787,10 +1787,10 @@ dependencies = [
"ctor",
"editor",
"env_logger",
"fuzzy2",
"fuzzy",
"go_to_line",
"gpui2",
"language2",
"language",
"menu2",
"picker",
"project",
@ -1889,9 +1889,9 @@ dependencies = [
"fs",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"log",
"lsp2",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"rpc2",
@ -1914,7 +1914,7 @@ dependencies = [
"fs2",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"settings2",
"smol",
"theme2",
@ -2438,9 +2438,9 @@ dependencies = [
"editor",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"log",
"lsp2",
"lsp",
"postage",
"project",
"schemars",
@ -2603,15 +2603,15 @@ dependencies = [
"db2",
"env_logger",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"git3",
"gpui2",
"indoc",
"itertools 0.10.5",
"language2",
"language",
"lazy_static",
"log",
"lsp2",
"lsp",
"multi_buffer",
"ordered-float 2.10.0",
"parking_lot 0.11.2",
@ -2830,7 +2830,7 @@ dependencies = [
"gpui2",
"human_bytes",
"isahc",
"language2",
"language",
"lazy_static",
"log",
"menu2",
@ -2860,9 +2860,9 @@ dependencies = [
"ctor",
"editor",
"env_logger",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"language",
"menu2",
"picker",
"postage",
@ -3250,7 +3250,7 @@ dependencies = [
name = "fuzzy"
version = "0.1.0"
dependencies = [
"gpui",
"gpui2",
"util",
]
@ -3263,14 +3263,6 @@ dependencies = [
"thread_local",
]
[[package]]
name = "fuzzy2"
version = "0.1.0"
dependencies = [
"gpui2",
"util",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -4191,61 +4183,6 @@ dependencies = [
[[package]]
name = "language"
version = "0.1.0"
dependencies = [
"anyhow",
"async-broadcast",
"async-trait",
"client",
"clock",
"collections",
"ctor",
"env_logger",
"fs",
"futures 0.3.28",
"fuzzy",
"git",
"globset",
"gpui",
"indoc",
"lazy_static",
"log",
"lsp",
"parking_lot 0.11.2",
"postage",
"pulldown-cmark",
"rand 0.8.5",
"regex",
"rpc",
"schemars",
"serde",
"serde_derive",
"serde_json",
"settings",
"similar",
"smallvec",
"smol",
"sum_tree",
"text",
"theme",
"tree-sitter",
"tree-sitter-elixir",
"tree-sitter-embedded-template",
"tree-sitter-heex",
"tree-sitter-html",
"tree-sitter-json 0.20.0",
"tree-sitter-markdown",
"tree-sitter-python",
"tree-sitter-ruby",
"tree-sitter-rust",
"tree-sitter-typescript",
"unicase",
"unindent",
"util",
]
[[package]]
name = "language2"
version = "0.1.0"
dependencies = [
"anyhow",
"async-broadcast",
@ -4256,14 +4193,14 @@ dependencies = [
"ctor",
"env_logger",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"git3",
"globset",
"gpui2",
"indoc",
"lazy_static",
"log",
"lsp2",
"lsp",
"parking_lot 0.11.2",
"postage",
"pulldown-cmark",
@ -4303,9 +4240,9 @@ version = "0.1.0"
dependencies = [
"anyhow",
"editor",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"language",
"picker",
"project",
"settings2",
@ -4326,8 +4263,8 @@ dependencies = [
"env_logger",
"futures 0.3.28",
"gpui2",
"language2",
"lsp2",
"language",
"lsp",
"project",
"serde",
"settings2",
@ -4599,7 +4536,7 @@ dependencies = [
"ctor",
"env_logger",
"futures 0.3.28",
"gpui",
"gpui2",
"log",
"lsp-types",
"parking_lot 0.11.2",
@ -4624,29 +4561,6 @@ dependencies = [
"url",
]
[[package]]
name = "lsp2"
version = "0.1.0"
dependencies = [
"anyhow",
"async-pipe",
"collections",
"ctor",
"env_logger",
"futures 0.3.28",
"gpui2",
"log",
"lsp-types",
"parking_lot 0.11.2",
"postage",
"serde",
"serde_derive",
"serde_json",
"smol",
"unindent",
"util",
]
[[package]]
name = "mach"
version = "0.3.2"
@ -4954,10 +4868,10 @@ dependencies = [
"gpui2",
"indoc",
"itertools 0.10.5",
"language2",
"language",
"lazy_static",
"log",
"lsp2",
"lsp",
"ordered-float 2.10.0",
"parking_lot 0.11.2",
"postage",
@ -5563,9 +5477,9 @@ name = "outline"
version = "0.1.0"
dependencies = [
"editor",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"language",
"ordered-float 2.10.0",
"picker",
"postage",
@ -5994,9 +5908,9 @@ dependencies = [
"fs2",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"log",
"lsp2",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"serde",
@ -6106,17 +6020,17 @@ dependencies = [
"fs2",
"fsevent",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"git2",
"git3",
"globset",
"gpui2",
"ignore",
"itertools 0.10.5",
"language2",
"language",
"lazy_static",
"log",
"lsp2",
"lsp",
"node_runtime",
"parking_lot 0.11.2",
"postage",
@ -6154,7 +6068,7 @@ dependencies = [
"editor",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"menu2",
"postage",
"pretty_assertions",
@ -6180,10 +6094,10 @@ dependencies = [
"anyhow",
"editor",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"lsp2",
"language",
"lsp",
"ordered-float 2.10.0",
"picker",
"postage",
@ -6531,9 +6445,9 @@ version = "0.1.0"
dependencies = [
"editor",
"futures 0.3.28",
"fuzzy2",
"fuzzy",
"gpui2",
"language2",
"language",
"ordered-float 2.10.0",
"picker",
"postage",
@ -6730,7 +6644,7 @@ dependencies = [
"collections",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"lazy_static",
"pulldown-cmark",
"smallvec",
@ -7356,7 +7270,7 @@ dependencies = [
"editor",
"futures 0.3.28",
"gpui2",
"language2",
"language",
"log",
"menu2",
"postage",
@ -7466,7 +7380,7 @@ dependencies = [
"futures 0.3.28",
"globset",
"gpui2",
"language2",
"language",
"lazy_static",
"log",
"ndarray",
@ -8280,11 +8194,11 @@ dependencies = [
"clap 4.4.4",
"dialoguer",
"editor",
"fuzzy2",
"fuzzy",
"gpui2",
"indoc",
"itertools 0.11.0",
"language2",
"language",
"log",
"menu2",
"picker",
@ -8646,7 +8560,7 @@ dependencies = [
"futures 0.3.28",
"gpui2",
"itertools 0.10.5",
"language2",
"language",
"lazy_static",
"libc",
"mio-extras",
@ -8791,7 +8705,7 @@ dependencies = [
"editor",
"feature_flags",
"fs2",
"fuzzy2",
"fuzzy",
"gpui2",
"log",
"parking_lot 0.11.2",
@ -9861,7 +9775,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"fs2",
"fuzzy2",
"fuzzy",
"gpui2",
"picker",
"ui2",
@ -9890,9 +9804,9 @@ dependencies = [
"gpui2",
"indoc",
"itertools 0.10.5",
"language2",
"language",
"log",
"lsp2",
"lsp",
"nvim-rs",
"parking_lot 0.11.2",
"project",
@ -10305,7 +10219,7 @@ dependencies = [
"db2",
"editor",
"fs2",
"fuzzy2",
"fuzzy",
"gpui2",
"install_cli",
"log",
@ -10579,7 +10493,7 @@ dependencies = [
"indoc",
"install_cli",
"itertools 0.10.5",
"language2",
"language",
"lazy_static",
"log",
"node_runtime",
@ -10722,13 +10636,13 @@ dependencies = [
"install_cli",
"isahc",
"journal",
"language2",
"language",
"language_selector",
"language_tools",
"lazy_static",
"libc",
"log",
"lsp2",
"lsp",
"menu2",
"node_runtime",
"notifications2",

View File

@ -38,7 +38,6 @@ members = [
"crates/fs2",
"crates/fsevent",
"crates/fuzzy",
"crates/fuzzy2",
"crates/git",
"crates/go_to_line",
"crates/gpui",
@ -49,13 +48,11 @@ members = [
"crates/journal",
"crates/journal",
"crates/language",
"crates/language2",
"crates/language_selector",
"crates/language_tools",
"crates/live_kit_client",
"crates/live_kit_server",
"crates/lsp",
"crates/lsp2",
"crates/media",
"crates/menu",
"crates/menu2",

View File

@ -11,7 +11,7 @@ doctest = false
[dependencies]
auto_update = { path = "../auto_update" }
editor = { path = "../editor" }
language = { path = "../language2", package = "language2" }
language = { path = "../language" }
gpui = { path = "../gpui2", package = "gpui2" }
project = { path = "../project" }
settings = { path = "../settings2", package = "settings2" }

View File

@ -14,7 +14,7 @@ test-support = []
[dependencies]
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
async-trait.workspace = true
anyhow.workspace = true
futures.workspace = true

View File

@ -15,7 +15,7 @@ collections = { path = "../collections"}
editor = { path = "../editor" }
fs = { package = "fs2", path = "../fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
menu = { package = "menu2", path = "../menu2" }
multi_buffer = { path = "../multi_buffer" }
project = { path = "../project" }

View File

@ -13,7 +13,7 @@ collections = { path = "../collections" }
editor = { path = "../editor" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
project = { path = "../project" }
search = { path = "../search" }
settings = { package = "settings2", path = "../settings2" }

View File

@ -26,7 +26,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
log.workspace = true
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2" }
fs = { package = "fs2", path = "../fs2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
media = { path = "../media" }
project = { path = "../project" }
settings = { package = "settings2", path = "../settings2" }
@ -46,7 +46,7 @@ smallvec.workspace = true
[dev-dependencies]
client = { package = "client2", path = "../client2", features = ["test-support"] }
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }

View File

@ -19,7 +19,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }
rpc = { package = "rpc2", path = "../rpc2" }
text = { package = "text2", path = "../text2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
settings = { package = "settings2", path = "../settings2" }
feature_flags = { path = "../feature_flags" }
sum_tree = { path = "../sum_tree" }

View File

@ -67,11 +67,11 @@ call = { package = "call2", path = "../call2", features = ["test-support"] }
client = { package = "client2", path = "../client2", features = ["test-support"] }
channel = { package = "channel2", path = "../channel2" }
editor = { path = "../editor", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
git = { package = "git3", path = "../git3", features = ["test-support"] }
live_kit_client = { package = "live_kit_client2", path = "../live_kit_client2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
node_runtime = { path = "../node_runtime" }
notifications = { package = "notifications2", path = "../notifications2", features = ["test-support"] }

View File

@ -33,9 +33,9 @@ collections = { path = "../collections" }
# drag_and_drop = { path = "../drag_and_drop" }
editor = { path = "../editor" }
feedback = { path = "../feedback" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
menu = { package = "menu2", path = "../menu2" }
notifications = { package = "notifications2", path = "../notifications2" }
rich_text = { path = "../rich_text" }

View File

@ -11,7 +11,7 @@ doctest = false
[dependencies]
collections = { path = "../collections" }
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
picker = { path = "../picker" }
project = { path = "../project" }
@ -27,7 +27,7 @@ serde.workspace = true
[dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { package="language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
menu = { package = "menu2", path = "../menu2" }
go_to_line = { path = "../go_to_line" }

View File

@ -22,10 +22,10 @@ test-support = [
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
lsp = { package = "lsp2", path = "../lsp2" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime"}
util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" }
@ -44,8 +44,8 @@ clock = { path = "../clock" }
collections = { path = "../collections", features = ["test-support"] }
fs = { path = "../fs", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@ -14,7 +14,7 @@ editor = { path = "../editor" }
fs = { package = "fs2", path = "../fs2" }
zed_actions = { path = "../zed_actions"}
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }

View File

@ -13,8 +13,8 @@ collections = { path = "../collections" }
editor = { path = "../editor" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
language = { package = "language2", path = "../language2" }
lsp = { package = "lsp2", path = "../lsp2" }
language = { path = "../language" }
lsp = { path = "../lsp" }
project = { path = "../project" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
@ -33,8 +33,8 @@ postage.workspace = true
[dev-dependencies]
client = { package = "client2", path = "../client2", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
workspace = {path = "../workspace", features = ["test-support"] }
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }

View File

@ -29,11 +29,11 @@ copilot = { path = "../copilot" }
db = { package="db2", path = "../db2" }
collections = { path = "../collections" }
# context_menu = { path = "../context_menu" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
git = { package = "git3", path = "../git3" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
lsp = { package = "lsp2", path = "../lsp2" }
language = { path = "../language" }
lsp = { path = "../lsp" }
multi_buffer = { path = "../multi_buffer" }
project = { path = "../project" }
rpc = { package = "rpc2", path = "../rpc2" }
@ -74,8 +74,8 @@ tree-sitter-typescript = { workspace = true, optional = true }
[dev-dependencies]
copilot = { path = "../copilot", features = ["test-support"] }
text = { package="text2", path = "../text2", features = ["test-support"] }
language = { package="language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }

View File

@ -15,7 +15,7 @@ client = { package = "client2", path = "../client2" }
db = { package = "db2", path = "../db2" }
editor = { path = "../editor" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
menu = { package = "menu2", path = "../menu2" }
project = { path = "../project" }
search = { path = "../search" }

View File

@ -11,7 +11,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
collections = { path = "../collections" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
picker = { path = "../picker" }
@ -28,7 +28,7 @@ serde.workspace = true
[dev-dependencies]
editor = { path = "../editor", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }

View File

@ -9,5 +9,5 @@ path = "src/fuzzy.rs"
doctest = false
[dependencies]
gpui = { path = "../gpui" }
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }

View File

@ -1,3 +1,4 @@
use gpui::BackgroundExecutor;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@ -5,8 +6,6 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use gpui::executor;
use crate::{
matcher::{Match, MatchCandidate, Matcher},
CharBag,
@ -135,7 +134,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
background: Arc<executor::Background>,
executor: BackgroundExecutor,
) -> Vec<PathMatch> {
let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
if path_count == 0 {
@ -149,13 +148,13 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
let query = &query;
let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = background.num_cpus().min(path_count);
let num_cpus = executor.num_cpus().min(path_count);
let segment_size = (path_count + num_cpus - 1) / num_cpus;
let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results))
.collect::<Vec<_>>();
background
executor
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let relative_to = relative_to.clone();

View File

@ -1,15 +1,15 @@
use std::{
borrow::Cow,
cmp::{self, Ordering},
sync::{atomic::AtomicBool, Arc},
};
use gpui::executor;
use crate::{
matcher::{Match, MatchCandidate, Matcher},
CharBag,
};
use gpui::BackgroundExecutor;
use std::{
borrow::Cow,
cmp::{self, Ordering},
iter,
ops::Range,
sync::atomic::AtomicBool,
};
#[derive(Clone, Debug)]
pub struct StringMatchCandidate {
@ -56,6 +56,32 @@ pub struct StringMatch {
pub string: String,
}
impl StringMatch {
pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
let mut positions = self.positions.iter().peekable();
iter::from_fn(move || {
while let Some(start) = positions.next().copied() {
let mut end = start + self.char_len_at_index(start);
while let Some(next_start) = positions.peek() {
if end == **next_start {
end += self.char_len_at_index(end);
positions.next();
} else {
break;
}
}
return Some(start..end);
}
None
})
}
fn char_len_at_index(&self, ix: usize) -> usize {
self.string[ix..].chars().next().unwrap().len_utf8()
}
}
impl PartialEq for StringMatch {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
@ -85,7 +111,7 @@ pub async fn match_strings(
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
background: Arc<executor::Background>,
executor: BackgroundExecutor,
) -> Vec<StringMatch> {
if candidates.is_empty() || max_results == 0 {
return Default::default();
@ -110,13 +136,13 @@ pub async fn match_strings(
let query = &query;
let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = background.num_cpus().min(candidates.len());
let num_cpus = executor.num_cpus().min(candidates.len());
let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results.min(candidates.len())))
.collect::<Vec<_>>();
background
executor
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let cancel_flag = &cancel_flag;

View File

@ -1,13 +0,0 @@
[package]
name = "fuzzy2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/fuzzy2.rs"
doctest = false
[dependencies]
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }

View File

@ -1,63 +0,0 @@
use std::iter::FromIterator;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct CharBag(u64);
impl CharBag {
pub fn is_superset(self, other: CharBag) -> bool {
self.0 & other.0 == other.0
}
fn insert(&mut self, c: char) {
let c = c.to_ascii_lowercase();
if ('a'..='z').contains(&c) {
let mut count = self.0;
let idx = c as u8 - b'a';
count >>= idx * 2;
count = ((count << 1) | 1) & 3;
count <<= idx * 2;
self.0 |= count;
} else if ('0'..='9').contains(&c) {
let idx = c as u8 - b'0';
self.0 |= 1 << (idx + 52);
} else if c == '-' {
self.0 |= 1 << 62;
}
}
}
impl Extend<char> for CharBag {
fn extend<T: IntoIterator<Item = char>>(&mut self, iter: T) {
for c in iter {
self.insert(c);
}
}
}
impl FromIterator<char> for CharBag {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
let mut result = Self::default();
result.extend(iter);
result
}
}
impl From<&str> for CharBag {
fn from(s: &str) -> Self {
let mut bag = Self(0);
for c in s.chars() {
bag.insert(c);
}
bag
}
}
impl From<&[char]> for CharBag {
fn from(chars: &[char]) -> Self {
let mut bag = Self(0);
for c in chars {
bag.insert(*c);
}
bag
}
}

View File

@ -1,10 +0,0 @@
mod char_bag;
mod matcher;
mod paths;
mod strings;
pub use char_bag::CharBag;
pub use paths::{
match_fixed_path_set, match_path_sets, PathMatch, PathMatchCandidate, PathMatchCandidateSet,
};
pub use strings::{match_strings, StringMatch, StringMatchCandidate};

View File

@ -1,464 +0,0 @@
use std::{
borrow::Cow,
sync::atomic::{self, AtomicBool},
};
use crate::CharBag;
const BASE_DISTANCE_PENALTY: f64 = 0.6;
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
const MIN_DISTANCE_PENALTY: f64 = 0.2;
pub struct Matcher<'a> {
query: &'a [char],
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
min_score: f64,
match_positions: Vec<usize>,
last_positions: Vec<usize>,
score_matrix: Vec<Option<f64>>,
best_position_matrix: Vec<usize>,
}
pub trait Match: Ord {
fn score(&self) -> f64;
fn set_positions(&mut self, positions: Vec<usize>);
}
pub trait MatchCandidate {
fn has_chars(&self, bag: CharBag) -> bool;
fn to_string(&self) -> Cow<'_, str>;
}
impl<'a> Matcher<'a> {
pub fn new(
query: &'a [char],
lowercase_query: &'a [char],
query_char_bag: CharBag,
smart_case: bool,
max_results: usize,
) -> Self {
Self {
query,
lowercase_query,
query_char_bag,
min_score: 0.0,
last_positions: vec![0; query.len()],
match_positions: vec![0; query.len()],
score_matrix: Vec::new(),
best_position_matrix: Vec::new(),
smart_case,
max_results,
}
}
pub fn match_candidates<C: MatchCandidate, R, F>(
&mut self,
prefix: &[char],
lowercase_prefix: &[char],
candidates: impl Iterator<Item = C>,
results: &mut Vec<R>,
cancel_flag: &AtomicBool,
build_match: F,
) where
R: Match,
F: Fn(&C, f64) -> R,
{
let mut candidate_chars = Vec::new();
let mut lowercase_candidate_chars = Vec::new();
for candidate in candidates {
if !candidate.has_chars(self.query_char_bag) {
continue;
}
if cancel_flag.load(atomic::Ordering::Relaxed) {
break;
}
candidate_chars.clear();
lowercase_candidate_chars.clear();
for c in candidate.to_string().chars() {
candidate_chars.push(c);
lowercase_candidate_chars.push(c.to_ascii_lowercase());
}
if !self.find_last_positions(lowercase_prefix, &lowercase_candidate_chars) {
continue;
}
let matrix_len = self.query.len() * (prefix.len() + candidate_chars.len());
self.score_matrix.clear();
self.score_matrix.resize(matrix_len, None);
self.best_position_matrix.clear();
self.best_position_matrix.resize(matrix_len, 0);
let score = self.score_match(
&candidate_chars,
&lowercase_candidate_chars,
prefix,
lowercase_prefix,
);
if score > 0.0 {
let mut mat = build_match(&candidate, score);
if let Err(i) = results.binary_search_by(|m| mat.cmp(m)) {
if results.len() < self.max_results {
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
} else if i < results.len() {
results.pop();
mat.set_positions(self.match_positions.clone());
results.insert(i, mat);
}
if results.len() == self.max_results {
self.min_score = results.last().unwrap().score();
}
}
}
}
}
fn find_last_positions(
&mut self,
lowercase_prefix: &[char],
lowercase_candidate: &[char],
) -> bool {
let mut lowercase_prefix = lowercase_prefix.iter();
let mut lowercase_candidate = lowercase_candidate.iter();
for (i, char) in self.lowercase_query.iter().enumerate().rev() {
if let Some(j) = lowercase_candidate.rposition(|c| c == char) {
self.last_positions[i] = j + lowercase_prefix.len();
} else if let Some(j) = lowercase_prefix.rposition(|c| c == char) {
self.last_positions[i] = j;
} else {
return false;
}
}
true
}
fn score_match(
&mut self,
path: &[char],
path_cased: &[char],
prefix: &[char],
lowercase_prefix: &[char],
) -> f64 {
let score = self.recursive_score_match(
path,
path_cased,
prefix,
lowercase_prefix,
0,
0,
self.query.len() as f64,
) * self.query.len() as f64;
if score <= 0.0 {
return 0.0;
}
let path_len = prefix.len() + path.len();
let mut cur_start = 0;
let mut byte_ix = 0;
let mut char_ix = 0;
for i in 0..self.query.len() {
let match_char_ix = self.best_position_matrix[i * path_len + cur_start];
while char_ix < match_char_ix {
let ch = prefix
.get(char_ix)
.or_else(|| path.get(char_ix - prefix.len()))
.unwrap();
byte_ix += ch.len_utf8();
char_ix += 1;
}
cur_start = match_char_ix + 1;
self.match_positions[i] = byte_ix;
}
score
}
#[allow(clippy::too_many_arguments)]
fn recursive_score_match(
&mut self,
path: &[char],
path_cased: &[char],
prefix: &[char],
lowercase_prefix: &[char],
query_idx: usize,
path_idx: usize,
cur_score: f64,
) -> f64 {
if query_idx == self.query.len() {
return 1.0;
}
let path_len = prefix.len() + path.len();
if let Some(memoized) = self.score_matrix[query_idx * path_len + path_idx] {
return memoized;
}
let mut score = 0.0;
let mut best_position = 0;
let query_char = self.lowercase_query[query_idx];
let limit = self.last_positions[query_idx];
let mut last_slash = 0;
for j in path_idx..=limit {
let path_char = if j < prefix.len() {
lowercase_prefix[j]
} else {
path_cased[j - prefix.len()]
};
let is_path_sep = path_char == '/' || path_char == '\\';
if query_idx == 0 && is_path_sep {
last_slash = j;
}
if query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\') {
let curr = if j < prefix.len() {
prefix[j]
} else {
path[j - prefix.len()]
};
let mut char_score = 1.0;
if j > path_idx {
let last = if j - 1 < prefix.len() {
prefix[j - 1]
} else {
path[j - 1 - prefix.len()]
};
if last == '/' {
char_score = 0.9;
} else if (last == '-' || last == '_' || last == ' ' || last.is_numeric())
|| (last.is_lowercase() && curr.is_uppercase())
{
char_score = 0.8;
} else if last == '.' {
char_score = 0.7;
} else if query_idx == 0 {
char_score = BASE_DISTANCE_PENALTY;
} else {
char_score = MIN_DISTANCE_PENALTY.max(
BASE_DISTANCE_PENALTY
- (j - path_idx - 1) as f64 * ADDITIONAL_DISTANCE_PENALTY,
);
}
}
// Apply a severe penalty if the case doesn't match.
// This will make the exact matches have higher score than the case-insensitive and the
// path insensitive matches.
if (self.smart_case || curr == '/') && self.query[query_idx] != curr {
char_score *= 0.001;
}
let mut multiplier = char_score;
// Scale the score based on how deep within the path we found the match.
if query_idx == 0 {
multiplier /= ((prefix.len() + path.len()) - last_slash) as f64;
}
let mut next_score = 1.0;
if self.min_score > 0.0 {
next_score = cur_score * multiplier;
// Scores only decrease. If we can't pass the previous best, bail
if next_score < self.min_score {
// Ensure that score is non-zero so we use it in the memo table.
if score == 0.0 {
score = 1e-18;
}
continue;
}
}
let new_score = self.recursive_score_match(
path,
path_cased,
prefix,
lowercase_prefix,
query_idx + 1,
j + 1,
next_score,
) * multiplier;
if new_score > score {
score = new_score;
best_position = j;
// Optimization: can't score better than 1.
if new_score == 1.0 {
break;
}
}
}
}
if best_position != 0 {
self.best_position_matrix[query_idx * path_len + path_idx] = best_position;
}
self.score_matrix[query_idx * path_len + path_idx] = Some(score);
score
}
}
#[cfg(test)]
mod tests {
use crate::{PathMatch, PathMatchCandidate};
use super::*;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
#[test]
fn test_get_last_positions() {
let mut query: &[char] = &['d', 'c'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(!result);
query = &['c', 'd'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['a', 'b', 'c'], &['b', 'd', 'e', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![2, 4]);
query = &['z', '/', 'z', 'f'];
let mut matcher = Matcher::new(query, query, query.into(), false, 10);
let result = matcher.find_last_positions(&['z', 'e', 'd', '/'], &['z', 'e', 'd', '/', 'f']);
assert!(result);
assert_eq!(matcher.last_positions, vec![0, 3, 4, 8]);
}
#[test]
fn test_match_path_entries() {
let paths = vec![
"",
"a",
"ab",
"abC",
"abcd",
"alphabravocharlie",
"AlphaBravoCharlie",
"thisisatestdir",
"/////ThisIsATestDir",
"/this/is/a/test/dir",
"/test/tiatd",
];
assert_eq!(
match_single_path_query("abc", false, &paths),
vec![
("abC", vec![0, 1, 2]),
("abcd", vec![0, 1, 2]),
("AlphaBravoCharlie", vec![0, 5, 10]),
("alphabravocharlie", vec![4, 5, 10]),
]
);
assert_eq!(
match_single_path_query("t/i/a/t/d", false, &paths),
vec![("/this/is/a/test/dir", vec![1, 5, 6, 8, 9, 10, 11, 15, 16]),]
);
assert_eq!(
match_single_path_query("tiatd", false, &paths),
vec![
("/test/tiatd", vec![6, 7, 8, 9, 10]),
("/this/is/a/test/dir", vec![1, 6, 9, 11, 16]),
("/////ThisIsATestDir", vec![5, 9, 11, 12, 16]),
("thisisatestdir", vec![0, 2, 6, 7, 11]),
]
);
}
#[test]
fn test_match_multibyte_path_entries() {
let paths = vec!["aαbβ/cγ", "αβγδ/bcde", "c1⃣2⃣3⃣/d4⃣5⃣6⃣/e7⃣8⃣9⃣/f", "/d/🆒/h"];
assert_eq!("1".len(), 7);
assert_eq!(
match_single_path_query("bcd", false, &paths),
vec![
("αβγδ/bcde", vec![9, 10, 11]),
("aαbβ/cγ", vec![3, 7, 10]),
]
);
assert_eq!(
match_single_path_query("cde", false, &paths),
vec![
("αβγδ/bcde", vec![10, 11, 12]),
("c1⃣2⃣3⃣/d4⃣5⃣6⃣/e7⃣8⃣9⃣/f", vec![0, 23, 46]),
]
);
}
fn match_single_path_query<'a>(
query: &str,
smart_case: bool,
paths: &[&'a str],
) -> Vec<(&'a str, Vec<usize>)> {
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
let query_chars = CharBag::from(&lowercase_query[..]);
let path_arcs: Vec<Arc<Path>> = paths
.iter()
.map(|path| Arc::from(PathBuf::from(path)))
.collect::<Vec<_>>();
let mut path_entries = Vec::new();
for (i, path) in paths.iter().enumerate() {
let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>();
let char_bag = CharBag::from(lowercase_path.as_slice());
path_entries.push(PathMatchCandidate {
char_bag,
path: &path_arcs[i],
});
}
let mut matcher = Matcher::new(&query, &lowercase_query, query_chars, smart_case, 100);
let cancel_flag = AtomicBool::new(false);
let mut results = Vec::new();
matcher.match_candidates(
&[],
&[],
path_entries.into_iter(),
&mut results,
&cancel_flag,
|candidate, score| PathMatch {
score,
worktree_id: 0,
positions: Vec::new(),
path: Arc::from(candidate.path),
path_prefix: "".into(),
distance_to_relative_ancestor: usize::MAX,
},
);
results
.into_iter()
.map(|result| {
(
paths
.iter()
.copied()
.find(|p| result.path.as_ref() == Path::new(p))
.unwrap(),
result.positions,
)
})
.collect()
}
}

View File

@ -1,257 +0,0 @@
use gpui::BackgroundExecutor;
use std::{
borrow::Cow,
cmp::{self, Ordering},
path::Path,
sync::{atomic::AtomicBool, Arc},
};
use crate::{
matcher::{Match, MatchCandidate, Matcher},
CharBag,
};
#[derive(Clone, Debug)]
pub struct PathMatchCandidate<'a> {
pub path: &'a Path,
pub char_bag: CharBag,
}
#[derive(Clone, Debug)]
pub struct PathMatch {
pub score: f64,
pub positions: Vec<usize>,
pub worktree_id: usize,
pub path: Arc<Path>,
pub path_prefix: Arc<str>,
/// Number of steps removed from a shared parent with the relative path
/// Used to order closer paths first in the search list
pub distance_to_relative_ancestor: usize,
}
pub trait PathMatchCandidateSet<'a>: Send + Sync {
type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
fn id(&self) -> usize;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn prefix(&self) -> Arc<str>;
fn candidates(&'a self, start: usize) -> Self::Candidates;
}
impl Match for PathMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl<'a> MatchCandidate for PathMatchCandidate<'a> {
fn has_chars(&self, bag: CharBag) -> bool {
self.char_bag.is_superset(bag)
}
fn to_string(&self) -> Cow<'a, str> {
self.path.to_string_lossy()
}
}
impl PartialEq for PathMatch {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for PathMatch {}
impl PartialOrd for PathMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PathMatch {
fn cmp(&self, other: &Self) -> Ordering {
self.score
.partial_cmp(&other.score)
.unwrap_or(Ordering::Equal)
.then_with(|| self.worktree_id.cmp(&other.worktree_id))
.then_with(|| {
other
.distance_to_relative_ancestor
.cmp(&self.distance_to_relative_ancestor)
})
.then_with(|| self.path.cmp(&other.path))
}
}
pub fn match_fixed_path_set(
candidates: Vec<PathMatchCandidate>,
worktree_id: usize,
query: &str,
smart_case: bool,
max_results: usize,
) -> Vec<PathMatch> {
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
let query_char_bag = CharBag::from(&lowercase_query[..]);
let mut matcher = Matcher::new(
&query,
&lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut results = Vec::new();
matcher.match_candidates(
&[],
&[],
candidates.into_iter(),
&mut results,
&AtomicBool::new(false),
|candidate, score| PathMatch {
score,
worktree_id,
positions: Vec::new(),
path: Arc::from(candidate.path),
path_prefix: Arc::from(""),
distance_to_relative_ancestor: usize::MAX,
},
);
results
}
pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
candidate_sets: &'a [Set],
query: &str,
relative_to: Option<Arc<Path>>,
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
executor: BackgroundExecutor,
) -> Vec<PathMatch> {
let path_count: usize = candidate_sets.iter().map(|s| s.len()).sum();
if path_count == 0 {
return Vec::new();
}
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
let lowercase_query = &lowercase_query;
let query = &query;
let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = executor.num_cpus().min(path_count);
let segment_size = (path_count + num_cpus - 1) / num_cpus;
let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results))
.collect::<Vec<_>>();
executor
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let relative_to = relative_to.clone();
scope.spawn(async move {
let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size;
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
let mut tree_start = 0;
for candidate_set in candidate_sets {
let tree_end = tree_start + candidate_set.len();
if tree_start < segment_end && segment_start < tree_end {
let start = cmp::max(tree_start, segment_start) - tree_start;
let end = cmp::min(tree_end, segment_end) - tree_start;
let candidates = candidate_set.candidates(start).take(end - start);
let worktree_id = candidate_set.id();
let prefix = candidate_set.prefix().chars().collect::<Vec<_>>();
let lowercase_prefix = prefix
.iter()
.map(|c| c.to_ascii_lowercase())
.collect::<Vec<_>>();
matcher.match_candidates(
&prefix,
&lowercase_prefix,
candidates,
results,
cancel_flag,
|candidate, score| PathMatch {
score,
worktree_id,
positions: Vec::new(),
path: Arc::from(candidate.path),
path_prefix: candidate_set.prefix(),
distance_to_relative_ancestor: relative_to.as_ref().map_or(
usize::MAX,
|relative_to| {
distance_between_paths(
candidate.path.as_ref(),
relative_to.as_ref(),
)
},
),
},
);
}
if tree_end >= segment_end {
break;
}
tree_start = tree_end;
}
})
}
})
.await;
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
}
results
}
/// Compute the distance from a given path to some other path
/// If there is no shared path, returns usize::MAX
fn distance_between_paths(path: &Path, relative_to: &Path) -> usize {
let mut path_components = path.components();
let mut relative_components = relative_to.components();
while path_components
.next()
.zip(relative_components.next())
.map(|(path_component, relative_component)| path_component == relative_component)
.unwrap_or_default()
{}
path_components.count() + relative_components.count() + 1
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::distance_between_paths;
#[test]
fn test_distance_between_paths_empty() {
distance_between_paths(Path::new(""), Path::new(""));
}
}

View File

@ -1,187 +0,0 @@
use crate::{
matcher::{Match, MatchCandidate, Matcher},
CharBag,
};
use gpui::BackgroundExecutor;
use std::{
borrow::Cow,
cmp::{self, Ordering},
iter,
ops::Range,
sync::atomic::AtomicBool,
};
#[derive(Clone, Debug)]
pub struct StringMatchCandidate {
pub id: usize,
pub string: String,
pub char_bag: CharBag,
}
impl Match for StringMatch {
fn score(&self) -> f64 {
self.score
}
fn set_positions(&mut self, positions: Vec<usize>) {
self.positions = positions;
}
}
impl StringMatchCandidate {
pub fn new(id: usize, string: String) -> Self {
Self {
id,
char_bag: CharBag::from(string.as_str()),
string,
}
}
}
impl<'a> MatchCandidate for &'a StringMatchCandidate {
fn has_chars(&self, bag: CharBag) -> bool {
self.char_bag.is_superset(bag)
}
fn to_string(&self) -> Cow<'a, str> {
self.string.as_str().into()
}
}
#[derive(Clone, Debug)]
pub struct StringMatch {
pub candidate_id: usize,
pub score: f64,
pub positions: Vec<usize>,
pub string: String,
}
impl StringMatch {
pub fn ranges<'a>(&'a self) -> impl 'a + Iterator<Item = Range<usize>> {
let mut positions = self.positions.iter().peekable();
iter::from_fn(move || {
while let Some(start) = positions.next().copied() {
let mut end = start + self.char_len_at_index(start);
while let Some(next_start) = positions.peek() {
if end == **next_start {
end += self.char_len_at_index(end);
positions.next();
} else {
break;
}
}
return Some(start..end);
}
None
})
}
fn char_len_at_index(&self, ix: usize) -> usize {
self.string[ix..].chars().next().unwrap().len_utf8()
}
}
impl PartialEq for StringMatch {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for StringMatch {}
impl PartialOrd for StringMatch {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StringMatch {
fn cmp(&self, other: &Self) -> Ordering {
self.score
.partial_cmp(&other.score)
.unwrap_or(Ordering::Equal)
.then_with(|| self.candidate_id.cmp(&other.candidate_id))
}
}
pub async fn match_strings(
candidates: &[StringMatchCandidate],
query: &str,
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
executor: BackgroundExecutor,
) -> Vec<StringMatch> {
if candidates.is_empty() || max_results == 0 {
return Default::default();
}
if query.is_empty() {
return candidates
.iter()
.map(|candidate| StringMatch {
candidate_id: candidate.id,
score: 0.,
positions: Default::default(),
string: candidate.string.clone(),
})
.collect();
}
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
let lowercase_query = &lowercase_query;
let query = &query;
let query_char_bag = CharBag::from(&lowercase_query[..]);
let num_cpus = executor.num_cpus().min(candidates.len());
let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
let mut segment_results = (0..num_cpus)
.map(|_| Vec::with_capacity(max_results.min(candidates.len())))
.collect::<Vec<_>>();
executor
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
let cancel_flag = &cancel_flag;
scope.spawn(async move {
let segment_start = cmp::min(segment_idx * segment_size, candidates.len());
let segment_end = cmp::min(segment_start + segment_size, candidates.len());
let mut matcher = Matcher::new(
query,
lowercase_query,
query_char_bag,
smart_case,
max_results,
);
matcher.match_candidates(
&[],
&[],
candidates[segment_start..segment_end].iter(),
results,
cancel_flag,
|candidate, score| StringMatch {
candidate_id: candidate.id,
score,
positions: Vec::new(),
string: candidate.string.to_string(),
},
);
});
}
})
.await;
let mut results = Vec::new();
for segment_result in segment_results {
if results.is_empty() {
results = segment_result;
} else {
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(a));
}
}
results
}

View File

@ -24,16 +24,15 @@ test-support = [
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy = { path = "../fuzzy" }
fs = { path = "../fs" }
git = { path = "../git" }
gpui = { path = "../gpui" }
fuzzy = { path = "../fuzzy" }
git = { package = "git3", path = "../git3" }
gpui = { package = "gpui2", path = "../gpui2" }
lsp = { path = "../lsp" }
rpc = { path = "../rpc" }
settings = { path = "../settings" }
rpc = { package = "rpc2", path = "../rpc2" }
settings = { package = "settings2", path = "../settings2" }
sum_tree = { path = "../sum_tree" }
text = { path = "../text" }
theme = { path = "../theme" }
text = { package = "text2", path = "../text2" }
theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
anyhow.workspace = true
@ -45,7 +44,6 @@ lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
regex.workspace = true
schemars.workspace = true
serde.workspace = true
@ -60,14 +58,15 @@ unicase = "2.6"
rand = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
pulldown-cmark = { version = "0.9.2", default-features = false }
[dev-dependencies]
client = { path = "../client", features = ["test-support"] }
client = { package = "client2", path = "../client2", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
text = { path = "../text", features = ["test-support"] }
settings = { path = "../settings", features = ["test-support"] }
text = { package = "text2", path = "../text2", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View File

@ -18,7 +18,8 @@ use crate::{
use anyhow::{anyhow, Result};
pub use clock::ReplicaId;
use futures::channel::oneshot;
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
use gpui::{AppContext, EventEmitter, HighlightStyle, ModelContext, Task, TaskLabel};
use lazy_static::lazy_static;
use lsp::LanguageServerId;
use parking_lot::Mutex;
use similar::{ChangeTag, TextDiff};
@ -52,14 +53,23 @@ pub use {tree_sitter_rust, tree_sitter_typescript};
pub use lsp::DiagnosticSeverity;
lazy_static! {
pub static ref BUFFER_DIFF_TASK: TaskLabel = TaskLabel::new();
}
pub struct Buffer {
text: TextBuffer,
diff_base: Option<String>,
git_diff: git::diff::BufferDiff,
file: Option<Arc<dyn File>>,
saved_version: clock::Global,
saved_version_fingerprint: RopeFingerprint,
/// The mtime of the file when this buffer was last loaded from
/// or saved to disk.
saved_mtime: SystemTime,
/// The version vector when this buffer was last loaded from
/// or saved to disk.
saved_version: clock::Global,
/// A hash of the current contents of the buffer's file.
file_fingerprint: RopeFingerprint,
transaction_depth: usize,
was_dirty_before_starting_transaction: Option<bool>,
reload_task: Option<Task<Result<()>>>,
@ -190,8 +200,8 @@ pub struct Completion {
pub old_range: Range<Anchor>,
pub new_text: String,
pub label: CodeLabel,
pub documentation: Option<Documentation>,
pub server_id: LanguageServerId,
pub documentation: Option<Documentation>,
pub lsp_completion: lsp::CompletionItem,
}
@ -422,8 +432,7 @@ impl Buffer {
.ok_or_else(|| anyhow!("missing line_ending"))?,
));
this.saved_version = proto::deserialize_version(&message.saved_version);
this.saved_version_fingerprint =
proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
this.file_fingerprint = proto::deserialize_fingerprint(&message.saved_version_fingerprint)?;
this.saved_mtime = message
.saved_mtime
.ok_or_else(|| anyhow!("invalid saved_mtime"))?
@ -439,7 +448,7 @@ impl Buffer {
diff_base: self.diff_base.as_ref().map(|h| h.to_string()),
line_ending: proto::serialize_line_ending(self.line_ending()) as i32,
saved_version: proto::serialize_version(&self.saved_version),
saved_version_fingerprint: proto::serialize_fingerprint(self.saved_version_fingerprint),
saved_version_fingerprint: proto::serialize_fingerprint(self.file_fingerprint),
saved_mtime: Some(self.saved_mtime.into()),
}
}
@ -477,7 +486,7 @@ impl Buffer {
));
let text_operations = self.text.operations().clone();
cx.background().spawn(async move {
cx.background_executor().spawn(async move {
let since = since.unwrap_or_default();
operations.extend(
text_operations
@ -509,7 +518,7 @@ impl Buffer {
Self {
saved_mtime,
saved_version: buffer.version(),
saved_version_fingerprint: buffer.as_rope().fingerprint(),
file_fingerprint: buffer.as_rope().fingerprint(),
reload_task: None,
transaction_depth: 0,
was_dirty_before_starting_transaction: None,
@ -576,7 +585,7 @@ impl Buffer {
}
pub fn saved_version_fingerprint(&self) -> RopeFingerprint {
self.saved_version_fingerprint
self.file_fingerprint
}
pub fn saved_mtime(&self) -> SystemTime {
@ -604,7 +613,7 @@ impl Buffer {
cx: &mut ModelContext<Self>,
) {
self.saved_version = version;
self.saved_version_fingerprint = fingerprint;
self.file_fingerprint = fingerprint;
self.saved_mtime = mtime;
cx.emit(Event::Saved);
cx.notify();
@ -620,13 +629,14 @@ impl Buffer {
let Some((new_mtime, new_text)) = this.update(&mut cx, |this, cx| {
let file = this.file.as_ref()?.as_local()?;
Some((file.mtime(), file.load(cx)))
}) else {
})?
else {
return Ok(());
};
let new_text = new_text.await?;
let diff = this
.update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))
.update(&mut cx, |this, cx| this.diff(new_text.clone(), cx))?
.await;
this.update(&mut cx, |this, cx| {
if this.version() == diff.base_version {
@ -652,8 +662,7 @@ impl Buffer {
}
this.reload_task.take();
});
Ok(())
})
}));
rx
}
@ -667,14 +676,14 @@ impl Buffer {
cx: &mut ModelContext<Self>,
) {
self.saved_version = version;
self.saved_version_fingerprint = fingerprint;
self.file_fingerprint = fingerprint;
self.text.set_line_ending(line_ending);
self.saved_mtime = mtime;
if let Some(file) = self.file.as_ref().and_then(|f| f.as_local()) {
file.buffer_reloaded(
self.remote_id(),
&self.saved_version,
self.saved_version_fingerprint,
self.file_fingerprint,
self.line_ending(),
self.saved_mtime,
cx,
@ -736,20 +745,18 @@ impl Buffer {
let snapshot = self.snapshot();
let mut diff = self.git_diff.clone();
let diff = cx.background().spawn(async move {
let diff = cx.background_executor().spawn(async move {
diff.update(&diff_base, &snapshot).await;
diff
});
let handle = cx.weak_handle();
Some(cx.spawn_weak(|_, mut cx| async move {
Some(cx.spawn(|this, mut cx| async move {
let buffer_diff = diff.await;
if let Some(this) = handle.upgrade(&mut cx) {
this.update(&mut cx, |this, _| {
this.git_diff = buffer_diff;
this.git_diff_update_count += 1;
})
}
this.update(&mut cx, |this, _| {
this.git_diff = buffer_diff;
this.git_diff_update_count += 1;
})
.ok();
}))
}
@ -847,7 +854,7 @@ impl Buffer {
let mut syntax_snapshot = syntax_map.snapshot();
drop(syntax_map);
let parse_task = cx.background().spawn({
let parse_task = cx.background_executor().spawn({
let language = language.clone();
let language_registry = language_registry.clone();
async move {
@ -857,7 +864,7 @@ impl Buffer {
});
match cx
.background()
.background_executor()
.block_with_timeout(self.sync_parse_timeout, parse_task)
{
Ok(new_syntax_snapshot) => {
@ -886,7 +893,8 @@ impl Buffer {
if parse_again {
this.reparse(cx);
}
});
})
.ok();
})
.detach();
}
@ -919,9 +927,9 @@ impl Buffer {
fn request_autoindent(&mut self, cx: &mut ModelContext<Self>) {
if let Some(indent_sizes) = self.compute_autoindents() {
let indent_sizes = cx.background().spawn(indent_sizes);
let indent_sizes = cx.background_executor().spawn(indent_sizes);
match cx
.background()
.background_executor()
.block_with_timeout(Duration::from_micros(500), indent_sizes)
{
Ok(indent_sizes) => self.apply_autoindents(indent_sizes, cx),
@ -930,7 +938,8 @@ impl Buffer {
let indent_sizes = indent_sizes.await;
this.update(&mut cx, |this, cx| {
this.apply_autoindents(indent_sizes, cx);
});
})
.ok();
}));
}
}
@ -1169,36 +1178,72 @@ impl Buffer {
pub fn diff(&self, mut new_text: String, cx: &AppContext) -> Task<Diff> {
let old_text = self.as_rope().clone();
let base_version = self.version();
cx.background().spawn(async move {
let old_text = old_text.to_string();
let line_ending = LineEnding::detect(&new_text);
LineEnding::normalize(&mut new_text);
let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
let mut edits = Vec::new();
let mut offset = 0;
let empty: Arc<str> = "".into();
for change in diff.iter_all_changes() {
let value = change.value();
let end_offset = offset + value.len();
match change.tag() {
ChangeTag::Equal => {
offset = end_offset;
cx.background_executor()
.spawn_labeled(*BUFFER_DIFF_TASK, async move {
let old_text = old_text.to_string();
let line_ending = LineEnding::detect(&new_text);
LineEnding::normalize(&mut new_text);
let diff = TextDiff::from_chars(old_text.as_str(), new_text.as_str());
let empty: Arc<str> = "".into();
let mut edits = Vec::new();
let mut old_offset = 0;
let mut new_offset = 0;
let mut last_edit: Option<(Range<usize>, Range<usize>)> = None;
for change in diff.iter_all_changes().map(Some).chain([None]) {
if let Some(change) = &change {
let len = change.value().len();
match change.tag() {
ChangeTag::Equal => {
old_offset += len;
new_offset += len;
}
ChangeTag::Delete => {
let old_end_offset = old_offset + len;
if let Some((last_old_range, _)) = &mut last_edit {
last_old_range.end = old_end_offset;
} else {
last_edit =
Some((old_offset..old_end_offset, new_offset..new_offset));
}
old_offset = old_end_offset;
}
ChangeTag::Insert => {
let new_end_offset = new_offset + len;
if let Some((_, last_new_range)) = &mut last_edit {
last_new_range.end = new_end_offset;
} else {
last_edit =
Some((old_offset..old_offset, new_offset..new_end_offset));
}
new_offset = new_end_offset;
}
}
}
ChangeTag::Delete => {
edits.push((offset..end_offset, empty.clone()));
offset = end_offset;
}
ChangeTag::Insert => {
edits.push((offset..offset, value.into()));
if let Some((old_range, new_range)) = &last_edit {
if old_offset > old_range.end
|| new_offset > new_range.end
|| change.is_none()
{
let text = if new_range.is_empty() {
empty.clone()
} else {
new_text[new_range.clone()].into()
};
edits.push((old_range.clone(), text));
last_edit.take();
}
}
}
}
Diff {
base_version,
line_ending,
edits,
}
})
Diff {
base_version,
line_ending,
edits,
}
})
}
/// Spawn a background task that searches the buffer for any whitespace
@ -1207,7 +1252,7 @@ impl Buffer {
let old_text = self.as_rope().clone();
let line_ending = self.line_ending();
let base_version = self.version();
cx.background().spawn(async move {
cx.background_executor().spawn(async move {
let ranges = trailing_whitespace_ranges(&old_text);
let empty = Arc::<str>::from("");
Diff {
@ -1282,12 +1327,12 @@ impl Buffer {
}
pub fn is_dirty(&self) -> bool {
self.saved_version_fingerprint != self.as_rope().fingerprint()
self.file_fingerprint != self.as_rope().fingerprint()
|| self.file.as_ref().map_or(false, |file| file.is_deleted())
}
pub fn has_conflict(&self) -> bool {
self.saved_version_fingerprint != self.as_rope().fingerprint()
self.file_fingerprint != self.as_rope().fingerprint()
&& self
.file
.as_ref()
@ -1458,95 +1503,82 @@ impl Buffer {
return None;
}
// Non-generic part hoisted out to reduce LLVM IR size.
fn tail(
this: &mut Buffer,
edits: Vec<(Range<usize>, Arc<str>)>,
autoindent_mode: Option<AutoindentMode>,
cx: &mut ModelContext<Buffer>,
) -> Option<clock::Lamport> {
this.start_transaction();
this.pending_autoindent.take();
let autoindent_request = autoindent_mode
.and_then(|mode| this.language.as_ref().map(|_| (this.snapshot(), mode)));
self.start_transaction();
self.pending_autoindent.take();
let autoindent_request = autoindent_mode
.and_then(|mode| self.language.as_ref().map(|_| (self.snapshot(), mode)));
let edit_operation = this.text.edit(edits.iter().cloned());
let edit_id = edit_operation.timestamp();
let edit_operation = self.text.edit(edits.iter().cloned());
let edit_id = edit_operation.timestamp();
if let Some((before_edit, mode)) = autoindent_request {
let mut delta = 0isize;
let entries = edits
.into_iter()
.enumerate()
.zip(&edit_operation.as_edit().unwrap().new_text)
.map(|((ix, (range, _)), new_text)| {
let new_text_length = new_text.len();
let old_start = range.start.to_point(&before_edit);
let new_start = (delta + range.start as isize) as usize;
delta +=
new_text_length as isize - (range.end as isize - range.start as isize);
if let Some((before_edit, mode)) = autoindent_request {
let mut delta = 0isize;
let entries = edits
.into_iter()
.enumerate()
.zip(&edit_operation.as_edit().unwrap().new_text)
.map(|((ix, (range, _)), new_text)| {
let new_text_length = new_text.len();
let old_start = range.start.to_point(&before_edit);
let new_start = (delta + range.start as isize) as usize;
delta += new_text_length as isize - (range.end as isize - range.start as isize);
let mut range_of_insertion_to_indent = 0..new_text_length;
let mut first_line_is_new = false;
let mut original_indent_column = None;
let mut range_of_insertion_to_indent = 0..new_text_length;
let mut first_line_is_new = false;
let mut original_indent_column = None;
// When inserting an entire line at the beginning of an existing line,
// treat the insertion as new.
if new_text.contains('\n')
&& old_start.column
<= before_edit.indent_size_for_line(old_start.row).len
{
first_line_is_new = true;
// When inserting an entire line at the beginning of an existing line,
// treat the insertion as new.
if new_text.contains('\n')
&& old_start.column <= before_edit.indent_size_for_line(old_start.row).len
{
first_line_is_new = true;
}
// When inserting text starting with a newline, avoid auto-indenting the
// previous line.
if new_text.starts_with('\n') {
range_of_insertion_to_indent.start += 1;
first_line_is_new = true;
}
// Avoid auto-indenting after the insertion.
if let AutoindentMode::Block {
original_indent_columns,
} = &mode
{
original_indent_column =
Some(original_indent_columns.get(ix).copied().unwrap_or_else(|| {
indent_size_for_text(
new_text[range_of_insertion_to_indent.clone()].chars(),
)
.len
}));
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
range_of_insertion_to_indent.end -= 1;
}
}
// When inserting text starting with a newline, avoid auto-indenting the
// previous line.
if new_text.starts_with('\n') {
range_of_insertion_to_indent.start += 1;
first_line_is_new = true;
}
AutoindentRequestEntry {
first_line_is_new,
original_indent_column,
indent_size: before_edit.language_indent_size_at(range.start, cx),
range: self.anchor_before(new_start + range_of_insertion_to_indent.start)
..self.anchor_after(new_start + range_of_insertion_to_indent.end),
}
})
.collect();
// Avoid auto-indenting after the insertion.
if let AutoindentMode::Block {
original_indent_columns,
} = &mode
{
original_indent_column = Some(
original_indent_columns.get(ix).copied().unwrap_or_else(|| {
indent_size_for_text(
new_text[range_of_insertion_to_indent.clone()].chars(),
)
.len
}),
);
if new_text[range_of_insertion_to_indent.clone()].ends_with('\n') {
range_of_insertion_to_indent.end -= 1;
}
}
AutoindentRequestEntry {
first_line_is_new,
original_indent_column,
indent_size: before_edit.language_indent_size_at(range.start, cx),
range: this
.anchor_before(new_start + range_of_insertion_to_indent.start)
..this.anchor_after(new_start + range_of_insertion_to_indent.end),
}
})
.collect();
this.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
}));
}
this.end_transaction(cx);
this.send_operation(Operation::Buffer(edit_operation), cx);
Some(edit_id)
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
}));
}
tail(self, edits, autoindent_mode, cx)
self.end_transaction(cx);
self.send_operation(Operation::Buffer(edit_operation), cx);
Some(edit_id)
}
fn did_edit(
@ -1879,9 +1911,7 @@ impl Buffer {
}
}
impl Entity for Buffer {
type Event = Event;
}
impl EventEmitter<Event> for Buffer {}
impl Deref for Buffer {
type Target = TextBuffer;

View File

@ -1,25 +1,25 @@
use super::*;
use crate::language_settings::{
AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
};
use super::*;
use crate::Buffer;
use clock::ReplicaId;
use collections::BTreeMap;
use gpui::{AppContext, ModelHandle};
use gpui::{AppContext, Model};
use gpui::{Context, TestAppContext};
use indoc::indoc;
use proto::deserialize_operation;
use rand::prelude::*;
use regex::RegexBuilder;
use settings::SettingsStore;
use std::{
cell::RefCell,
env,
ops::Range,
rc::Rc,
time::{Duration, Instant},
};
use text::network::Network;
use text::LineEnding;
use text::{Point, ToPoint};
use unindent::Unindent as _;
use util::{assert_set_eq, post_inc, test::marked_text_ranges, RandomCharIter};
@ -42,8 +42,8 @@ fn init_logger() {
fn test_line_endings(cx: &mut gpui::AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
let mut buffer = Buffer::new(0, cx.model_id() as u64, "one\r\ntwo\rthree")
cx.new_model(|cx| {
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
.with_language(Arc::new(rust_lang()), cx);
assert_eq!(buffer.text(), "one\ntwo\nthree");
assert_eq!(buffer.line_ending(), LineEnding::Windows);
@ -135,24 +135,24 @@ fn test_select_language() {
#[gpui::test]
fn test_edit_events(cx: &mut gpui::AppContext) {
let mut now = Instant::now();
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
let buffer1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abcdef"));
let buffer2 = cx.add_model(|cx| Buffer::new(1, cx.model_id() as u64, "abcdef"));
let buffer1_ops = Rc::new(RefCell::new(Vec::new()));
let buffer1 = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
let buffer2 = cx.new_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
buffer1.update(cx, {
let buffer1_ops = buffer1_ops.clone();
|buffer, cx| {
let buffer_1_events = buffer_1_events.clone();
cx.subscribe(&buffer1, move |_, _, event, _| match event.clone() {
Event::Operation(op) => buffer1_ops.borrow_mut().push(op),
event => buffer_1_events.borrow_mut().push(event),
Event::Operation(op) => buffer1_ops.lock().push(op),
event => buffer_1_events.lock().push(event),
})
.detach();
let buffer_2_events = buffer_2_events.clone();
cx.subscribe(&buffer2, move |_, _, event, _| {
buffer_2_events.borrow_mut().push(event.clone())
buffer_2_events.lock().push(event.clone())
})
.detach();
@ -179,12 +179,10 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
// Incorporating a set of remote ops emits a single edited event,
// followed by a dirty changed event.
buffer2.update(cx, |buffer, cx| {
buffer
.apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
.unwrap();
buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
});
assert_eq!(
mem::take(&mut *buffer_1_events.borrow_mut()),
mem::take(&mut *buffer_1_events.lock()),
vec![
Event::Edited,
Event::DirtyChanged,
@ -193,7 +191,7 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
]
);
assert_eq!(
mem::take(&mut *buffer_2_events.borrow_mut()),
mem::take(&mut *buffer_2_events.lock()),
vec![Event::Edited, Event::DirtyChanged]
);
@ -205,28 +203,26 @@ fn test_edit_events(cx: &mut gpui::AppContext) {
// Incorporating the remote ops again emits a single edited event,
// followed by a dirty changed event.
buffer2.update(cx, |buffer, cx| {
buffer
.apply_ops(buffer1_ops.borrow_mut().drain(..), cx)
.unwrap();
buffer.apply_ops(buffer1_ops.lock().drain(..), cx).unwrap();
});
assert_eq!(
mem::take(&mut *buffer_1_events.borrow_mut()),
mem::take(&mut *buffer_1_events.lock()),
vec![Event::Edited, Event::DirtyChanged,]
);
assert_eq!(
mem::take(&mut *buffer_2_events.borrow_mut()),
mem::take(&mut *buffer_2_events.lock()),
vec![Event::Edited, Event::DirtyChanged]
);
}
#[gpui::test]
async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
async fn test_apply_diff(cx: &mut TestAppContext) {
let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
let anchor = buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
let text = "a\nccc\ndddd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
@ -234,7 +230,7 @@ async fn test_apply_diff(cx: &mut gpui::TestAppContext) {
});
let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
let diff = buffer.read_with(cx, |b, cx| b.diff(text.into(), cx)).await;
let diff = buffer.update(cx, |b, cx| b.diff(text.into(), cx)).await;
buffer.update(cx, |buffer, cx| {
buffer.apply_diff(diff, cx).unwrap();
assert_eq!(buffer.text(), text);
@ -254,15 +250,15 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
]
.join("\n");
let buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, text));
let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
// Spawn a task to format the buffer's whitespace.
// Pause so that the foratting task starts running.
let format = buffer.read_with(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
let format = buffer.update(cx, |buffer, cx| buffer.remove_trailing_whitespace(cx));
smol::future::yield_now().await;
// Edit the buffer while the normalization task is running.
let version_before_edit = buffer.read_with(cx, |buffer, _| buffer.version());
let version_before_edit = buffer.update(cx, |buffer, _| buffer.version());
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
@ -318,12 +314,13 @@ async fn test_normalize_whitespace(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_reparse(cx: &mut gpui::TestAppContext) {
let text = "fn a() {}";
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
});
// Wait for the initial text to parse
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
assert_eq!(
get_tree_sexp(&buffer, cx),
concat!(
@ -354,7 +351,8 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
assert_eq!(buf.text(), "fn a(b: C) { d; }");
assert!(buf.is_parsing());
});
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert!(!buffer.update(cx, |buffer, _| buffer.is_parsing()));
assert_eq!(
get_tree_sexp(&buffer, cx),
concat!(
@ -386,7 +384,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
assert!(buf.is_parsing());
});
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert_eq!(
get_tree_sexp(&buffer, cx),
concat!(
@ -408,7 +406,8 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
assert_eq!(buf.text(), "fn a() {}");
assert!(buf.is_parsing());
});
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert_eq!(
get_tree_sexp(&buffer, cx),
concat!(
@ -426,7 +425,7 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
assert_eq!(buf.text(), "fn a(b: C) { d.e::<G>(f); }");
assert!(buf.is_parsing());
});
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert_eq!(
get_tree_sexp(&buffer, cx),
concat!(
@ -443,15 +442,15 @@ async fn test_reparse(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
let buffer = cx.add_model(|cx| {
let buffer = cx.new_model(|cx| {
let mut buffer =
Buffer::new(0, cx.model_id() as u64, "{}").with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
buffer.set_sync_parse_timeout(Duration::ZERO);
buffer
});
// Wait for the initial text to parse
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert_eq!(
get_tree_sexp(&buffer, cx),
"(source_file (expression_statement (block)))"
@ -460,7 +459,7 @@ async fn test_resetting_language(cx: &mut gpui::TestAppContext) {
buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(Arc::new(json_lang())), cx)
});
buffer.condition(cx, |buffer, _| !buffer.is_parsing()).await;
cx.executor().run_until_parked();
assert_eq!(get_tree_sexp(&buffer, cx), "(document (object))");
}
@ -493,11 +492,11 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
});
let outline = buffer
.read_with(cx, |buffer, _| buffer.snapshot().outline(None))
.update(cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap();
assert_eq!(
@ -560,7 +559,7 @@ async fn test_outline(cx: &mut gpui::TestAppContext) {
cx: &'a gpui::TestAppContext,
) -> Vec<(&'a str, Vec<usize>)> {
let matches = cx
.read(|cx| outline.search(query, cx.background().clone()))
.update(|cx| outline.search(query, cx.background_executor().clone()))
.await;
matches
.into_iter()
@ -579,11 +578,11 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
});
let outline = buffer
.read_with(cx, |buffer, _| buffer.snapshot().outline(None))
.update(cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap();
assert_eq!(
@ -617,10 +616,10 @@ async fn test_outline_with_extra_context(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(language), cx)
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
});
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
// extra context nodes are included in the outline.
let outline = snapshot.outline(None).unwrap();
@ -661,10 +660,10 @@ async fn test_symbols_containing(cx: &mut gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx)
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
});
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot());
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
// point is at the start of an item
assert_eq!(
@ -882,10 +881,10 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
#[gpui::test]
fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "fn a() { b(|c| {}) }";
let buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
let snapshot = buffer.snapshot();
assert_eq!(
@ -923,10 +922,10 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "fn a() {}";
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n \n}");
@ -966,10 +965,10 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
settings.defaults.hard_tabs = Some(true);
});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "fn a() {}";
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
buffer.edit([(8..8, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "fn a() {\n\t\n}");
@ -1007,10 +1006,11 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let entity_id = cx.entity_id();
let mut buffer = Buffer::new(
0,
cx.model_id() as u64,
entity_id.as_u64(),
"
fn a() {
c;
@ -1080,10 +1080,12 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
buffer
});
cx.add_model(|cx| {
cx.new_model(|cx| {
eprintln!("second buffer: {:?}", cx.entity_id());
let mut buffer = Buffer::new(
0,
cx.model_id() as u64,
cx.entity_id().as_u64(),
"
fn a() {
b();
@ -1137,16 +1139,18 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
);
buffer
});
eprintln!("DONE");
}
#[gpui::test]
fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
cx.model_id() as u64,
cx.entity_id().as_u64(),
"
fn a() {
i
@ -1205,10 +1209,10 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let mut buffer = Buffer::new(
0,
cx.model_id() as u64,
cx.entity_id().as_u64(),
"
fn a() {}
"
@ -1262,10 +1266,10 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "a\nb";
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[(0..1, "\n"), (2..3, "\n")],
Some(AutoindentMode::EachLine),
@ -1280,7 +1284,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "
const a: usize = 1;
fn b() {
@ -1292,7 +1296,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
.unindent();
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
buffer.edit(
[(Point::new(3, 0)..Point::new(3, 0), "e(\n f()\n);\n")],
Some(AutoindentMode::EachLine),
@ -1322,7 +1326,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
fn test_autoindent_block_mode(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = r#"
fn a() {
b();
@ -1330,7 +1334,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
"#
.unindent();
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
// When this text was copied, both of the quotation marks were at the same
// indent level, but the indentation of the first line was not included in
@ -1406,7 +1410,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = r#"
fn a() {
if b() {
@ -1416,7 +1420,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
"#
.unindent();
let mut buffer =
Buffer::new(0, cx.model_id() as u64, text).with_language(Arc::new(rust_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
// The original indent columns are not known, so this text is
// auto-indented in a block as if the first line was copied in
@ -1486,7 +1490,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = "
* one
- a
@ -1495,7 +1499,7 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
"
.unindent();
let mut buffer = Buffer::new(0, cx.model_id() as u64, text).with_language(
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text).with_language(
Arc::new(Language::new(
LanguageConfig {
name: "Markdown".into(),
@ -1555,7 +1559,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
language_registry.add(html_language.clone());
language_registry.add(javascript_language.clone());
cx.add_model(|cx| {
cx.new_model(|cx| {
let (text, ranges) = marked_text_ranges(
&"
<div>ˇ
@ -1571,7 +1575,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
false,
);
let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
buffer.set_language_registry(language_registry);
buffer.set_language(Some(html_language), cx);
buffer.edit(
@ -1606,9 +1610,9 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
settings.defaults.tab_size = Some(2.try_into().unwrap());
});
cx.add_model(|cx| {
cx.new_model(|cx| {
let mut buffer =
Buffer::new(0, cx.model_id() as u64, "").with_language(Arc::new(ruby_lang()), cx);
Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
let text = r#"
class C
@ -1649,7 +1653,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let language = Language::new(
LanguageConfig {
name: "JavaScript".into(),
@ -1710,7 +1714,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
.unindent();
let buffer =
Buffer::new(0, cx.model_id() as u64, &text).with_language(Arc::new(language), cx);
Buffer::new(0, cx.entity_id().as_u64(), &text).with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot();
let config = snapshot.language_scope_at(0).unwrap();
@ -1782,7 +1786,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
fn test_language_scope_at_with_rust(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
@ -1822,7 +1826,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
"#
.unindent();
let buffer = Buffer::new(0, cx.model_id() as u64, text.clone())
let buffer = Buffer::new(0, cx.entity_id().as_u64(), text.clone())
.with_language(Arc::new(language), cx);
let snapshot = buffer.snapshot();
@ -1850,7 +1854,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
init_settings(cx, |_| {});
cx.add_model(|cx| {
cx.new_model(|cx| {
let text = r#"
<ol>
<% people.each do |person| %>
@ -1867,7 +1871,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
language_registry.add(Arc::new(html_lang()));
language_registry.add(Arc::new(erb_lang()));
let mut buffer = Buffer::new(0, cx.model_id() as u64, text);
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), text);
buffer.set_language_registry(language_registry.clone());
buffer.set_language(
language_registry
@ -1898,8 +1902,8 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
fn test_serialization(cx: &mut gpui::AppContext) {
let mut now = Instant::now();
let buffer1 = cx.add_model(|cx| {
let mut buffer = Buffer::new(0, cx.model_id() as u64, "abc");
let buffer1 = cx.new_model(|cx| {
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc");
buffer.edit([(3..3, "D")], None, cx);
now += Duration::from_secs(1);
@ -1919,9 +1923,9 @@ fn test_serialization(cx: &mut gpui::AppContext) {
let state = buffer1.read(cx).to_proto();
let ops = cx
.background()
.background_executor()
.block(buffer1.read(cx).serialize_ops(None, cx));
let buffer2 = cx.add_model(|cx| {
let buffer2 = cx.new_model(|cx| {
let mut buffer = Buffer::from_proto(1, state, None).unwrap();
buffer
.apply_ops(
@ -1953,14 +1957,15 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
.collect::<String>();
let mut replica_ids = Vec::new();
let mut buffers = Vec::new();
let network = Rc::new(RefCell::new(Network::new(rng.clone())));
let base_buffer = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, base_text.as_str()));
let network = Arc::new(Mutex::new(Network::new(rng.clone())));
let base_buffer =
cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
for i in 0..rng.gen_range(min_peers..=max_peers) {
let buffer = cx.add_model(|cx| {
let buffer = cx.new_model(|cx| {
let state = base_buffer.read(cx).to_proto();
let ops = cx
.background()
.background_executor()
.block(base_buffer.read(cx).serialize_ops(None, cx));
let mut buffer = Buffer::from_proto(i as ReplicaId, state, None).unwrap();
buffer
@ -1975,16 +1980,17 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
if let Event::Operation(op) = event {
network
.borrow_mut()
.lock()
.broadcast(buffer.replica_id(), vec![proto::serialize_operation(op)]);
}
})
.detach();
buffer
});
buffers.push(buffer);
replica_ids.push(i as ReplicaId);
network.borrow_mut().add_peer(i as ReplicaId);
network.lock().add_peer(i as ReplicaId);
log::info!("Adding initial peer with replica id {}", i);
}
@ -2065,7 +2071,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
50..=59 if replica_ids.len() < max_peers => {
let old_buffer_state = buffer.read(cx).to_proto();
let old_buffer_ops = cx
.background()
.background_executor()
.block(buffer.read(cx).serialize_ops(None, cx));
let new_replica_id = (0..=replica_ids.len() as ReplicaId)
.filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
@ -2076,7 +2082,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
new_replica_id,
replica_id
);
new_buffer = Some(cx.add_model(|cx| {
new_buffer = Some(cx.new_model(|cx| {
let mut new_buffer =
Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
new_buffer
@ -2096,7 +2102,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
let network = network.clone();
cx.subscribe(&cx.handle(), move |buffer, _, event, _| {
if let Event::Operation(op) = event {
network.borrow_mut().broadcast(
network.lock().broadcast(
buffer.replica_id(),
vec![proto::serialize_operation(op)],
);
@ -2105,15 +2111,15 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
.detach();
new_buffer
}));
network.borrow_mut().replicate(replica_id, new_replica_id);
network.lock().replicate(replica_id, new_replica_id);
if new_replica_id as usize == replica_ids.len() {
replica_ids.push(new_replica_id);
} else {
let new_buffer = new_buffer.take().unwrap();
while network.borrow().has_unreceived(new_replica_id) {
while network.lock().has_unreceived(new_replica_id) {
let ops = network
.borrow_mut()
.lock()
.receive(new_replica_id)
.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap());
@ -2140,9 +2146,9 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
});
mutation_count -= 1;
}
_ if network.borrow().has_unreceived(replica_id) => {
_ if network.lock().has_unreceived(replica_id) => {
let ops = network
.borrow_mut()
.lock()
.receive(replica_id)
.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap());
@ -2167,7 +2173,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
buffer.read(cx).check_invariants();
}
if mutation_count == 0 && network.borrow().is_idle() {
if mutation_count == 0 && network.lock().is_idle() {
break;
}
}
@ -2438,8 +2444,8 @@ fn javascript_lang() -> Language {
.unwrap()
}
fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
buffer.read_with(cx, |buffer, _| {
fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui::TestAppContext) -> String {
buffer.update(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
layers[0].node().to_sexp()
@ -2454,8 +2460,8 @@ fn assert_bracket_pairs(
cx: &mut AppContext,
) {
let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
let buffer = cx.add_model(|cx| {
Buffer::new(0, cx.model_id() as u64, expected_text.clone())
let buffer = cx.new_model(|cx| {
Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
.with_language(Arc::new(language), cx)
});
let buffer = buffer.update(cx, |buffer, _cx| buffer.snapshot());
@ -2478,9 +2484,10 @@ fn assert_bracket_pairs(
}
fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
cx.set_global(SettingsStore::test(cx));
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
crate::init(cx);
cx.update_global::<SettingsStore, _, _>(|settings, cx| {
cx.update_global::<SettingsStore, _>(|settings, cx| {
settings.update_user_settings::<AllLanguageSettings>(cx, f);
});
}

View File

@ -1,4 +1,4 @@
use gpui::fonts::HighlightStyle;
use gpui::HighlightStyle;
use std::sync::Arc;
use theme::SyntaxTheme;
@ -79,23 +79,23 @@ impl Default for HighlightId {
#[cfg(test)]
mod tests {
use super::*;
use gpui::color::Color;
use gpui::rgba;
#[test]
fn test_highlight_map() {
let theme = SyntaxTheme::new(
[
("function", Color::from_u32(0x100000ff)),
("function.method", Color::from_u32(0x200000ff)),
("function.async", Color::from_u32(0x300000ff)),
("variable.builtin.self.rust", Color::from_u32(0x400000ff)),
("variable.builtin", Color::from_u32(0x500000ff)),
("variable", Color::from_u32(0x600000ff)),
let theme = SyntaxTheme {
highlights: [
("function", rgba(0x100000ff)),
("function.method", rgba(0x200000ff)),
("function.async", rgba(0x300000ff)),
("variable.builtin.self.rust", rgba(0x400000ff)),
("variable.builtin", rgba(0x500000ff)),
("variable", rgba(0x600000ff)),
]
.iter()
.map(|(name, color)| (name.to_string(), (*color).into()))
.collect(),
);
};
let capture_names = &[
"function.special",

View File

@ -2,13 +2,13 @@ mod buffer;
mod diagnostic_set;
mod highlight_map;
pub mod language_settings;
pub mod markdown;
mod outline;
pub mod proto;
mod syntax_map;
#[cfg(test)]
mod buffer_tests;
pub mod markdown;
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
@ -18,7 +18,7 @@ use futures::{
future::{BoxFuture, Shared},
FutureExt, TryFutureExt as _,
};
use gpui::{executor::Background, AppContext, AsyncAppContext, Task};
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
pub use highlight_map::HighlightMap;
use lazy_static::lazy_static;
use lsp::{CodeActionKind, LanguageServerBinary};
@ -44,7 +44,7 @@ use std::{
};
use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query};
use tree_sitter::{self, wasmtime, Query, WasmStore};
use unicase::UniCase;
use util::{http::HttpClient, paths::PathExt};
use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
@ -84,10 +84,15 @@ impl LspBinaryStatusSender {
}
thread_local! {
static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
static PARSER: RefCell<Parser> = {
let mut parser = Parser::new();
parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap();
RefCell::new(parser)
};
}
lazy_static! {
pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default();
pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default();
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig {
@ -111,6 +116,7 @@ pub struct LanguageServerName(pub Arc<str>);
pub struct CachedLspAdapter {
pub name: LanguageServerName,
pub short_name: &'static str,
pub initialization_options: Option<Value>,
pub disk_based_diagnostic_sources: Vec<String>,
pub disk_based_diagnostics_progress_token: Option<String>,
pub language_ids: HashMap<String, String>,
@ -122,6 +128,7 @@ impl CachedLspAdapter {
pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
let name = adapter.name().await;
let short_name = adapter.short_name();
let initialization_options = adapter.initialization_options().await;
let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await;
let disk_based_diagnostics_progress_token =
adapter.disk_based_diagnostics_progress_token().await;
@ -130,6 +137,7 @@ impl CachedLspAdapter {
Arc::new(CachedLspAdapter {
name,
short_name,
initialization_options,
disk_based_diagnostic_sources,
disk_based_diagnostics_progress_token,
language_ids,
@ -357,6 +365,7 @@ pub struct CodeLabel {
#[derive(Clone, Deserialize)]
pub struct LanguageConfig {
pub name: Arc<str>,
pub grammar_name: Option<Arc<str>>,
pub path_suffixes: Vec<String>,
pub brackets: BracketPairConfig,
#[serde(default, deserialize_with = "deserialize_regex")]
@ -443,6 +452,7 @@ impl Default for LanguageConfig {
fn default() -> Self {
Self {
name: "".into(),
grammar_name: None,
path_suffixes: Default::default(),
brackets: Default::default(),
auto_indent_using_last_non_empty_line: auto_indent_using_last_non_empty_line_default(),
@ -617,14 +627,25 @@ type AvailableLanguageId = usize;
#[derive(Clone)]
struct AvailableLanguage {
id: AvailableLanguageId,
path: &'static str,
config: LanguageConfig,
grammar: tree_sitter::Language,
grammar: AvailableGrammar,
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
get_queries: fn(&str) -> LanguageQueries,
loaded: bool,
}
#[derive(Clone)]
enum AvailableGrammar {
Native {
grammar: tree_sitter::Language,
asset_dir: &'static str,
get_queries: fn(&str) -> LanguageQueries,
},
Wasm {
grammar_name: Arc<str>,
path: Arc<Path>,
},
}
pub struct LanguageRegistry {
state: RwLock<LanguageRegistryState>,
language_server_download_dir: Option<Arc<Path>>,
@ -633,7 +654,7 @@ pub struct LanguageRegistry {
lsp_binary_paths: Mutex<
HashMap<LanguageServerName, Shared<Task<Result<LanguageServerBinary, Arc<anyhow::Error>>>>>,
>,
executor: Option<Arc<Background>>,
executor: Option<BackgroundExecutor>,
lsp_binary_status_tx: LspBinaryStatusSender,
}
@ -682,7 +703,7 @@ impl LanguageRegistry {
Self::new(Task::ready(()))
}
pub fn set_executor(&mut self, executor: Arc<Background>) {
pub fn set_executor(&mut self, executor: BackgroundExecutor) {
self.executor = Some(executor);
}
@ -696,7 +717,7 @@ impl LanguageRegistry {
pub fn register(
&self,
path: &'static str,
asset_dir: &'static str,
config: LanguageConfig,
grammar: tree_sitter::Language,
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
@ -705,11 +726,24 @@ impl LanguageRegistry {
let state = &mut *self.state.write();
state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id),
path,
config,
grammar,
grammar: AvailableGrammar::Native {
grammar,
get_queries,
asset_dir,
},
lsp_adapters,
get_queries,
loaded: false,
});
}
pub fn register_wasm(&self, path: Arc<Path>, grammar_name: Arc<str>, config: LanguageConfig) {
let state = &mut *self.state.write();
state.available_languages.push(AvailableLanguage {
id: post_inc(&mut state.next_available_language_id),
config,
grammar: AvailableGrammar::Wasm { grammar_name, path },
lsp_adapters: Vec::new(),
loaded: false,
});
}
@ -749,7 +783,7 @@ impl LanguageRegistry {
let mut state = self.state.write();
state.theme = Some(theme.clone());
for language in &state.languages {
language.set_theme(&theme.editor.syntax);
language.set_theme(&theme.syntax());
}
}
@ -834,13 +868,43 @@ impl LanguageRegistry {
executor
.spawn(async move {
let id = language.id;
let queries = (language.get_queries)(&language.path);
let language =
Language::new(language.config, Some(language.grammar))
let name = language.config.name.clone();
let language = async {
let (grammar, queries) = match language.grammar {
AvailableGrammar::Native {
grammar,
asset_dir,
get_queries,
} => (grammar, (get_queries)(asset_dir)),
AvailableGrammar::Wasm { grammar_name, path } => {
let mut wasm_path = path.join(grammar_name.as_ref());
wasm_path.set_extension("wasm");
let wasm_bytes = std::fs::read(&wasm_path)?;
let grammar = PARSER.with(|parser| {
let mut parser = parser.borrow_mut();
let mut store = parser.take_wasm_store().unwrap();
let grammar =
store.load_language(&grammar_name, &wasm_bytes);
parser.set_wasm_store(store).unwrap();
grammar
})?;
let mut queries = LanguageQueries::default();
if let Ok(contents) = std::fs::read_to_string(
&path.join("highlights.scm"),
) {
queries.highlights = Some(contents.into());
}
(grammar, queries)
}
};
Language::new(language.config, Some(grammar))
.with_lsp_adapters(language.lsp_adapters)
.await;
let name = language.name();
match language.with_queries(queries) {
.await
.with_queries(queries)
}
.await;
match language {
Ok(language) => {
let language = Arc::new(language);
let mut state = this.state.write();
@ -918,7 +982,7 @@ impl LanguageRegistry {
}
let servers_tx = servers_tx.clone();
cx.background()
cx.background_executor()
.spawn(async move {
if fake_server
.try_receive_notification::<lsp::notification::Initialized>()
@ -955,18 +1019,22 @@ impl LanguageRegistry {
let task = {
let container_dir = container_dir.clone();
cx.spawn(|mut cx| async move {
cx.spawn(move |mut cx| async move {
login_shell_env_loaded.await;
let mut lock = this.lsp_binary_paths.lock();
let entry = lock
let entry = this
.lsp_binary_paths
.lock()
.entry(adapter.name.clone())
.or_insert_with(|| {
let adapter = adapter.clone();
let language = language.clone();
let delegate = delegate.clone();
cx.spawn(|cx| {
get_binary(
adapter.clone(),
language.clone(),
delegate.clone(),
adapter,
language,
delegate,
container_dir,
lsp_binary_statuses,
cx,
@ -976,9 +1044,8 @@ impl LanguageRegistry {
.shared()
})
.clone();
drop(lock);
let binary = match entry.clone().await {
let binary = match entry.await {
Ok(binary) => binary,
Err(err) => anyhow::bail!("{err}"),
};
@ -1047,7 +1114,7 @@ impl LanguageRegistryState {
fn add(&mut self, language: Arc<Language>) {
if let Some(theme) = self.theme.as_ref() {
language.set_theme(&theme.editor.syntax);
language.set_theme(&theme.syntax());
}
self.languages.push(language);
self.version += 1;
@ -1387,9 +1454,9 @@ impl Language {
let query = Query::new(&self.grammar_mut().ts_language, source)?;
let mut override_configs_by_id = HashMap::default();
for (ix, name) in query.capture_names().iter().copied().enumerate() {
for (ix, name) in query.capture_names().iter().enumerate() {
if !name.starts_with('_') {
let value = self.config.overrides.remove(name).unwrap_or_default();
let value = self.config.overrides.remove(*name).unwrap_or_default();
for server_name in &value.opt_into_language_servers {
if !self
.config
@ -1400,7 +1467,7 @@ impl Language {
}
}
override_configs_by_id.insert(ix as u32, (name.into(), value));
override_configs_by_id.insert(ix as u32, (name.to_string(), value));
}
}
@ -1855,7 +1922,8 @@ mod tests {
#[gpui::test(iterations = 10)]
async fn test_first_line_pattern(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.background());
languages.set_executor(cx.executor());
let languages = Arc::new(languages);
languages.register(
"/javascript",
@ -1892,7 +1960,7 @@ mod tests {
#[gpui::test(iterations = 10)]
async fn test_language_loading(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test();
languages.set_executor(cx.background());
languages.set_executor(cx.executor());
let languages = Arc::new(languages);
languages.register(
"/JSON",

View File

@ -8,10 +8,11 @@ use schemars::{
JsonSchema,
};
use serde::{Deserialize, Serialize};
use settings::Settings;
use std::{num::NonZeroU32, path::Path, sync::Arc};
pub fn init(cx: &mut AppContext) {
settings::register::<AllLanguageSettings>(cx);
AllLanguageSettings::register(cx);
}
pub fn language_settings<'a>(
@ -28,7 +29,7 @@ pub fn all_language_settings<'a>(
cx: &'a AppContext,
) -> &'a AllLanguageSettings {
let location = file.map(|f| (f.worktree_id(), f.path().as_ref()));
settings::get_local(location, cx)
AllLanguageSettings::get(location, cx)
}
#[derive(Debug, Clone)]
@ -254,7 +255,7 @@ impl InlayHintKind {
}
}
impl settings::Setting for AllLanguageSettings {
impl settings::Settings for AllLanguageSettings {
const KEY: Option<&'static str> = None;
type FileContent = AllLanguageSettingsContent;
@ -262,7 +263,7 @@ impl settings::Setting for AllLanguageSettings {
fn load(
default_value: &Self::FileContent,
user_settings: &[&Self::FileContent],
_: &AppContext,
_: &mut AppContext,
) -> Result<Self> {
// A default is provided for all settings.
let mut defaults: LanguageSettings =

View File

@ -2,7 +2,7 @@ use std::sync::Arc;
use std::{ops::Range, path::PathBuf};
use crate::{HighlightId, Language, LanguageRegistry};
use gpui::fonts::{self, HighlightStyle, Weight};
use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
#[derive(Debug, Clone)]
@ -26,18 +26,18 @@ impl MarkdownHighlight {
let mut highlight = HighlightStyle::default();
if style.italic {
highlight.italic = Some(true);
highlight.font_style = Some(FontStyle::Italic);
}
if style.underline {
highlight.underline = Some(fonts::Underline {
thickness: 1.0.into(),
highlight.underline = Some(UnderlineStyle {
thickness: px(1.),
..Default::default()
});
}
if style.weight != fonts::Weight::default() {
highlight.weight = Some(style.weight);
if style.weight != FontWeight::default() {
highlight.font_weight = Some(style.weight);
}
Some(highlight)
@ -52,7 +52,7 @@ impl MarkdownHighlight {
pub struct MarkdownHighlightStyle {
pub italic: bool,
pub underline: bool,
pub weight: Weight,
pub weight: FontWeight,
}
#[derive(Debug, Clone)]
@ -138,7 +138,7 @@ pub async fn parse_markdown_block(
let mut style = MarkdownHighlightStyle::default();
if bold_depth > 0 {
style.weight = Weight::BOLD;
style.weight = FontWeight::BOLD;
}
if italic_depth > 0 {

View File

@ -1,6 +1,6 @@
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{executor::Background, fonts::HighlightStyle};
use std::{ops::Range, sync::Arc};
use gpui::{BackgroundExecutor, HighlightStyle};
use std::ops::Range;
#[derive(Debug)]
pub struct Outline<T> {
@ -57,7 +57,7 @@ impl<T> Outline<T> {
}
}
pub async fn search(&self, query: &str, executor: Arc<Background>) -> Vec<StringMatch> {
pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
let query = query.trim_start();
let is_path_query = query.contains(' ');
let smart_case = query.chars().any(|c| c.is_uppercase());
@ -81,6 +81,7 @@ impl<T> Outline<T> {
let mut prev_item_ix = 0;
for mut string_match in matches {
let outline_match = &self.items[string_match.candidate_id];
string_match.string = outline_match.text.clone();
if is_path_query {
let prefix_len = self.path_candidate_prefixes[string_match.candidate_id];

View File

@ -7,7 +7,6 @@ use futures::FutureExt;
use parking_lot::Mutex;
use std::{
borrow::Cow,
cell::RefCell,
cmp::{self, Ordering, Reverse},
collections::BinaryHeap,
fmt, iter,
@ -16,13 +15,9 @@ use std::{
};
use sum_tree::{Bias, SeekTarget, SumTree};
use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint};
use tree_sitter::{
Node, Parser, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree,
};
use tree_sitter::{Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatches, Tree};
thread_local! {
static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
}
use super::PARSER;
static QUERY_CURSORS: Mutex<Vec<QueryCursor>> = Mutex::new(vec![]);

View File

@ -1,86 +0,0 @@
[package]
name = "language2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/language2.rs"
doctest = false
[features]
test-support = [
"rand",
"client/test-support",
"collections/test-support",
"lsp/test-support",
"text/test-support",
"tree-sitter-rust",
"tree-sitter-typescript",
"settings/test-support",
"util/test-support",
]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
git = { package = "git3", path = "../git3" }
gpui = { package = "gpui2", path = "../gpui2" }
lsp = { package = "lsp2", path = "../lsp2" }
rpc = { package = "rpc2", path = "../rpc2" }
settings = { package = "settings2", path = "../settings2" }
sum_tree = { path = "../sum_tree" }
text = { package = "text2", path = "../text2" }
theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
anyhow.workspace = true
async-broadcast = "0.4"
async-trait.workspace = true
futures.workspace = true
globset.workspace = true
lazy_static.workspace = true
log.workspace = true
parking_lot.workspace = true
postage.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
similar = "1.3"
smallvec.workspace = true
smol.workspace = true
tree-sitter.workspace = true
unicase = "2.6"
rand = { workspace = true, optional = true }
tree-sitter-rust = { workspace = true, optional = true }
tree-sitter-typescript = { workspace = true, optional = true }
pulldown-cmark = { version = "0.9.2", default-features = false }
[dev-dependencies]
client = { package = "client2", path = "../client2", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
text = { package = "text2", path = "../text2", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true
indoc.workspace = true
rand.workspace = true
unindent.workspace = true
tree-sitter-embedded-template.workspace = true
tree-sitter-html.workspace = true
tree-sitter-json.workspace = true
tree-sitter-markdown.workspace = true
tree-sitter-rust.workspace = true
tree-sitter-python.workspace = true
tree-sitter-typescript.workspace = true
tree-sitter-ruby.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-heex.workspace = true

View File

@ -1,5 +0,0 @@
fn main() {
if let Ok(bundled) = std::env::var("ZED_BUNDLE") {
println!("cargo:rustc-env=ZED_BUNDLE={}", bundled);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,236 +0,0 @@
use crate::Diagnostic;
use collections::HashMap;
use lsp::LanguageServerId;
use std::{
cmp::{Ordering, Reverse},
iter,
ops::Range,
};
use sum_tree::{self, Bias, SumTree};
use text::{Anchor, FromAnchor, PointUtf16, ToOffset};
#[derive(Clone, Debug, Default)]
pub struct DiagnosticSet {
diagnostics: SumTree<DiagnosticEntry<Anchor>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticEntry<T> {
pub range: Range<T>,
pub diagnostic: Diagnostic,
}
#[derive(Debug)]
pub struct DiagnosticGroup<T> {
pub entries: Vec<DiagnosticEntry<T>>,
pub primary_ix: usize,
}
#[derive(Clone, Debug)]
pub struct Summary {
start: Anchor,
end: Anchor,
min_start: Anchor,
max_end: Anchor,
count: usize,
}
impl<T> DiagnosticEntry<T> {
// Used to provide diagnostic context to lsp codeAction request
pub fn to_lsp_diagnostic_stub(&self) -> lsp::Diagnostic {
let code = self
.diagnostic
.code
.clone()
.map(lsp::NumberOrString::String);
lsp::Diagnostic {
code,
severity: Some(self.diagnostic.severity),
..Default::default()
}
}
}
impl DiagnosticSet {
pub fn from_sorted_entries<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
where
I: IntoIterator<Item = DiagnosticEntry<Anchor>>,
{
Self {
diagnostics: SumTree::from_iter(iter, buffer),
}
}
pub fn new<I>(iter: I, buffer: &text::BufferSnapshot) -> Self
where
I: IntoIterator<Item = DiagnosticEntry<PointUtf16>>,
{
let mut entries = iter.into_iter().collect::<Vec<_>>();
entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end)));
Self {
diagnostics: SumTree::from_iter(
entries.into_iter().map(|entry| DiagnosticEntry {
range: buffer.anchor_before(entry.range.start)
..buffer.anchor_before(entry.range.end),
diagnostic: entry.diagnostic,
}),
buffer,
),
}
}
pub fn len(&self) -> usize {
self.diagnostics.summary().count
}
pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
self.diagnostics.iter()
}
pub fn range<'a, T, O>(
&'a self,
range: Range<T>,
buffer: &'a text::BufferSnapshot,
inclusive: bool,
reversed: bool,
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
where
T: 'a + ToOffset,
O: FromAnchor,
{
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
let range = buffer.anchor_before(range.start)..buffer.anchor_at(range.end, end_bias);
let mut cursor = self.diagnostics.filter::<_, ()>({
move |summary: &Summary| {
let start_cmp = range.start.cmp(&summary.max_end, buffer);
let end_cmp = range.end.cmp(&summary.min_start, buffer);
if inclusive {
start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
} else {
start_cmp == Ordering::Less && end_cmp == Ordering::Greater
}
}
});
if reversed {
cursor.prev(buffer);
} else {
cursor.next(buffer);
}
iter::from_fn({
move || {
if let Some(diagnostic) = cursor.item() {
if reversed {
cursor.prev(buffer);
} else {
cursor.next(buffer);
}
Some(diagnostic.resolve(buffer))
} else {
None
}
}
})
}
pub fn groups(
&self,
language_server_id: LanguageServerId,
output: &mut Vec<(LanguageServerId, DiagnosticGroup<Anchor>)>,
buffer: &text::BufferSnapshot,
) {
let mut groups = HashMap::default();
for entry in self.diagnostics.iter() {
groups
.entry(entry.diagnostic.group_id)
.or_insert(Vec::new())
.push(entry.clone());
}
let start_ix = output.len();
output.extend(groups.into_values().filter_map(|mut entries| {
entries.sort_unstable_by(|a, b| a.range.start.cmp(&b.range.start, buffer));
entries
.iter()
.position(|entry| entry.diagnostic.is_primary)
.map(|primary_ix| {
(
language_server_id,
DiagnosticGroup {
entries,
primary_ix,
},
)
})
}));
output[start_ix..].sort_unstable_by(|(id_a, group_a), (id_b, group_b)| {
group_a.entries[group_a.primary_ix]
.range
.start
.cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
.then_with(|| id_a.cmp(&id_b))
});
}
pub fn group<'a, O: FromAnchor>(
&'a self,
group_id: usize,
buffer: &'a text::BufferSnapshot,
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> {
self.iter()
.filter(move |entry| entry.diagnostic.group_id == group_id)
.map(|entry| entry.resolve(buffer))
}
}
impl sum_tree::Item for DiagnosticEntry<Anchor> {
type Summary = Summary;
fn summary(&self) -> Self::Summary {
Summary {
start: self.range.start,
end: self.range.end,
min_start: self.range.start,
max_end: self.range.end,
count: 1,
}
}
}
impl DiagnosticEntry<Anchor> {
pub fn resolve<O: FromAnchor>(&self, buffer: &text::BufferSnapshot) -> DiagnosticEntry<O> {
DiagnosticEntry {
range: O::from_anchor(&self.range.start, buffer)
..O::from_anchor(&self.range.end, buffer),
diagnostic: self.diagnostic.clone(),
}
}
}
impl Default for Summary {
fn default() -> Self {
Self {
start: Anchor::MIN,
end: Anchor::MAX,
min_start: Anchor::MAX,
max_end: Anchor::MIN,
count: 0,
}
}
}
impl sum_tree::Summary for Summary {
type Context = text::BufferSnapshot;
fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
if other.min_start.cmp(&self.min_start, buffer).is_lt() {
self.min_start = other.min_start;
}
if other.max_end.cmp(&self.max_end, buffer).is_gt() {
self.max_end = other.max_end;
}
self.start = other.start;
self.end = other.end;
self.count += other.count;
}
}

View File

@ -1,111 +0,0 @@
use gpui::HighlightStyle;
use std::sync::Arc;
use theme::SyntaxTheme;
#[derive(Clone, Debug)]
pub struct HighlightMap(Arc<[HighlightId]>);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HighlightId(pub u32);
const DEFAULT_SYNTAX_HIGHLIGHT_ID: HighlightId = HighlightId(u32::MAX);
impl HighlightMap {
pub fn new(capture_names: &[&str], theme: &SyntaxTheme) -> Self {
// For each capture name in the highlight query, find the longest
// key in the theme's syntax styles that matches all of the
// dot-separated components of the capture name.
HighlightMap(
capture_names
.iter()
.map(|capture_name| {
theme
.highlights
.iter()
.enumerate()
.filter_map(|(i, (key, _))| {
let mut len = 0;
let capture_parts = capture_name.split('.');
for key_part in key.split('.') {
if capture_parts.clone().any(|part| part == key_part) {
len += 1;
} else {
return None;
}
}
Some((i, len))
})
.max_by_key(|(_, len)| *len)
.map_or(DEFAULT_SYNTAX_HIGHLIGHT_ID, |(i, _)| HighlightId(i as u32))
})
.collect(),
)
}
pub fn get(&self, capture_id: u32) -> HighlightId {
self.0
.get(capture_id as usize)
.copied()
.unwrap_or(DEFAULT_SYNTAX_HIGHLIGHT_ID)
}
}
impl HighlightId {
pub fn is_default(&self) -> bool {
*self == DEFAULT_SYNTAX_HIGHLIGHT_ID
}
pub fn style(&self, theme: &SyntaxTheme) -> Option<HighlightStyle> {
theme.highlights.get(self.0 as usize).map(|entry| entry.1)
}
pub fn name<'a>(&self, theme: &'a SyntaxTheme) -> Option<&'a str> {
theme.highlights.get(self.0 as usize).map(|e| e.0.as_str())
}
}
impl Default for HighlightMap {
fn default() -> Self {
Self(Arc::new([]))
}
}
impl Default for HighlightId {
fn default() -> Self {
DEFAULT_SYNTAX_HIGHLIGHT_ID
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::rgba;
#[test]
fn test_highlight_map() {
let theme = SyntaxTheme {
highlights: [
("function", rgba(0x100000ff)),
("function.method", rgba(0x200000ff)),
("function.async", rgba(0x300000ff)),
("variable.builtin.self.rust", rgba(0x400000ff)),
("variable.builtin", rgba(0x500000ff)),
("variable", rgba(0x600000ff)),
]
.iter()
.map(|(name, color)| (name.to_string(), (*color).into()))
.collect(),
};
let capture_names = &[
"function.special",
"function.async.rust",
"variable.builtin.self",
];
let map = HighlightMap::new(capture_names, &theme);
assert_eq!(map.get(0).name(&theme), Some("function"));
assert_eq!(map.get(1).name(&theme), Some("function.async"));
assert_eq!(map.get(2).name(&theme), Some("variable.builtin"));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,431 +0,0 @@
use crate::{File, Language};
use anyhow::Result;
use collections::{HashMap, HashSet};
use globset::GlobMatcher;
use gpui::AppContext;
use schemars::{
schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
JsonSchema,
};
use serde::{Deserialize, Serialize};
use settings::Settings;
use std::{num::NonZeroU32, path::Path, sync::Arc};
pub fn init(cx: &mut AppContext) {
AllLanguageSettings::register(cx);
}
pub fn language_settings<'a>(
language: Option<&Arc<Language>>,
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a LanguageSettings {
let language_name = language.map(|l| l.name());
all_language_settings(file, cx).language(language_name.as_deref())
}
pub fn all_language_settings<'a>(
file: Option<&Arc<dyn File>>,
cx: &'a AppContext,
) -> &'a AllLanguageSettings {
let location = file.map(|f| (f.worktree_id(), f.path().as_ref()));
AllLanguageSettings::get(location, cx)
}
#[derive(Debug, Clone)]
pub struct AllLanguageSettings {
pub copilot: CopilotSettings,
defaults: LanguageSettings,
languages: HashMap<Arc<str>, LanguageSettings>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct LanguageSettings {
pub tab_size: NonZeroU32,
pub hard_tabs: bool,
pub soft_wrap: SoftWrap,
pub preferred_line_length: u32,
pub show_wrap_guides: bool,
pub wrap_guides: Vec<usize>,
pub format_on_save: FormatOnSave,
pub remove_trailing_whitespace_on_save: bool,
pub ensure_final_newline_on_save: bool,
pub formatter: Formatter,
pub prettier: HashMap<String, serde_json::Value>,
pub enable_language_server: bool,
pub show_copilot_suggestions: bool,
pub show_whitespaces: ShowWhitespaceSetting,
pub extend_comment_on_newline: bool,
pub inlay_hints: InlayHintSettings,
}
#[derive(Clone, Debug, Default)]
pub struct CopilotSettings {
pub feature_enabled: bool,
pub disabled_globs: Vec<GlobMatcher>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
#[serde(default)]
pub features: Option<FeaturesContent>,
#[serde(default)]
pub copilot: Option<CopilotSettingsContent>,
#[serde(flatten)]
pub defaults: LanguageSettingsContent,
#[serde(default, alias = "language_overrides")]
pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct LanguageSettingsContent {
#[serde(default)]
pub tab_size: Option<NonZeroU32>,
#[serde(default)]
pub hard_tabs: Option<bool>,
#[serde(default)]
pub soft_wrap: Option<SoftWrap>,
#[serde(default)]
pub preferred_line_length: Option<u32>,
#[serde(default)]
pub show_wrap_guides: Option<bool>,
#[serde(default)]
pub wrap_guides: Option<Vec<usize>>,
#[serde(default)]
pub format_on_save: Option<FormatOnSave>,
#[serde(default)]
pub remove_trailing_whitespace_on_save: Option<bool>,
#[serde(default)]
pub ensure_final_newline_on_save: Option<bool>,
#[serde(default)]
pub formatter: Option<Formatter>,
#[serde(default)]
pub prettier: Option<HashMap<String, serde_json::Value>>,
#[serde(default)]
pub enable_language_server: Option<bool>,
#[serde(default)]
pub show_copilot_suggestions: Option<bool>,
#[serde(default)]
pub show_whitespaces: Option<ShowWhitespaceSetting>,
#[serde(default)]
pub extend_comment_on_newline: Option<bool>,
#[serde(default)]
pub inlay_hints: Option<InlayHintSettings>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct CopilotSettingsContent {
#[serde(default)]
pub disabled_globs: Option<Vec<String>>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FeaturesContent {
pub copilot: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SoftWrap {
None,
EditorWidth,
PreferredLineLength,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum FormatOnSave {
On,
Off,
LanguageServer,
External {
command: Arc<str>,
arguments: Arc<[String]>,
},
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ShowWhitespaceSetting {
Selection,
None,
All,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Formatter {
#[default]
Auto,
LanguageServer,
Prettier,
External {
command: Arc<str>,
arguments: Arc<[String]>,
},
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct InlayHintSettings {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_true")]
pub show_type_hints: bool,
#[serde(default = "default_true")]
pub show_parameter_hints: bool,
#[serde(default = "default_true")]
pub show_other_hints: bool,
}
fn default_true() -> bool {
true
}
impl InlayHintSettings {
pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
let mut kinds = HashSet::default();
if self.show_type_hints {
kinds.insert(Some(InlayHintKind::Type));
}
if self.show_parameter_hints {
kinds.insert(Some(InlayHintKind::Parameter));
}
if self.show_other_hints {
kinds.insert(None);
}
kinds
}
}
impl AllLanguageSettings {
pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
if let Some(name) = language_name {
if let Some(overrides) = self.languages.get(name) {
return overrides;
}
}
&self.defaults
}
pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
!self
.copilot
.disabled_globs
.iter()
.any(|glob| glob.is_match(path))
}
pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
if !self.copilot.feature_enabled {
return false;
}
if let Some(path) = path {
if !self.copilot_enabled_for_path(path) {
return false;
}
}
self.language(language.map(|l| l.name()).as_deref())
.show_copilot_suggestions
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlayHintKind {
Type,
Parameter,
}
impl InlayHintKind {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"type" => Some(InlayHintKind::Type),
"parameter" => Some(InlayHintKind::Parameter),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
InlayHintKind::Type => "type",
InlayHintKind::Parameter => "parameter",
}
}
}
impl settings::Settings for AllLanguageSettings {
const KEY: Option<&'static str> = None;
type FileContent = AllLanguageSettingsContent;
fn load(
default_value: &Self::FileContent,
user_settings: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
// A default is provided for all settings.
let mut defaults: LanguageSettings =
serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
let mut languages = HashMap::default();
for (language_name, settings) in &default_value.languages {
let mut language_settings = defaults.clone();
merge_settings(&mut language_settings, &settings);
languages.insert(language_name.clone(), language_settings);
}
let mut copilot_enabled = default_value
.features
.as_ref()
.and_then(|f| f.copilot)
.ok_or_else(Self::missing_default)?;
let mut copilot_globs = default_value
.copilot
.as_ref()
.and_then(|c| c.disabled_globs.as_ref())
.ok_or_else(Self::missing_default)?;
for user_settings in user_settings {
if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
copilot_enabled = copilot;
}
if let Some(globs) = user_settings
.copilot
.as_ref()
.and_then(|f| f.disabled_globs.as_ref())
{
copilot_globs = globs;
}
// A user's global settings override the default global settings and
// all default language-specific settings.
merge_settings(&mut defaults, &user_settings.defaults);
for language_settings in languages.values_mut() {
merge_settings(language_settings, &user_settings.defaults);
}
// A user's language-specific settings override default language-specific settings.
for (language_name, user_language_settings) in &user_settings.languages {
merge_settings(
languages
.entry(language_name.clone())
.or_insert_with(|| defaults.clone()),
&user_language_settings,
);
}
}
Ok(Self {
copilot: CopilotSettings {
feature_enabled: copilot_enabled,
disabled_globs: copilot_globs
.iter()
.filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
.collect(),
},
defaults,
languages,
})
}
fn json_schema(
generator: &mut schemars::gen::SchemaGenerator,
params: &settings::SettingsJsonSchemaParams,
_: &AppContext,
) -> schemars::schema::RootSchema {
let mut root_schema = generator.root_schema_for::<Self::FileContent>();
// Create a schema for a 'languages overrides' object, associating editor
// settings with specific languages.
assert!(root_schema
.definitions
.contains_key("LanguageSettingsContent"));
let languages_object_schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()),
object: Some(Box::new(ObjectValidation {
properties: params
.language_names
.iter()
.map(|name| {
(
name.clone(),
Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
)
})
.collect(),
..Default::default()
})),
..Default::default()
};
root_schema
.definitions
.extend([("Languages".into(), languages_object_schema.into())]);
root_schema
.schema
.object
.as_mut()
.unwrap()
.properties
.extend([
(
"languages".to_owned(),
Schema::new_ref("#/definitions/Languages".into()),
),
// For backward compatibility
(
"language_overrides".to_owned(),
Schema::new_ref("#/definitions/Languages".into()),
),
]);
root_schema
}
}
fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
merge(&mut settings.tab_size, src.tab_size);
merge(&mut settings.hard_tabs, src.hard_tabs);
merge(&mut settings.soft_wrap, src.soft_wrap);
merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
merge(&mut settings.wrap_guides, src.wrap_guides.clone());
merge(
&mut settings.preferred_line_length,
src.preferred_line_length,
);
merge(&mut settings.formatter, src.formatter.clone());
merge(&mut settings.prettier, src.prettier.clone());
merge(&mut settings.format_on_save, src.format_on_save.clone());
merge(
&mut settings.remove_trailing_whitespace_on_save,
src.remove_trailing_whitespace_on_save,
);
merge(
&mut settings.ensure_final_newline_on_save,
src.ensure_final_newline_on_save,
);
merge(
&mut settings.enable_language_server,
src.enable_language_server,
);
merge(
&mut settings.show_copilot_suggestions,
src.show_copilot_suggestions,
);
merge(&mut settings.show_whitespaces, src.show_whitespaces);
merge(
&mut settings.extend_comment_on_newline,
src.extend_comment_on_newline,
);
merge(&mut settings.inlay_hints, src.inlay_hints);
fn merge<T>(target: &mut T, value: Option<T>) {
if let Some(value) = value {
*target = value;
}
}
}

View File

@ -1,301 +0,0 @@
use std::sync::Arc;
use std::{ops::Range, path::PathBuf};
use crate::{HighlightId, Language, LanguageRegistry};
use gpui::{px, FontStyle, FontWeight, HighlightStyle, UnderlineStyle};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
#[derive(Debug, Clone)]
pub struct ParsedMarkdown {
pub text: String,
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
pub region_ranges: Vec<Range<usize>>,
pub regions: Vec<ParsedRegion>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MarkdownHighlight {
Style(MarkdownHighlightStyle),
Code(HighlightId),
}
impl MarkdownHighlight {
pub fn to_highlight_style(&self, theme: &theme::SyntaxTheme) -> Option<HighlightStyle> {
match self {
MarkdownHighlight::Style(style) => {
let mut highlight = HighlightStyle::default();
if style.italic {
highlight.font_style = Some(FontStyle::Italic);
}
if style.underline {
highlight.underline = Some(UnderlineStyle {
thickness: px(1.),
..Default::default()
});
}
if style.weight != FontWeight::default() {
highlight.font_weight = Some(style.weight);
}
Some(highlight)
}
MarkdownHighlight::Code(id) => id.style(theme),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MarkdownHighlightStyle {
pub italic: bool,
pub underline: bool,
pub weight: FontWeight,
}
#[derive(Debug, Clone)]
pub struct ParsedRegion {
pub code: bool,
pub link: Option<Link>,
}
#[derive(Debug, Clone)]
pub enum Link {
Web { url: String },
Path { path: PathBuf },
}
impl Link {
fn identify(text: String) -> Option<Link> {
if text.starts_with("http") {
return Some(Link::Web { url: text });
}
let path = PathBuf::from(text);
if path.is_absolute() {
return Some(Link::Path { path });
}
None
}
}
pub async fn parse_markdown(
markdown: &str,
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
) -> ParsedMarkdown {
let mut text = String::new();
let mut highlights = Vec::new();
let mut region_ranges = Vec::new();
let mut regions = Vec::new();
parse_markdown_block(
markdown,
language_registry,
language,
&mut text,
&mut highlights,
&mut region_ranges,
&mut regions,
)
.await;
ParsedMarkdown {
text,
highlights,
region_ranges,
regions,
}
}
pub async fn parse_markdown_block(
markdown: &str,
language_registry: &Arc<LanguageRegistry>,
language: Option<Arc<Language>>,
text: &mut String,
highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
region_ranges: &mut Vec<Range<usize>>,
regions: &mut Vec<ParsedRegion>,
) {
let mut bold_depth = 0;
let mut italic_depth = 0;
let mut link_url = None;
let mut current_language = None;
let mut list_stack = Vec::new();
for event in Parser::new_ext(&markdown, Options::all()) {
let prev_len = text.len();
match event {
Event::Text(t) => {
if let Some(language) = &current_language {
highlight_code(text, highlights, t.as_ref(), language);
} else {
text.push_str(t.as_ref());
let mut style = MarkdownHighlightStyle::default();
if bold_depth > 0 {
style.weight = FontWeight::BOLD;
}
if italic_depth > 0 {
style.italic = true;
}
if let Some(link) = link_url.clone().and_then(|u| Link::identify(u)) {
region_ranges.push(prev_len..text.len());
regions.push(ParsedRegion {
code: false,
link: Some(link),
});
style.underline = true;
}
if style != MarkdownHighlightStyle::default() {
let mut new_highlight = true;
if let Some((last_range, MarkdownHighlight::Style(last_style))) =
highlights.last_mut()
{
if last_range.end == prev_len && last_style == &style {
last_range.end = text.len();
new_highlight = false;
}
}
if new_highlight {
let range = prev_len..text.len();
highlights.push((range, MarkdownHighlight::Style(style)));
}
}
}
}
Event::Code(t) => {
text.push_str(t.as_ref());
region_ranges.push(prev_len..text.len());
let link = link_url.clone().and_then(|u| Link::identify(u));
if link.is_some() {
highlights.push((
prev_len..text.len(),
MarkdownHighlight::Style(MarkdownHighlightStyle {
underline: true,
..Default::default()
}),
));
}
regions.push(ParsedRegion { code: true, link });
}
Event::Start(tag) => match tag {
Tag::Paragraph => new_paragraph(text, &mut list_stack),
Tag::Heading(_, _, _) => {
new_paragraph(text, &mut list_stack);
bold_depth += 1;
}
Tag::CodeBlock(kind) => {
new_paragraph(text, &mut list_stack);
current_language = if let CodeBlockKind::Fenced(language) = kind {
language_registry
.language_for_name(language.as_ref())
.await
.ok()
} else {
language.clone()
}
}
Tag::Emphasis => italic_depth += 1,
Tag::Strong => bold_depth += 1,
Tag::Link(_, url, _) => link_url = Some(url.to_string()),
Tag::List(number) => {
list_stack.push((number, false));
}
Tag::Item => {
let len = list_stack.len();
if let Some((list_number, has_content)) = list_stack.last_mut() {
*has_content = false;
if !text.is_empty() && !text.ends_with('\n') {
text.push('\n');
}
for _ in 0..len - 1 {
text.push_str(" ");
}
if let Some(number) = list_number {
text.push_str(&format!("{}. ", number));
*number += 1;
*has_content = false;
} else {
text.push_str("- ");
}
}
}
_ => {}
},
Event::End(tag) => match tag {
Tag::Heading(_, _, _) => bold_depth -= 1,
Tag::CodeBlock(_) => current_language = None,
Tag::Emphasis => italic_depth -= 1,
Tag::Strong => bold_depth -= 1,
Tag::Link(_, _, _) => link_url = None,
Tag::List(_) => drop(list_stack.pop()),
_ => {}
},
Event::HardBreak => text.push('\n'),
Event::SoftBreak => text.push(' '),
_ => {}
}
}
}
pub fn highlight_code(
text: &mut String,
highlights: &mut Vec<(Range<usize>, MarkdownHighlight)>,
content: &str,
language: &Arc<Language>,
) {
let prev_len = text.len();
text.push_str(content);
for (range, highlight_id) in language.highlight_text(&content.into(), 0..content.len()) {
let highlight = MarkdownHighlight::Code(highlight_id);
highlights.push((prev_len + range.start..prev_len + range.end, highlight));
}
}
pub fn new_paragraph(text: &mut String, list_stack: &mut Vec<(Option<u64>, bool)>) {
let mut is_subsequent_paragraph_of_list = false;
if let Some((_, has_content)) = list_stack.last_mut() {
if *has_content {
is_subsequent_paragraph_of_list = true;
} else {
*has_content = true;
return;
}
}
if !text.is_empty() {
if !text.ends_with('\n') {
text.push('\n');
}
text.push('\n');
}
for _ in 0..list_stack.len().saturating_sub(1) {
text.push_str(" ");
}
if is_subsequent_paragraph_of_list {
text.push_str(" ");
}
}

View File

@ -1,139 +0,0 @@
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{BackgroundExecutor, HighlightStyle};
use std::ops::Range;
#[derive(Debug)]
pub struct Outline<T> {
pub items: Vec<OutlineItem<T>>,
candidates: Vec<StringMatchCandidate>,
path_candidates: Vec<StringMatchCandidate>,
path_candidate_prefixes: Vec<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OutlineItem<T> {
pub depth: usize,
pub range: Range<T>,
pub text: String,
pub highlight_ranges: Vec<(Range<usize>, HighlightStyle)>,
pub name_ranges: Vec<Range<usize>>,
}
impl<T> Outline<T> {
pub fn new(items: Vec<OutlineItem<T>>) -> Self {
let mut candidates = Vec::new();
let mut path_candidates = Vec::new();
let mut path_candidate_prefixes = Vec::new();
let mut path_text = String::new();
let mut path_stack = Vec::new();
for (id, item) in items.iter().enumerate() {
if item.depth < path_stack.len() {
path_stack.truncate(item.depth);
path_text.truncate(path_stack.last().copied().unwrap_or(0));
}
if !path_text.is_empty() {
path_text.push(' ');
}
path_candidate_prefixes.push(path_text.len());
path_text.push_str(&item.text);
path_stack.push(path_text.len());
let candidate_text = item
.name_ranges
.iter()
.map(|range| &item.text[range.start as usize..range.end as usize])
.collect::<String>();
path_candidates.push(StringMatchCandidate::new(id, path_text.clone()));
candidates.push(StringMatchCandidate::new(id, candidate_text));
}
Self {
candidates,
path_candidates,
path_candidate_prefixes,
items,
}
}
pub async fn search(&self, query: &str, executor: BackgroundExecutor) -> Vec<StringMatch> {
let query = query.trim_start();
let is_path_query = query.contains(' ');
let smart_case = query.chars().any(|c| c.is_uppercase());
let mut matches = fuzzy::match_strings(
if is_path_query {
&self.path_candidates
} else {
&self.candidates
},
query,
smart_case,
100,
&Default::default(),
executor.clone(),
)
.await;
matches.sort_unstable_by_key(|m| m.candidate_id);
let mut tree_matches = Vec::new();
let mut prev_item_ix = 0;
for mut string_match in matches {
let outline_match = &self.items[string_match.candidate_id];
string_match.string = outline_match.text.clone();
if is_path_query {
let prefix_len = self.path_candidate_prefixes[string_match.candidate_id];
string_match
.positions
.retain(|position| *position >= prefix_len);
for position in &mut string_match.positions {
*position -= prefix_len;
}
} else {
let mut name_ranges = outline_match.name_ranges.iter();
let mut name_range = name_ranges.next().unwrap();
let mut preceding_ranges_len = 0;
for position in &mut string_match.positions {
while *position >= preceding_ranges_len + name_range.len() as usize {
preceding_ranges_len += name_range.len();
name_range = name_ranges.next().unwrap();
}
*position = name_range.start as usize + (*position - preceding_ranges_len);
}
}
let insertion_ix = tree_matches.len();
let mut cur_depth = outline_match.depth;
for (ix, item) in self.items[prev_item_ix..string_match.candidate_id]
.iter()
.enumerate()
.rev()
{
if cur_depth == 0 {
break;
}
let candidate_index = ix + prev_item_ix;
if item.depth == cur_depth - 1 {
tree_matches.insert(
insertion_ix,
StringMatch {
candidate_id: candidate_index,
score: Default::default(),
positions: Default::default(),
string: Default::default(),
},
);
cur_depth -= 1;
}
}
prev_item_ix = string_match.candidate_id + 1;
tree_matches.push(string_match);
}
tree_matches
}
}

View File

@ -1,590 +0,0 @@
use crate::{
diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
Language,
};
use anyhow::{anyhow, Result};
use clock::ReplicaId;
use lsp::{DiagnosticSeverity, LanguageServerId};
use rpc::proto;
use std::{ops::Range, sync::Arc};
use text::*;
pub use proto::{BufferState, Operation};
pub fn serialize_fingerprint(fingerprint: RopeFingerprint) -> String {
fingerprint.to_hex()
}
pub fn deserialize_fingerprint(fingerprint: &str) -> Result<RopeFingerprint> {
RopeFingerprint::from_hex(fingerprint)
.map_err(|error| anyhow!("invalid fingerprint: {}", error))
}
pub fn deserialize_line_ending(message: proto::LineEnding) -> text::LineEnding {
match message {
proto::LineEnding::Unix => text::LineEnding::Unix,
proto::LineEnding::Windows => text::LineEnding::Windows,
}
}
pub fn serialize_line_ending(message: text::LineEnding) -> proto::LineEnding {
match message {
text::LineEnding::Unix => proto::LineEnding::Unix,
text::LineEnding::Windows => proto::LineEnding::Windows,
}
}
pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
proto::Operation {
variant: Some(match operation {
crate::Operation::Buffer(text::Operation::Edit(edit)) => {
proto::operation::Variant::Edit(serialize_edit_operation(edit))
}
crate::Operation::Buffer(text::Operation::Undo(undo)) => {
proto::operation::Variant::Undo(proto::operation::Undo {
replica_id: undo.timestamp.replica_id as u32,
lamport_timestamp: undo.timestamp.value,
version: serialize_version(&undo.version),
counts: undo
.counts
.iter()
.map(|(edit_id, count)| proto::UndoCount {
replica_id: edit_id.replica_id as u32,
lamport_timestamp: edit_id.value,
count: *count,
})
.collect(),
})
}
crate::Operation::UpdateSelections {
selections,
line_mode,
lamport_timestamp,
cursor_shape,
} => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
selections: serialize_selections(selections),
line_mode: *line_mode,
cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
}),
crate::Operation::UpdateDiagnostics {
lamport_timestamp,
server_id,
diagnostics,
} => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
server_id: server_id.0 as u64,
diagnostics: serialize_diagnostics(diagnostics.iter()),
}),
crate::Operation::UpdateCompletionTriggers {
triggers,
lamport_timestamp,
} => proto::operation::Variant::UpdateCompletionTriggers(
proto::operation::UpdateCompletionTriggers {
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
triggers: triggers.clone(),
},
),
}),
}
}
pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
proto::operation::Edit {
replica_id: operation.timestamp.replica_id as u32,
lamport_timestamp: operation.timestamp.value,
version: serialize_version(&operation.version),
ranges: operation.ranges.iter().map(serialize_range).collect(),
new_text: operation
.new_text
.iter()
.map(|text| text.to_string())
.collect(),
}
}
pub fn serialize_undo_map_entry(
(edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]),
) -> proto::UndoMapEntry {
proto::UndoMapEntry {
replica_id: edit_id.replica_id as u32,
local_timestamp: edit_id.value,
counts: counts
.iter()
.map(|(undo_id, count)| proto::UndoCount {
replica_id: undo_id.replica_id as u32,
lamport_timestamp: undo_id.value,
count: *count,
})
.collect(),
}
}
pub fn split_operations(
mut operations: Vec<proto::Operation>,
) -> impl Iterator<Item = Vec<proto::Operation>> {
#[cfg(any(test, feature = "test-support"))]
const CHUNK_SIZE: usize = 5;
#[cfg(not(any(test, feature = "test-support")))]
const CHUNK_SIZE: usize = 100;
let mut done = false;
std::iter::from_fn(move || {
if done {
return None;
}
let operations = operations
.drain(..std::cmp::min(CHUNK_SIZE, operations.len()))
.collect::<Vec<_>>();
if operations.is_empty() {
done = true;
}
Some(operations)
})
}
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
selections.iter().map(serialize_selection).collect()
}
pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
proto::Selection {
id: selection.id as u64,
start: Some(proto::EditorAnchor {
anchor: Some(serialize_anchor(&selection.start)),
excerpt_id: 0,
}),
end: Some(proto::EditorAnchor {
anchor: Some(serialize_anchor(&selection.end)),
excerpt_id: 0,
}),
reversed: selection.reversed,
}
}
pub fn serialize_cursor_shape(cursor_shape: &CursorShape) -> proto::CursorShape {
match cursor_shape {
CursorShape::Bar => proto::CursorShape::CursorBar,
CursorShape::Block => proto::CursorShape::CursorBlock,
CursorShape::Underscore => proto::CursorShape::CursorUnderscore,
CursorShape::Hollow => proto::CursorShape::CursorHollow,
}
}
pub fn deserialize_cursor_shape(cursor_shape: proto::CursorShape) -> CursorShape {
match cursor_shape {
proto::CursorShape::CursorBar => CursorShape::Bar,
proto::CursorShape::CursorBlock => CursorShape::Block,
proto::CursorShape::CursorUnderscore => CursorShape::Underscore,
proto::CursorShape::CursorHollow => CursorShape::Hollow,
}
}
pub fn serialize_diagnostics<'a>(
diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<Anchor>>,
) -> Vec<proto::Diagnostic> {
diagnostics
.into_iter()
.map(|entry| proto::Diagnostic {
source: entry.diagnostic.source.clone(),
start: Some(serialize_anchor(&entry.range.start)),
end: Some(serialize_anchor(&entry.range.end)),
message: entry.diagnostic.message.clone(),
severity: match entry.diagnostic.severity {
DiagnosticSeverity::ERROR => proto::diagnostic::Severity::Error,
DiagnosticSeverity::WARNING => proto::diagnostic::Severity::Warning,
DiagnosticSeverity::INFORMATION => proto::diagnostic::Severity::Information,
DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint,
_ => proto::diagnostic::Severity::None,
} as i32,
group_id: entry.diagnostic.group_id as u64,
is_primary: entry.diagnostic.is_primary,
is_valid: entry.diagnostic.is_valid,
code: entry.diagnostic.code.clone(),
is_disk_based: entry.diagnostic.is_disk_based,
is_unnecessary: entry.diagnostic.is_unnecessary,
})
.collect()
}
pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
proto::Anchor {
replica_id: anchor.timestamp.replica_id as u32,
timestamp: anchor.timestamp.value,
offset: anchor.offset as u64,
bias: match anchor.bias {
Bias::Left => proto::Bias::Left as i32,
Bias::Right => proto::Bias::Right as i32,
},
buffer_id: anchor.buffer_id,
}
}
// This behavior is currently copied in the collab database, for snapshotting channel notes
pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operation> {
Ok(
match message
.variant
.ok_or_else(|| anyhow!("missing operation variant"))?
{
proto::operation::Variant::Edit(edit) => {
crate::Operation::Buffer(text::Operation::Edit(deserialize_edit_operation(edit)))
}
proto::operation::Variant::Undo(undo) => {
crate::Operation::Buffer(text::Operation::Undo(UndoOperation {
timestamp: clock::Lamport {
replica_id: undo.replica_id as ReplicaId,
value: undo.lamport_timestamp,
},
version: deserialize_version(&undo.version),
counts: undo
.counts
.into_iter()
.map(|c| {
(
clock::Lamport {
replica_id: c.replica_id as ReplicaId,
value: c.lamport_timestamp,
},
c.count,
)
})
.collect(),
}))
}
proto::operation::Variant::UpdateSelections(message) => {
let selections = message
.selections
.into_iter()
.filter_map(|selection| {
Some(Selection {
id: selection.id as usize,
start: deserialize_anchor(selection.start?.anchor?)?,
end: deserialize_anchor(selection.end?.anchor?)?,
reversed: selection.reversed,
goal: SelectionGoal::None,
})
})
.collect::<Vec<_>>();
crate::Operation::UpdateSelections {
lamport_timestamp: clock::Lamport {
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
selections: Arc::from(selections),
line_mode: message.line_mode,
cursor_shape: deserialize_cursor_shape(
proto::CursorShape::from_i32(message.cursor_shape)
.ok_or_else(|| anyhow!("Missing cursor shape"))?,
),
}
}
proto::operation::Variant::UpdateDiagnostics(message) => {
crate::Operation::UpdateDiagnostics {
lamport_timestamp: clock::Lamport {
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
server_id: LanguageServerId(message.server_id as usize),
diagnostics: deserialize_diagnostics(message.diagnostics),
}
}
proto::operation::Variant::UpdateCompletionTriggers(message) => {
crate::Operation::UpdateCompletionTriggers {
triggers: message.triggers,
lamport_timestamp: clock::Lamport {
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
}
}
},
)
}
pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
EditOperation {
timestamp: clock::Lamport {
replica_id: edit.replica_id as ReplicaId,
value: edit.lamport_timestamp,
},
version: deserialize_version(&edit.version),
ranges: edit.ranges.into_iter().map(deserialize_range).collect(),
new_text: edit.new_text.into_iter().map(Arc::from).collect(),
}
}
pub fn deserialize_undo_map_entry(
entry: proto::UndoMapEntry,
) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) {
(
clock::Lamport {
replica_id: entry.replica_id as u16,
value: entry.local_timestamp,
},
entry
.counts
.into_iter()
.map(|undo_count| {
(
clock::Lamport {
replica_id: undo_count.replica_id as u16,
value: undo_count.lamport_timestamp,
},
undo_count.count,
)
})
.collect(),
)
}
pub fn deserialize_selections(selections: Vec<proto::Selection>) -> Arc<[Selection<Anchor>]> {
Arc::from(
selections
.into_iter()
.filter_map(deserialize_selection)
.collect::<Vec<_>>(),
)
}
pub fn deserialize_selection(selection: proto::Selection) -> Option<Selection<Anchor>> {
Some(Selection {
id: selection.id as usize,
start: deserialize_anchor(selection.start?.anchor?)?,
end: deserialize_anchor(selection.end?.anchor?)?,
reversed: selection.reversed,
goal: SelectionGoal::None,
})
}
pub fn deserialize_diagnostics(
diagnostics: Vec<proto::Diagnostic>,
) -> Arc<[DiagnosticEntry<Anchor>]> {
diagnostics
.into_iter()
.filter_map(|diagnostic| {
Some(DiagnosticEntry {
range: deserialize_anchor(diagnostic.start?)?..deserialize_anchor(diagnostic.end?)?,
diagnostic: Diagnostic {
source: diagnostic.source,
severity: match proto::diagnostic::Severity::from_i32(diagnostic.severity)? {
proto::diagnostic::Severity::Error => DiagnosticSeverity::ERROR,
proto::diagnostic::Severity::Warning => DiagnosticSeverity::WARNING,
proto::diagnostic::Severity::Information => DiagnosticSeverity::INFORMATION,
proto::diagnostic::Severity::Hint => DiagnosticSeverity::HINT,
proto::diagnostic::Severity::None => return None,
},
message: diagnostic.message,
group_id: diagnostic.group_id as usize,
code: diagnostic.code,
is_valid: diagnostic.is_valid,
is_primary: diagnostic.is_primary,
is_disk_based: diagnostic.is_disk_based,
is_unnecessary: diagnostic.is_unnecessary,
},
})
})
.collect()
}
pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
Some(Anchor {
timestamp: clock::Lamport {
replica_id: anchor.replica_id as ReplicaId,
value: anchor.timestamp,
},
offset: anchor.offset as usize,
bias: match proto::Bias::from_i32(anchor.bias)? {
proto::Bias::Left => Bias::Left,
proto::Bias::Right => Bias::Right,
},
buffer_id: anchor.buffer_id,
})
}
pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<clock::Lamport> {
let replica_id;
let value;
match operation.variant.as_ref()? {
proto::operation::Variant::Edit(op) => {
replica_id = op.replica_id;
value = op.lamport_timestamp;
}
proto::operation::Variant::Undo(op) => {
replica_id = op.replica_id;
value = op.lamport_timestamp;
}
proto::operation::Variant::UpdateDiagnostics(op) => {
replica_id = op.replica_id;
value = op.lamport_timestamp;
}
proto::operation::Variant::UpdateSelections(op) => {
replica_id = op.replica_id;
value = op.lamport_timestamp;
}
proto::operation::Variant::UpdateCompletionTriggers(op) => {
replica_id = op.replica_id;
value = op.lamport_timestamp;
}
}
Some(clock::Lamport {
replica_id: replica_id as ReplicaId,
value,
})
}
pub fn serialize_completion(completion: &Completion) -> proto::Completion {
proto::Completion {
old_start: Some(serialize_anchor(&completion.old_range.start)),
old_end: Some(serialize_anchor(&completion.old_range.end)),
new_text: completion.new_text.clone(),
server_id: completion.server_id.0 as u64,
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
}
}
pub async fn deserialize_completion(
completion: proto::Completion,
language: Option<Arc<Language>>,
) -> Result<Completion> {
let old_start = completion
.old_start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid old start"))?;
let old_end = completion
.old_end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid old end"))?;
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
let mut label = None;
if let Some(language) = language {
label = language.label_for_completion(&lsp_completion).await;
}
Ok(Completion {
old_range: old_start..old_end,
new_text: completion.new_text,
label: label.unwrap_or_else(|| {
CodeLabel::plain(
lsp_completion.label.clone(),
lsp_completion.filter_text.as_deref(),
)
}),
documentation: None,
server_id: LanguageServerId(completion.server_id as usize),
lsp_completion,
})
}
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
proto::CodeAction {
server_id: action.server_id.0 as u64,
start: Some(serialize_anchor(&action.range.start)),
end: Some(serialize_anchor(&action.range.end)),
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
}
}
pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
let start = action
.start
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid start"))?;
let end = action
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid end"))?;
let lsp_action = serde_json::from_slice(&action.lsp_action)?;
Ok(CodeAction {
server_id: LanguageServerId(action.server_id as usize),
range: start..end,
lsp_action,
})
}
pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
proto::Transaction {
id: Some(serialize_timestamp(transaction.id)),
edit_ids: transaction
.edit_ids
.iter()
.copied()
.map(serialize_timestamp)
.collect(),
start: serialize_version(&transaction.start),
}
}
pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transaction> {
Ok(Transaction {
id: deserialize_timestamp(
transaction
.id
.ok_or_else(|| anyhow!("missing transaction id"))?,
),
edit_ids: transaction
.edit_ids
.into_iter()
.map(deserialize_timestamp)
.collect(),
start: deserialize_version(&transaction.start),
})
}
pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp {
proto::LamportTimestamp {
replica_id: timestamp.replica_id as u32,
value: timestamp.value,
}
}
pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport {
clock::Lamport {
replica_id: timestamp.replica_id as ReplicaId,
value: timestamp.value,
}
}
pub fn serialize_range(range: &Range<FullOffset>) -> proto::Range {
proto::Range {
start: range.start.0 as u64,
end: range.end.0 as u64,
}
}
pub fn deserialize_range(range: proto::Range) -> Range<FullOffset> {
FullOffset(range.start as usize)..FullOffset(range.end as usize)
}
pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global {
let mut version = clock::Global::new();
for entry in message {
version.observe(clock::Lamport {
replica_id: entry.replica_id as ReplicaId,
value: entry.timestamp,
});
}
version
}
pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
version
.iter()
.map(|entry| proto::VectorClockEntry {
replica_id: entry.replica_id as u32,
timestamp: entry.value,
})
.collect()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
language = { package = "language2", path = "../language2" }
fuzzy = { path = "../fuzzy" }
language = { path = "../language" }
gpui = { package = "gpui2", path = "../gpui2" }
picker = { path = "../picker" }
project = { path = "../project" }

View File

@ -13,13 +13,13 @@ collections = { path = "../collections" }
editor = { path = "../editor" }
settings = { package = "settings2", path = "../settings2" }
theme = { package = "theme2", path = "../theme2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
project = { path = "../project" }
workspace = { path = "../workspace" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
util = { path = "../util" }
lsp = { package = "lsp2", path = "../lsp2" }
lsp = { path = "../lsp" }
futures.workspace = true
serde.workspace = true
anyhow.workspace = true

View File

@ -13,7 +13,7 @@ test-support = ["async-pipe"]
[dependencies]
collections = { path = "../collections" }
gpui = { path = "../gpui" }
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }
anyhow.workspace = true
@ -29,7 +29,7 @@ serde_json.workspace = true
smol.workspace = true
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }

View File

@ -5,7 +5,7 @@ pub use lsp_types::*;
use anyhow::{anyhow, Context, Result};
use collections::HashMap;
use futures::{channel::oneshot, io::BufWriter, AsyncRead, AsyncWrite, FutureExt};
use gpui::{executor, AsyncAppContext, Task};
use gpui::{AsyncAppContext, BackgroundExecutor, Task};
use parking_lot::Mutex;
use postage::{barrier, prelude::Stream};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -62,7 +62,7 @@ pub struct LanguageServer {
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
io_handlers: Arc<Mutex<HashMap<usize, IoHandler>>>,
executor: Arc<executor::Background>,
executor: BackgroundExecutor,
#[allow(clippy::type_complexity)]
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
output_done_rx: Mutex<Option<barrier::Receiver>>,
@ -210,7 +210,7 @@ impl LanguageServer {
Stdin: AsyncWrite + Unpin + Send + 'static,
Stdout: AsyncRead + Unpin + Send + 'static,
Stderr: AsyncRead + Unpin + Send + 'static,
F: FnMut(AnyNotification) + 'static + Send + Clone,
F: FnMut(AnyNotification) + 'static + Send + Sync + Clone,
{
let (outbound_tx, outbound_rx) = channel::unbounded::<String>();
let (output_done_tx, output_done_rx) = barrier::channel();
@ -220,30 +220,35 @@ impl LanguageServer {
Arc::new(Mutex::new(Some(HashMap::<_, ResponseHandler>::default())));
let io_handlers = Arc::new(Mutex::new(HashMap::default()));
let stdout_input_task = cx.spawn(|cx| {
Self::handle_input(
stdout,
on_unhandled_notification.clone(),
notification_handlers.clone(),
response_handlers.clone(),
io_handlers.clone(),
cx,
)
.log_err()
let stdout_input_task = cx.spawn({
let on_unhandled_notification = on_unhandled_notification.clone();
let notification_handlers = notification_handlers.clone();
let response_handlers = response_handlers.clone();
let io_handlers = io_handlers.clone();
move |cx| {
Self::handle_input(
stdout,
on_unhandled_notification,
notification_handlers,
response_handlers,
io_handlers,
cx,
)
.log_err()
}
});
let stderr_input_task = stderr
.map(|stderr| {
cx.spawn(|_| {
Self::handle_stderr(stderr, io_handlers.clone(), stderr_capture.clone())
.log_err()
})
let io_handlers = io_handlers.clone();
let stderr_captures = stderr_capture.clone();
cx.spawn(|_| Self::handle_stderr(stderr, io_handlers, stderr_captures).log_err())
})
.unwrap_or_else(|| Task::Ready(Some(None)));
let input_task = cx.spawn(|_| async move {
let (stdout, stderr) = futures::join!(stdout_input_task, stderr_input_task);
stdout.or(stderr)
});
let output_task = cx.background().spawn({
let output_task = cx.background_executor().spawn({
Self::handle_output(
stdin,
outbound_rx,
@ -264,7 +269,7 @@ impl LanguageServer {
code_action_kinds,
next_id: Default::default(),
outbound_tx,
executor: cx.background(),
executor: cx.background_executor().clone(),
io_tasks: Mutex::new(Some((input_task, output_task))),
output_done_rx: Mutex::new(Some(output_done_rx)),
root_path: root_path.to_path_buf(),
@ -481,10 +486,7 @@ impl LanguageServer {
completion_item: Some(CompletionItemCapability {
snippet_support: Some(true),
resolve_support: Some(CompletionItemCapabilityResolveSupport {
properties: vec![
"documentation".to_string(),
"additionalTextEdits".to_string(),
],
properties: vec!["additionalTextEdits".to_string()],
}),
..Default::default()
}),
@ -610,7 +612,7 @@ impl LanguageServer {
where
T: request::Request,
T::Params: 'static + Send,
F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> Fut,
F: 'static + FnMut(T::Params, AsyncAppContext) -> Fut + Send,
Fut: 'static + Future<Output = Result<T::Result>>,
{
self.on_custom_request(T::METHOD, f)
@ -644,7 +646,7 @@ impl LanguageServer {
#[must_use]
pub fn on_custom_notification<Params, F>(&self, method: &'static str, mut f: F) -> Subscription
where
F: 'static + Send + FnMut(Params, AsyncAppContext),
F: 'static + FnMut(Params, AsyncAppContext) + Send,
Params: DeserializeOwned,
{
let prev_handler = self.notification_handlers.lock().insert(
@ -672,7 +674,7 @@ impl LanguageServer {
mut f: F,
) -> Subscription
where
F: 'static + Send + FnMut(Params, AsyncAppContext) -> Fut,
F: 'static + FnMut(Params, AsyncAppContext) -> Fut + Send,
Fut: 'static + Future<Output = Result<Res>>,
Params: DeserializeOwned + Send + 'static,
Res: Serialize,
@ -685,7 +687,7 @@ impl LanguageServer {
match serde_json::from_str(params) {
Ok(params) => {
let response = f(params, cx.clone());
cx.foreground()
cx.foreground_executor()
.spawn({
let outbound_tx = outbound_tx.clone();
async move {
@ -780,20 +782,11 @@ impl LanguageServer {
)
}
// some child of string literal (be it "" or ``) which is the child of an attribute
// <Foo className="bar" />
// <Foo className={`bar`} />
// <Foo className={something + "bar"} />
// <Foo className={something + "bar"} />
// const classes = "awesome ";
// <Foo className={classes} />
fn request_internal<T: request::Request>(
next_id: &AtomicUsize,
response_handlers: &Mutex<Option<HashMap<usize, ResponseHandler>>>,
outbound_tx: &channel::Sender<String>,
executor: &Arc<executor::Background>,
executor: &BackgroundExecutor,
params: T::Params,
) -> impl 'static + Future<Output = anyhow::Result<T::Result>>
where
@ -1071,8 +1064,9 @@ impl FakeLanguageServer {
.on_request::<T, _, _>(move |params, cx| {
let result = handler(params, cx.clone());
let responded_tx = responded_tx.clone();
let executor = cx.background_executor().clone();
async move {
cx.background().simulate_random_delay().await;
executor.simulate_random_delay().await;
let result = result.await;
responded_tx.unbounded_send(()).ok();
result

View File

@ -1,38 +0,0 @@
[package]
name = "lsp2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/lsp2.rs"
doctest = false
[features]
test-support = ["async-pipe"]
[dependencies]
collections = { path = "../collections" }
gpui = { package = "gpui2", path = "../gpui2" }
util = { path = "../util" }
anyhow.workspace = true
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553", optional = true }
futures.workspace = true
log.workspace = true
lsp-types = { git = "https://github.com/zed-industries/lsp-types", branch = "updated-completion-list-item-defaults" }
parking_lot.workspace = true
postage.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
smol.workspace = true
[dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "82d00a04211cf4e1236029aa03e6b6ce2a74c553" }
ctor.workspace = true
env_logger.workspace = true
unindent.workspace = true

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,8 @@ clock = { path = "../clock" }
collections = { path = "../collections" }
git = { package = "git3", path = "../git3" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
lsp = { package = "lsp2", path = "../lsp2" }
language = { path = "../language" }
lsp = { path = "../lsp" }
rich_text = { path = "../rich_text" }
settings = { package = "settings2", path = "../settings2" }
snippet = { path = "../snippet" }
@ -61,8 +61,8 @@ tree-sitter-typescript = { workspace = true, optional = true }
[dev-dependencies]
copilot = { path = "../copilot", features = ["test-support"] }
text = { package = "text2", path = "../text2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }

View File

@ -10,10 +10,10 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
picker = { path = "../picker" }
settings = { package = "settings2", path = "../settings2" }
text = { package = "text2", path = "../text2" }

View File

@ -14,10 +14,10 @@ test-support = []
[dependencies]
client = { package = "client2", path = "../client2" }
collections = { path = "../collections"}
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
gpui = { package = "gpui2", path = "../gpui2" }
fs = { package = "fs2", path = "../fs2" }
lsp = { package = "lsp2", path = "../lsp2" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime"}
util = { path = "../util" }
@ -30,6 +30,6 @@ futures.workspace = true
parking_lot.workspace = true
[dev-dependencies]
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }

View File

@ -28,11 +28,11 @@ collections = { path = "../collections" }
db = { package = "db2", path = "../db2" }
fs = { package = "fs2", path = "../fs2" }
fsevent = { path = "../fsevent" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
git = { package = "git3", path = "../git3" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
lsp = { package = "lsp2", path = "../lsp2" }
language = { path = "../language" }
lsp = { path = "../lsp" }
node_runtime = { path = "../node_runtime" }
prettier = { package = "prettier2", path = "../prettier2" }
rpc = { package = "rpc2", path = "../rpc2" }
@ -74,8 +74,8 @@ collections = { path = "../collections", features = ["test-support"] }
db = { package = "db2", path = "../db2", features = ["test-support"] }
fs = { package = "fs2", path = "../fs2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
prettier = { package = "prettier2", path = "../prettier2", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }

View File

@ -34,7 +34,7 @@ unicase = "2.6"
[dev-dependencies]
client = { path = "../client2", package = "client2", features = ["test-support"] }
language = { path = "../language2", package = "language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }

View File

@ -10,7 +10,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
picker = { path = "../picker" }
project = { path = "../project" }
@ -30,8 +30,8 @@ futures.workspace = true
editor = { path = "../editor", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }

View File

@ -10,9 +10,9 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
picker = { path = "../picker" }
settings = { package = "settings2", path = "../settings2" }
text = { package = "text2", path = "../text2" }

View File

@ -19,7 +19,7 @@ collections = { path = "../collections" }
gpui = { package = "gpui2", path = "../gpui2" }
sum_tree = { path = "../sum_tree" }
theme = { package = "theme2", path = "../theme2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
util = { path = "../util" }
anyhow.workspace = true
futures.workspace = true

View File

@ -13,7 +13,7 @@ bitflags = "1"
collections = { path = "../collections" }
editor = { path = "../editor" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
menu = { package = "menu2", path = "../menu2" }
project = { path = "../project" }
settings = { package = "settings2", path = "../settings2" }

View File

@ -12,7 +12,7 @@ doctest = false
ai = { path = "../ai" }
collections = { path = "../collections" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
project = { path = "../project" }
workspace = { path = "../workspace" }
util = { path = "../util" }
@ -42,7 +42,7 @@ ndarray = { version = "0.15.0" }
ai = { path = "../ai", features = ["test-support"] }
collections = { path = "../collections", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
workspace = { path = "../workspace", features = ["test-support"] }

View File

@ -16,11 +16,11 @@ chrono = "0.4"
clap = { version = "4.4", features = ["derive", "string"] }
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
editor = { path = "../editor" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
indoc.workspace = true
itertools = "0.11.0"
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
log.workspace = true
rust-embed.workspace = true
serde.workspace = true

View File

@ -10,7 +10,7 @@ doctest = false
[dependencies]
editor = { path = "../editor" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
gpui = { package = "gpui2", path = "../gpui2" }
project = { path = "../project" }
# search = { path = "../search" }

View File

@ -13,7 +13,7 @@ client = { package = "client2", path = "../client2" }
editor = { path = "../editor" }
feature_flags = { path = "../feature_flags" }
fs = { package = "fs2", path = "../fs2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
picker = { path = "../picker" }
settings = { package = "settings2", path = "../settings2" }

View File

@ -6,7 +6,7 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fuzzy = {package = "fuzzy2", path = "../fuzzy2"}
fuzzy = { path = "../fuzzy"}
fs = {package = "fs2", path = "../fs2"}
gpui = {package = "gpui2", path = "../gpui2"}
picker = {path = "../picker"}

View File

@ -28,7 +28,7 @@ collections = { path = "../collections" }
command_palette = { path = "../command_palette" }
editor = { path = "../editor" }
gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
search = { path = "../search" }
settings = { package = "settings2", path = "../settings2" }
workspace = { path = "../workspace" }
@ -44,10 +44,10 @@ futures.workspace = true
editor = { path = "../editor", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
settings = { package = "settings2", path = "../settings2" }
workspace = { path = "../workspace", features = ["test-support"] }
theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
lsp = { path = "../lsp", features = ["test-support"] }

View File

@ -14,7 +14,7 @@ test-support = []
client = { package = "client2", path = "../client2" }
editor = { path = "../editor" }
fs = { package = "fs2", path = "../fs2" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
fuzzy = { path = "../fuzzy" }
gpui = { package = "gpui2", path = "../gpui2" }
ui = { package = "ui2", path = "../ui2" }
db = { package = "db2", path = "../db2" }

View File

@ -27,7 +27,7 @@ collections = { path = "../collections" }
fs = { path = "../fs2", package = "fs2" }
gpui = { package = "gpui2", path = "../gpui2" }
install_cli = { path = "../install_cli" }
language = { path = "../language2", package = "language2" }
language = { path = "../language" }
#menu = { path = "../menu" }
node_runtime = { path = "../node_runtime" }
project = { path = "../project" }

View File

@ -43,9 +43,9 @@ go_to_line = { path = "../go_to_line" }
gpui = { package = "gpui2", path = "../gpui2" }
install_cli = { path = "../install_cli" }
journal = { path = "../journal" }
language = { package = "language2", path = "../language2" }
language = { path = "../language" }
language_selector = { path = "../language_selector" }
lsp = { package = "lsp2", path = "../lsp2" }
lsp = { path = "../lsp" }
menu = { package = "menu2", path = "../menu2" }
language_tools = { path = "../language_tools" }
node_runtime = { path = "../node_runtime" }
@ -149,7 +149,7 @@ call = { package = "call2", path = "../call2", features = ["test-support"] }
# editor = { path = "../editor", features = ["test-support"] }
# gpui = { path = "../gpui", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
language = { package = "language2", path = "../language2", features = ["test-support"] }
language = { path = "../language", features = ["test-support"] }
# lsp = { path = "../lsp", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
# rpc = { path = "../rpc", features = ["test-support"] }

View File

@ -3,7 +3,7 @@ use async_trait::async_trait;
use collections::HashMap;
use futures::lock::Mutex;
use gpui::executor::Background;
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
use std::{any::Any, path::PathBuf, sync::Arc};