Merge branch 'main' into finally-fix-terminal-line-height
This commit is contained in:
commit
f54ab73b47
50 changed files with 3598 additions and 2148 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1192,7 +1192,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.8.3"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
|
@ -1341,14 +1341,17 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"async-tar",
|
"async-tar",
|
||||||
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
|
"fs",
|
||||||
"futures 0.3.25",
|
"futures 0.3.25",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
"node_runtime",
|
"node_runtime",
|
||||||
|
"rpc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"settings",
|
"settings",
|
||||||
|
@ -1831,6 +1834,7 @@ dependencies = [
|
||||||
"editor",
|
"editor",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
|
"lsp",
|
||||||
"postage",
|
"postage",
|
||||||
"project",
|
"project",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -4687,6 +4691,7 @@ dependencies = [
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"copilot",
|
||||||
"ctor",
|
"ctor",
|
||||||
"db",
|
"db",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -8517,7 +8522,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.83.0"
|
version = "0.84.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
@ -76,6 +76,7 @@ serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
|
||||||
rand = { version = "0.8" }
|
rand = { version = "0.8" }
|
||||||
postage = { version = "0.5", features = ["futures-traits"] }
|
postage = { version = "0.5", features = ["futures-traits"] }
|
||||||
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
|
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "c51896d32dcc11a38e41f36e3deb1a6a9c4f4b14" }
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,325 +1,325 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"context": "Editor && VimControl && !VimWaiting",
|
"context": "Editor && VimControl && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"g": [
|
"g": [
|
||||||
"vim::PushOperator",
|
"vim::PushOperator",
|
||||||
{
|
{
|
||||||
"Namespace": "G"
|
"Namespace": "G"
|
||||||
}
|
|
||||||
],
|
|
||||||
"i": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"Object": {
|
|
||||||
"around": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"a": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"Object": {
|
|
||||||
"around": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"h": "vim::Left",
|
|
||||||
"backspace": "vim::Backspace",
|
|
||||||
"j": "vim::Down",
|
|
||||||
"enter": "vim::NextLineStart",
|
|
||||||
"k": "vim::Up",
|
|
||||||
"l": "vim::Right",
|
|
||||||
"$": "vim::EndOfLine",
|
|
||||||
"shift-g": "vim::EndOfDocument",
|
|
||||||
"w": "vim::NextWordStart",
|
|
||||||
"shift-w": [
|
|
||||||
"vim::NextWordStart",
|
|
||||||
{
|
|
||||||
"ignorePunctuation": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"e": "vim::NextWordEnd",
|
|
||||||
"shift-e": [
|
|
||||||
"vim::NextWordEnd",
|
|
||||||
{
|
|
||||||
"ignorePunctuation": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"b": "vim::PreviousWordStart",
|
|
||||||
"shift-b": [
|
|
||||||
"vim::PreviousWordStart",
|
|
||||||
{
|
|
||||||
"ignorePunctuation": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"%": "vim::Matching",
|
|
||||||
"ctrl-y": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"LineUp"
|
|
||||||
],
|
|
||||||
"f": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"FindForward": {
|
|
||||||
"before": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"t": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"FindForward": {
|
|
||||||
"before": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shift-f": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"FindBackward": {
|
|
||||||
"after": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shift-t": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"FindBackward": {
|
|
||||||
"after": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"escape": "editor::Cancel",
|
|
||||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
|
||||||
"1": [
|
|
||||||
"vim::Number",
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"2": [
|
|
||||||
"vim::Number",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"3": [
|
|
||||||
"vim::Number",
|
|
||||||
3
|
|
||||||
],
|
|
||||||
"4": [
|
|
||||||
"vim::Number",
|
|
||||||
4
|
|
||||||
],
|
|
||||||
"5": [
|
|
||||||
"vim::Number",
|
|
||||||
5
|
|
||||||
],
|
|
||||||
"6": [
|
|
||||||
"vim::Number",
|
|
||||||
6
|
|
||||||
],
|
|
||||||
"7": [
|
|
||||||
"vim::Number",
|
|
||||||
7
|
|
||||||
],
|
|
||||||
"8": [
|
|
||||||
"vim::Number",
|
|
||||||
8
|
|
||||||
],
|
|
||||||
"9": [
|
|
||||||
"vim::Number",
|
|
||||||
9
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"i": [
|
||||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
"vim::PushOperator",
|
||||||
"bindings": {
|
{
|
||||||
"c": [
|
"Object": {
|
||||||
"vim::PushOperator",
|
"around": false
|
||||||
"Change"
|
}
|
||||||
],
|
|
||||||
"shift-c": "vim::ChangeToEndOfLine",
|
|
||||||
"d": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
"Delete"
|
|
||||||
],
|
|
||||||
"shift-d": "vim::DeleteToEndOfLine",
|
|
||||||
"y": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
"Yank"
|
|
||||||
],
|
|
||||||
"z": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"Namespace": "Z"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"i": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Insert"
|
|
||||||
],
|
|
||||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
|
||||||
"a": "vim::InsertAfter",
|
|
||||||
"shift-a": "vim::InsertEndOfLine",
|
|
||||||
"x": "vim::DeleteRight",
|
|
||||||
"shift-x": "vim::DeleteLeft",
|
|
||||||
"^": "vim::FirstNonWhitespace",
|
|
||||||
"o": "vim::InsertLineBelow",
|
|
||||||
"shift-o": "vim::InsertLineAbove",
|
|
||||||
"v": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
{
|
|
||||||
"Visual": {
|
|
||||||
"line": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"shift-v": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
{
|
|
||||||
"Visual": {
|
|
||||||
"line": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"p": "vim::Paste",
|
|
||||||
"u": "editor::Undo",
|
|
||||||
"ctrl-r": "editor::Redo",
|
|
||||||
"ctrl-o": "pane::GoBack",
|
|
||||||
"/": [
|
|
||||||
"buffer_search::Deploy",
|
|
||||||
{
|
|
||||||
"focus": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ctrl-f": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"PageDown"
|
|
||||||
],
|
|
||||||
"ctrl-b": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"PageUp"
|
|
||||||
],
|
|
||||||
"ctrl-d": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"HalfPageDown"
|
|
||||||
],
|
|
||||||
"ctrl-u": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"HalfPageUp"
|
|
||||||
],
|
|
||||||
"ctrl-e": [
|
|
||||||
"vim::Scroll",
|
|
||||||
"LineDown"
|
|
||||||
],
|
|
||||||
"r": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
"Replace"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"a": [
|
||||||
"context": "Editor && vim_operator == n",
|
"vim::PushOperator",
|
||||||
"bindings": {
|
{
|
||||||
"0": [
|
"Object": {
|
||||||
"vim::Number",
|
"around": true
|
||||||
0
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"h": "vim::Left",
|
||||||
"context": "Editor && vim_operator == g",
|
"backspace": "vim::Backspace",
|
||||||
"bindings": {
|
"j": "vim::Down",
|
||||||
"g": "vim::StartOfDocument",
|
"enter": "vim::NextLineStart",
|
||||||
"h": "editor::Hover",
|
"k": "vim::Up",
|
||||||
"escape": [
|
"l": "vim::Right",
|
||||||
"vim::SwitchMode",
|
"$": "vim::EndOfLine",
|
||||||
"Normal"
|
"shift-g": "vim::EndOfDocument",
|
||||||
],
|
"w": "vim::NextWordStart",
|
||||||
"d": "editor::GoToDefinition"
|
"shift-w": [
|
||||||
|
"vim::NextWordStart",
|
||||||
|
{
|
||||||
|
"ignorePunctuation": true
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"e": "vim::NextWordEnd",
|
||||||
"context": "Editor && vim_operator == c",
|
"shift-e": [
|
||||||
"bindings": {
|
"vim::NextWordEnd",
|
||||||
"c": "vim::CurrentLine"
|
{
|
||||||
|
"ignorePunctuation": true
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"b": "vim::PreviousWordStart",
|
||||||
"context": "Editor && vim_operator == d",
|
"shift-b": [
|
||||||
"bindings": {
|
"vim::PreviousWordStart",
|
||||||
"d": "vim::CurrentLine"
|
{
|
||||||
|
"ignorePunctuation": true
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"%": "vim::Matching",
|
||||||
"context": "Editor && vim_operator == y",
|
"ctrl-y": [
|
||||||
"bindings": {
|
"vim::Scroll",
|
||||||
"y": "vim::CurrentLine"
|
"LineUp"
|
||||||
|
],
|
||||||
|
"f": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
{
|
||||||
|
"FindForward": {
|
||||||
|
"before": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"t": [
|
||||||
"context": "Editor && vim_operator == z",
|
"vim::PushOperator",
|
||||||
"bindings": {
|
{
|
||||||
"t": "editor::ScrollCursorTop",
|
"FindForward": {
|
||||||
"z": "editor::ScrollCursorCenter",
|
"before": true
|
||||||
"b": "editor::ScrollCursorBottom",
|
}
|
||||||
"escape": [
|
|
||||||
"vim::SwitchMode",
|
|
||||||
"Normal"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"shift-f": [
|
||||||
"context": "Editor && VimObject",
|
"vim::PushOperator",
|
||||||
"bindings": {
|
{
|
||||||
"w": "vim::Word",
|
"FindBackward": {
|
||||||
"shift-w": [
|
"after": false
|
||||||
"vim::Word",
|
}
|
||||||
{
|
|
||||||
"ignorePunctuation": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"s": "vim::Sentence",
|
|
||||||
"'": "vim::Quotes",
|
|
||||||
"`": "vim::BackQuotes",
|
|
||||||
"\"": "vim::DoubleQuotes",
|
|
||||||
"(": "vim::Parentheses",
|
|
||||||
")": "vim::Parentheses",
|
|
||||||
"[": "vim::SquareBrackets",
|
|
||||||
"]": "vim::SquareBrackets",
|
|
||||||
"{": "vim::CurlyBrackets",
|
|
||||||
"}": "vim::CurlyBrackets",
|
|
||||||
"<": "vim::AngleBrackets",
|
|
||||||
">": "vim::AngleBrackets"
|
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
"shift-t": [
|
||||||
"context": "Editor && vim_mode == visual && !VimWaiting",
|
"vim::PushOperator",
|
||||||
"bindings": {
|
{
|
||||||
"u": "editor::Undo",
|
"FindBackward": {
|
||||||
"c": "vim::VisualChange",
|
"after": true
|
||||||
"d": "vim::VisualDelete",
|
}
|
||||||
"x": "vim::VisualDelete",
|
|
||||||
"y": "vim::VisualYank",
|
|
||||||
"p": "vim::VisualPaste",
|
|
||||||
"r": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
"Replace"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && vim_mode == insert",
|
|
||||||
"bindings": {
|
|
||||||
"escape": "vim::NormalBefore",
|
|
||||||
"ctrl-c": "vim::NormalBefore"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Editor && VimWaiting",
|
|
||||||
"bindings": {
|
|
||||||
"tab": "vim::Tab",
|
|
||||||
"enter": "vim::Enter",
|
|
||||||
"escape": "editor::Cancel"
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"escape": "editor::Cancel",
|
||||||
|
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||||
|
"1": [
|
||||||
|
"vim::Number",
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"vim::Number",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
"vim::Number",
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
"vim::Number",
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
"vim::Number",
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"6": [
|
||||||
|
"vim::Number",
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
"vim::Number",
|
||||||
|
7
|
||||||
|
],
|
||||||
|
"8": [
|
||||||
|
"vim::Number",
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"9": [
|
||||||
|
"vim::Number",
|
||||||
|
9
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||||
|
"bindings": {
|
||||||
|
"c": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Change"
|
||||||
|
],
|
||||||
|
"shift-c": "vim::ChangeToEndOfLine",
|
||||||
|
"d": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Delete"
|
||||||
|
],
|
||||||
|
"shift-d": "vim::DeleteToEndOfLine",
|
||||||
|
"y": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Yank"
|
||||||
|
],
|
||||||
|
"z": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
{
|
||||||
|
"Namespace": "Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"i": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
"Insert"
|
||||||
|
],
|
||||||
|
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||||
|
"a": "vim::InsertAfter",
|
||||||
|
"shift-a": "vim::InsertEndOfLine",
|
||||||
|
"x": "vim::DeleteRight",
|
||||||
|
"shift-x": "vim::DeleteLeft",
|
||||||
|
"^": "vim::FirstNonWhitespace",
|
||||||
|
"o": "vim::InsertLineBelow",
|
||||||
|
"shift-o": "vim::InsertLineAbove",
|
||||||
|
"v": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
{
|
||||||
|
"Visual": {
|
||||||
|
"line": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shift-v": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
{
|
||||||
|
"Visual": {
|
||||||
|
"line": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"p": "vim::Paste",
|
||||||
|
"u": "editor::Undo",
|
||||||
|
"ctrl-r": "editor::Redo",
|
||||||
|
"ctrl-o": "pane::GoBack",
|
||||||
|
"/": [
|
||||||
|
"buffer_search::Deploy",
|
||||||
|
{
|
||||||
|
"focus": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ctrl-f": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"PageDown"
|
||||||
|
],
|
||||||
|
"ctrl-b": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"PageUp"
|
||||||
|
],
|
||||||
|
"ctrl-d": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"HalfPageDown"
|
||||||
|
],
|
||||||
|
"ctrl-u": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"HalfPageUp"
|
||||||
|
],
|
||||||
|
"ctrl-e": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"LineDown"
|
||||||
|
],
|
||||||
|
"r": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Replace"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == n",
|
||||||
|
"bindings": {
|
||||||
|
"0": [
|
||||||
|
"vim::Number",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == g",
|
||||||
|
"bindings": {
|
||||||
|
"g": "vim::StartOfDocument",
|
||||||
|
"h": "editor::Hover",
|
||||||
|
"escape": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
"Normal"
|
||||||
|
],
|
||||||
|
"d": "editor::GoToDefinition"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == c",
|
||||||
|
"bindings": {
|
||||||
|
"c": "vim::CurrentLine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == d",
|
||||||
|
"bindings": {
|
||||||
|
"d": "vim::CurrentLine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == y",
|
||||||
|
"bindings": {
|
||||||
|
"y": "vim::CurrentLine"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == z",
|
||||||
|
"bindings": {
|
||||||
|
"t": "editor::ScrollCursorTop",
|
||||||
|
"z": "editor::ScrollCursorCenter",
|
||||||
|
"b": "editor::ScrollCursorBottom",
|
||||||
|
"escape": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
"Normal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && VimObject",
|
||||||
|
"bindings": {
|
||||||
|
"w": "vim::Word",
|
||||||
|
"shift-w": [
|
||||||
|
"vim::Word",
|
||||||
|
{
|
||||||
|
"ignorePunctuation": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"s": "vim::Sentence",
|
||||||
|
"'": "vim::Quotes",
|
||||||
|
"`": "vim::BackQuotes",
|
||||||
|
"\"": "vim::DoubleQuotes",
|
||||||
|
"(": "vim::Parentheses",
|
||||||
|
")": "vim::Parentheses",
|
||||||
|
"[": "vim::SquareBrackets",
|
||||||
|
"]": "vim::SquareBrackets",
|
||||||
|
"{": "vim::CurlyBrackets",
|
||||||
|
"}": "vim::CurlyBrackets",
|
||||||
|
"<": "vim::AngleBrackets",
|
||||||
|
">": "vim::AngleBrackets"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_mode == visual && !VimWaiting",
|
||||||
|
"bindings": {
|
||||||
|
"u": "editor::Undo",
|
||||||
|
"c": "vim::VisualChange",
|
||||||
|
"d": "vim::VisualDelete",
|
||||||
|
"x": "vim::VisualDelete",
|
||||||
|
"y": "vim::VisualYank",
|
||||||
|
"p": "vim::VisualPaste",
|
||||||
|
"r": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Replace"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_mode == insert",
|
||||||
|
"bindings": {
|
||||||
|
"escape": "vim::NormalBefore",
|
||||||
|
"ctrl-c": "vim::NormalBefore"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && VimWaiting",
|
||||||
|
"bindings": {
|
||||||
|
"tab": "vim::Tab",
|
||||||
|
"enter": "vim::Enter",
|
||||||
|
"escape": "editor::Cancel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
@ -1,264 +1,267 @@
|
||||||
{
|
{
|
||||||
// The name of the Zed theme to use for the UI
|
// The name of the Zed theme to use for the UI
|
||||||
"theme": "One Dark",
|
"theme": "One Dark",
|
||||||
// Features that can be globally enabled or disabled
|
// Features that can be globally enabled or disabled
|
||||||
"features": {
|
"features": {
|
||||||
// Show Copilot icon in status bar
|
// Show Copilot icon in status bar
|
||||||
"copilot": true
|
"copilot": true
|
||||||
},
|
},
|
||||||
// The name of a font to use for rendering text in the editor
|
// The name of a font to use for rendering text in the editor
|
||||||
"buffer_font_family": "Zed Mono",
|
"buffer_font_family": "Zed Mono",
|
||||||
// The OpenType features to enable for text in the editor.
|
// The OpenType features to enable for text in the editor.
|
||||||
"buffer_font_features": {
|
"buffer_font_features": {
|
||||||
// Disable ligatures:
|
// Disable ligatures:
|
||||||
// "calt": false
|
// "calt": false
|
||||||
},
|
},
|
||||||
// The default font size for text in the editor
|
// The default font size for text in the editor
|
||||||
"buffer_font_size": 15,
|
"buffer_font_size": 15,
|
||||||
// The factor to grow the active pane by. Defaults to 1.0
|
// The factor to grow the active pane by. Defaults to 1.0
|
||||||
// which gives the same size as all other panes.
|
// which gives the same size as all other panes.
|
||||||
"active_pane_magnification": 1.0,
|
"active_pane_magnification": 1.0,
|
||||||
// Whether to enable vim modes and key bindings
|
// Whether to enable vim modes and key bindings
|
||||||
"vim_mode": false,
|
"vim_mode": false,
|
||||||
// Whether to show the informational hover box when moving the mouse
|
// Whether to show the informational hover box when moving the mouse
|
||||||
// over symbols in the editor.
|
// over symbols in the editor.
|
||||||
"hover_popover_enabled": true,
|
"hover_popover_enabled": true,
|
||||||
// Whether to confirm before quitting Zed.
|
// Whether to confirm before quitting Zed.
|
||||||
"confirm_quit": false,
|
"confirm_quit": false,
|
||||||
// Whether the cursor blinks in the editor.
|
// Whether the cursor blinks in the editor.
|
||||||
"cursor_blink": true,
|
"cursor_blink": true,
|
||||||
// Whether to pop the completions menu while typing in an editor without
|
// Whether to pop the completions menu while typing in an editor without
|
||||||
// explicitly requesting it.
|
// explicitly requesting it.
|
||||||
"show_completions_on_input": true,
|
"show_completions_on_input": true,
|
||||||
// Controls whether copilot provides suggestion immediately
|
// Controls whether copilot provides suggestion immediately
|
||||||
// or waits for a `copilot::Toggle`
|
// or waits for a `copilot::Toggle`
|
||||||
"show_copilot_suggestions": true,
|
"show_copilot_suggestions": true,
|
||||||
// Whether the screen sharing icon is shown in the os status bar.
|
// Whether the screen sharing icon is shown in the os status bar.
|
||||||
"show_call_status_icon": true,
|
"show_call_status_icon": true,
|
||||||
// Whether to use language servers to provide code intelligence.
|
// Whether to use language servers to provide code intelligence.
|
||||||
"enable_language_server": true,
|
"enable_language_server": true,
|
||||||
// When to automatically save edited buffers. This setting can
|
// When to automatically save edited buffers. This setting can
|
||||||
// take four values.
|
// take four values.
|
||||||
//
|
//
|
||||||
// 1. Never automatically save:
|
// 1. Never automatically save:
|
||||||
// "autosave": "off",
|
// "autosave": "off",
|
||||||
// 2. Save when changing focus away from the Zed window:
|
// 2. Save when changing focus away from the Zed window:
|
||||||
// "autosave": "on_window_change",
|
// "autosave": "on_window_change",
|
||||||
// 3. Save when changing focus away from a specific buffer:
|
// 3. Save when changing focus away from a specific buffer:
|
||||||
// "autosave": "on_focus_change",
|
// "autosave": "on_focus_change",
|
||||||
// 4. Save when idle for a certain amount of time:
|
// 4. Save when idle for a certain amount of time:
|
||||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||||
"autosave": "off",
|
"autosave": "off",
|
||||||
// Where to place the dock by default. This setting can take three
|
// Where to place the dock by default. This setting can take three
|
||||||
// values:
|
// values:
|
||||||
//
|
//
|
||||||
// 1. Position the dock attached to the bottom of the workspace
|
// 1. Position the dock attached to the bottom of the workspace
|
||||||
// "default_dock_anchor": "bottom"
|
// "default_dock_anchor": "bottom"
|
||||||
// 2. Position the dock to the right of the workspace like a side panel
|
// 2. Position the dock to the right of the workspace like a side panel
|
||||||
// "default_dock_anchor": "right"
|
// "default_dock_anchor": "right"
|
||||||
// 3. Position the dock full screen over the entire workspace"
|
// 3. Position the dock full screen over the entire workspace"
|
||||||
// "default_dock_anchor": "expanded"
|
// "default_dock_anchor": "expanded"
|
||||||
"default_dock_anchor": "bottom",
|
"default_dock_anchor": "bottom",
|
||||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||||
// before saving it.
|
// before saving it.
|
||||||
"remove_trailing_whitespace_on_save": true,
|
"remove_trailing_whitespace_on_save": true,
|
||||||
// Whether or not to ensure there's a single newline at the end of a buffer
|
// Whether or not to ensure there's a single newline at the end of a buffer
|
||||||
// when saving it.
|
// when saving it.
|
||||||
"ensure_final_newline_on_save": true,
|
"ensure_final_newline_on_save": true,
|
||||||
// Whether or not to perform a buffer format before saving
|
// Whether or not to perform a buffer format before saving
|
||||||
"format_on_save": "on",
|
"format_on_save": "on",
|
||||||
// How to perform a buffer format. This setting can take two values:
|
// How to perform a buffer format. This setting can take two values:
|
||||||
//
|
//
|
||||||
// 1. Format code using the current language server:
|
// 1. Format code using the current language server:
|
||||||
// "format_on_save": "language_server"
|
// "format_on_save": "language_server"
|
||||||
// 2. Format code using an external command:
|
// 2. Format code using an external command:
|
||||||
// "format_on_save": {
|
// "format_on_save": {
|
||||||
// "external": {
|
// "external": {
|
||||||
// "command": "prettier",
|
// "command": "prettier",
|
||||||
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
// "arguments": ["--stdin-filepath", "{buffer_path}"]
|
||||||
// }
|
// }
|
||||||
|
// }
|
||||||
|
"formatter": "language_server",
|
||||||
|
// How to soft-wrap long lines of text. This setting can take
|
||||||
|
// three values:
|
||||||
|
//
|
||||||
|
// 1. Do not soft wrap.
|
||||||
|
// "soft_wrap": "none",
|
||||||
|
// 2. Soft wrap lines that overflow the editor:
|
||||||
|
// "soft_wrap": "editor_width",
|
||||||
|
// 3. Soft wrap lines at the preferred line length
|
||||||
|
// "soft_wrap": "preferred_line_length",
|
||||||
|
"soft_wrap": "none",
|
||||||
|
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
||||||
|
// is enabled.
|
||||||
|
"preferred_line_length": 80,
|
||||||
|
// Whether to indent lines using tab characters, as opposed to multiple
|
||||||
|
// spaces.
|
||||||
|
"hard_tabs": false,
|
||||||
|
// How many columns a tab should occupy.
|
||||||
|
"tab_size": 4,
|
||||||
|
// Control what info is collected by Zed.
|
||||||
|
"telemetry": {
|
||||||
|
// Send debug info like crash reports.
|
||||||
|
"diagnostics": true,
|
||||||
|
// Send anonymized usage data like what languages you're using Zed with.
|
||||||
|
"metrics": true
|
||||||
|
},
|
||||||
|
// Automatically update Zed
|
||||||
|
"auto_update": true,
|
||||||
|
// Git gutter behavior configuration.
|
||||||
|
"git": {
|
||||||
|
// Control whether the git gutter is shown. May take 2 values:
|
||||||
|
// 1. Show the gutter
|
||||||
|
// "git_gutter": "tracked_files"
|
||||||
|
// 2. Hide the gutter
|
||||||
|
// "git_gutter": "hide"
|
||||||
|
"git_gutter": "tracked_files"
|
||||||
|
},
|
||||||
|
// Settings specific to journaling
|
||||||
|
"journal": {
|
||||||
|
// The path of the directory where journal entries are stored
|
||||||
|
"path": "~",
|
||||||
|
// What format to display the hours in
|
||||||
|
// May take 2 values:
|
||||||
|
// 1. hour12
|
||||||
|
// 2. hour24
|
||||||
|
"hour_format": "hour12"
|
||||||
|
},
|
||||||
|
// Settings specific to the terminal
|
||||||
|
"terminal": {
|
||||||
|
// What shell to use when opening a terminal. May take 3 values:
|
||||||
|
// 1. Use the system's default terminal configuration in /etc/passwd
|
||||||
|
// "shell": "system"
|
||||||
|
// 2. A program:
|
||||||
|
// "shell": {
|
||||||
|
// "program": "sh"
|
||||||
|
// }
|
||||||
|
// 3. A program with arguments:
|
||||||
|
// "shell": {
|
||||||
|
// "with_arguments": {
|
||||||
|
// "program": "/bin/bash",
|
||||||
|
// "arguments": ["--login"]
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
"formatter": "language_server",
|
"shell": "system",
|
||||||
// How to soft-wrap long lines of text. This setting can take
|
// What working directory to use when launching the terminal.
|
||||||
// three values:
|
// May take 4 values:
|
||||||
|
// 1. Use the current file's project directory. Will Fallback to the
|
||||||
|
// first project directory strategy if unsuccessful
|
||||||
|
// "working_directory": "current_project_directory"
|
||||||
|
// 2. Use the first project in this workspace's directory
|
||||||
|
// "working_directory": "first_project_directory"
|
||||||
|
// 3. Always use this platform's home directory (if we can find it)
|
||||||
|
// "working_directory": "always_home"
|
||||||
|
// 4. Always use a specific directory. This value will be shell expanded.
|
||||||
|
// If this path is not a valid directory the terminal will default to
|
||||||
|
// this platform's home directory (if we can find it)
|
||||||
|
// "working_directory": {
|
||||||
|
// "always": {
|
||||||
|
// "directory": "~/zed/projects/"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// 1. Do not soft wrap.
|
//
|
||||||
// "soft_wrap": "none",
|
"working_directory": "current_project_directory",
|
||||||
// 2. Soft wrap lines that overflow the editor:
|
// Set the cursor blinking behavior in the terminal.
|
||||||
// "soft_wrap": "editor_width",
|
// May take 4 values:
|
||||||
// 3. Soft wrap lines at the preferred line length
|
// 1. Never blink the cursor, ignoring the terminal mode
|
||||||
// "soft_wrap": "preferred_line_length",
|
// "blinking": "off",
|
||||||
"soft_wrap": "none",
|
// 2. Default the cursor blink to off, but allow the terminal to
|
||||||
// The column at which to soft-wrap lines, for buffers where soft-wrap
|
// set blinking
|
||||||
// is enabled.
|
// "blinking": "terminal_controlled",
|
||||||
"preferred_line_length": 80,
|
// 3. Always blink the cursor, ignoring the terminal mode
|
||||||
// Whether to indent lines using tab characters, as opposed to multiple
|
// "blinking": "on",
|
||||||
// spaces.
|
"blinking": "terminal_controlled",
|
||||||
"hard_tabs": false,
|
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||||
// How many columns a tab should occupy.
|
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||||
"tab_size": 4,
|
// presses when in the alternate screen (e.g. when running applications
|
||||||
// Control what info is collected by Zed.
|
// like vim or less). The terminal can still set and unset this mode.
|
||||||
"telemetry": {
|
// May take 2 values:
|
||||||
// Send debug info like crash reports.
|
// 1. Default alternate scroll mode to on
|
||||||
"diagnostics": true,
|
// "alternate_scroll": "on",
|
||||||
// Send anonymized usage data like what languages you're using Zed with.
|
// 2. Default alternate scroll mode to off
|
||||||
"metrics": true
|
// "alternate_scroll": "off",
|
||||||
|
"alternate_scroll": "off",
|
||||||
|
// Set whether the option key behaves as the meta key.
|
||||||
|
// May take 2 values:
|
||||||
|
// 1. Rely on default platform handling of option key, on macOS
|
||||||
|
// this means generating certain unicode characters
|
||||||
|
// "option_to_meta": false,
|
||||||
|
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
||||||
|
// "option_to_meta": true,
|
||||||
|
"option_as_meta": false,
|
||||||
|
// Whether or not selecting text in the terminal will automatically
|
||||||
|
// copy to the system clipboard.
|
||||||
|
"copy_on_select": false,
|
||||||
|
// Any key-value pairs added to this list will be added to the terminal's
|
||||||
|
// enviroment. Use `:` to seperate multiple values.
|
||||||
|
"env": {
|
||||||
|
// "KEY": "value1:value2"
|
||||||
},
|
},
|
||||||
// Automatically update Zed
|
// Set the terminal's line height.
|
||||||
"auto_update": true,
|
// May take 3 values:
|
||||||
// Git gutter behavior configuration.
|
// 1. Use a line height that's comfortable for reading, 1.618
|
||||||
"git": {
|
// "line_height": "comfortable"
|
||||||
// Control whether the git gutter is shown. May take 2 values:
|
// 2. Use a standard line height, 1.3. This option is useful for TUIs,
|
||||||
// 1. Show the gutter
|
// particularly if they use box characters
|
||||||
// "git_gutter": "tracked_files"
|
// "line_height": "standard",
|
||||||
// 2. Hide the gutter
|
// 3. Use a custom line height.
|
||||||
// "git_gutter": "hide"
|
// "line_height": 1.2,
|
||||||
"git_gutter": "tracked_files"
|
"line_height": "comfortable"
|
||||||
|
// Set the terminal's font size. If this option is not included,
|
||||||
|
// the terminal will default to matching the buffer's font size.
|
||||||
|
// "font_size": "15"
|
||||||
|
// Set the terminal's font family. If this option is not included,
|
||||||
|
// the terminal will default to matching the buffer's font family.
|
||||||
|
// "font_family": "Zed Mono"
|
||||||
|
},
|
||||||
|
// Different settings for specific languages.
|
||||||
|
"languages": {
|
||||||
|
"Plain Text": {
|
||||||
|
"soft_wrap": "preferred_line_length"
|
||||||
},
|
},
|
||||||
// Settings specific to journaling
|
"Elixir": {
|
||||||
"journal": {
|
"tab_size": 2
|
||||||
// The path of the directory where journal entries are stored
|
|
||||||
"path": "~",
|
|
||||||
// What format to display the hours in
|
|
||||||
// May take 2 values:
|
|
||||||
// 1. hour12
|
|
||||||
// 2. hour24
|
|
||||||
"hour_format": "hour12"
|
|
||||||
},
|
},
|
||||||
// Settings specific to the terminal
|
"Go": {
|
||||||
"terminal": {
|
"tab_size": 4,
|
||||||
// What shell to use when opening a terminal. May take 3 values:
|
"hard_tabs": true
|
||||||
// 1. Use the system's default terminal configuration in /etc/passwd
|
|
||||||
// "shell": "system"
|
|
||||||
// 2. A program:
|
|
||||||
// "shell": {
|
|
||||||
// "program": "sh"
|
|
||||||
// }
|
|
||||||
// 3. A program with arguments:
|
|
||||||
// "shell": {
|
|
||||||
// "with_arguments": {
|
|
||||||
// "program": "/bin/bash",
|
|
||||||
// "arguments": ["--login"]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
"shell": "system",
|
|
||||||
// What working directory to use when launching the terminal.
|
|
||||||
// May take 4 values:
|
|
||||||
// 1. Use the current file's project directory. Will Fallback to the
|
|
||||||
// first project directory strategy if unsuccessful
|
|
||||||
// "working_directory": "current_project_directory"
|
|
||||||
// 2. Use the first project in this workspace's directory
|
|
||||||
// "working_directory": "first_project_directory"
|
|
||||||
// 3. Always use this platform's home directory (if we can find it)
|
|
||||||
// "working_directory": "always_home"
|
|
||||||
// 4. Always use a specific directory. This value will be shell expanded.
|
|
||||||
// If this path is not a valid directory the terminal will default to
|
|
||||||
// this platform's home directory (if we can find it)
|
|
||||||
// "working_directory": {
|
|
||||||
// "always": {
|
|
||||||
// "directory": "~/zed/projects/"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
"working_directory": "current_project_directory",
|
|
||||||
// Set the cursor blinking behavior in the terminal.
|
|
||||||
// May take 4 values:
|
|
||||||
// 1. Never blink the cursor, ignoring the terminal mode
|
|
||||||
// "blinking": "off",
|
|
||||||
// 2. Default the cursor blink to off, but allow the terminal to
|
|
||||||
// set blinking
|
|
||||||
// "blinking": "terminal_controlled",
|
|
||||||
// 3. Always blink the cursor, ignoring the terminal mode
|
|
||||||
// "blinking": "on",
|
|
||||||
"blinking": "terminal_controlled",
|
|
||||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
|
||||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
|
||||||
// presses when in the alternate screen (e.g. when running applications
|
|
||||||
// like vim or less). The terminal can still set and unset this mode.
|
|
||||||
// May take 2 values:
|
|
||||||
// 1. Default alternate scroll mode to on
|
|
||||||
// "alternate_scroll": "on",
|
|
||||||
// 2. Default alternate scroll mode to off
|
|
||||||
// "alternate_scroll": "off",
|
|
||||||
"alternate_scroll": "off",
|
|
||||||
// Set whether the option key behaves as the meta key.
|
|
||||||
// May take 2 values:
|
|
||||||
// 1. Rely on default platform handling of option key, on macOS
|
|
||||||
// this means generating certain unicode characters
|
|
||||||
// "option_to_meta": false,
|
|
||||||
// 2. Make the option keys behave as a 'meta' key, e.g. for emacs
|
|
||||||
// "option_to_meta": true,
|
|
||||||
"option_as_meta": false,
|
|
||||||
// Whether or not selecting text in the terminal will automatically
|
|
||||||
// copy to the system clipboard.
|
|
||||||
"copy_on_select": false,
|
|
||||||
// Any key-value pairs added to this list will be added to the terminal's
|
|
||||||
// enviroment. Use `:` to seperate multiple values.
|
|
||||||
"env": {
|
|
||||||
// "KEY": "value1:value2"
|
|
||||||
},
|
|
||||||
// Set the terminal's line height.
|
|
||||||
// May take 3 values:
|
|
||||||
// 1. Use a line height that's comfortable for reading, 1.618
|
|
||||||
// "line_height": "comfortable"
|
|
||||||
// 2. Use a standard line height, 1.3. This option is useful for TUIs,
|
|
||||||
// particularly if they use box characters
|
|
||||||
// "line_height": "standard",
|
|
||||||
// 3. Use a custom line height.
|
|
||||||
// "line_height": 1.2,
|
|
||||||
"line_height": "comfortable"
|
|
||||||
// Set the terminal's font size. If this option is not included,
|
|
||||||
// the terminal will default to matching the buffer's font size.
|
|
||||||
// "font_size": "15"
|
|
||||||
// Set the terminal's font family. If this option is not included,
|
|
||||||
// the terminal will default to matching the buffer's font family.
|
|
||||||
// "font_family": "Zed Mono"
|
|
||||||
},
|
},
|
||||||
// Different settings for specific languages.
|
"Markdown": {
|
||||||
"languages": {
|
"soft_wrap": "preferred_line_length"
|
||||||
"Plain Text": {
|
|
||||||
"soft_wrap": "preferred_line_length"
|
|
||||||
},
|
|
||||||
"Elixir": {
|
|
||||||
"tab_size": 2
|
|
||||||
},
|
|
||||||
"Go": {
|
|
||||||
"tab_size": 4,
|
|
||||||
"hard_tabs": true
|
|
||||||
},
|
|
||||||
"Markdown": {
|
|
||||||
"soft_wrap": "preferred_line_length"
|
|
||||||
},
|
|
||||||
"JavaScript": {
|
|
||||||
"tab_size": 2
|
|
||||||
},
|
|
||||||
"TypeScript": {
|
|
||||||
"tab_size": 2
|
|
||||||
},
|
|
||||||
"TSX": {
|
|
||||||
"tab_size": 2
|
|
||||||
},
|
|
||||||
"YAML": {
|
|
||||||
"tab_size": 2
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
// LSP Specific settings.
|
"JavaScript": {
|
||||||
"lsp": {
|
"tab_size": 2
|
||||||
// Specify the LSP name as a key here.
|
},
|
||||||
// As of 8/10/22, supported LSPs are:
|
"TypeScript": {
|
||||||
// pyright
|
"tab_size": 2
|
||||||
// gopls
|
},
|
||||||
// rust-analyzer
|
"TSX": {
|
||||||
// typescript-language-server
|
"tab_size": 2
|
||||||
// vscode-json-languageserver
|
},
|
||||||
// "rust-analyzer": {
|
"YAML": {
|
||||||
// //These initialization options are merged into Zed's defaults
|
"tab_size": 2
|
||||||
// "initialization_options": {
|
},
|
||||||
// "checkOnSave": {
|
"JSON": {
|
||||||
// "command": "clippy"
|
"tab_size": 2
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// LSP Specific settings.
|
||||||
|
"lsp": {
|
||||||
|
// Specify the LSP name as a key here.
|
||||||
|
// As of 8/10/22, supported LSPs are:
|
||||||
|
// pyright
|
||||||
|
// gopls
|
||||||
|
// rust-analyzer
|
||||||
|
// typescript-language-server
|
||||||
|
// vscode-json-languageserver
|
||||||
|
// "rust-analyzer": {
|
||||||
|
// //These initialization options are merged into Zed's defaults
|
||||||
|
// "initialization_options": {
|
||||||
|
// "checkOnSave": {
|
||||||
|
// "command": "clippy"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,5 @@
|
||||||
// custom settings, run the `open default settings` command
|
// custom settings, run the `open default settings` command
|
||||||
// from the command palette or from `Zed` application menu.
|
// from the command palette or from `Zed` application menu.
|
||||||
{
|
{
|
||||||
"buffer_font_size": 15
|
"buffer_font_size": 15
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,4 +18,4 @@ settings = { path = "../settings" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -9,4 +9,4 @@ path = "src/clock.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
||||||
default-run = "collab"
|
default-run = "collab"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.8.3"
|
version = "0.9.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -22,6 +22,7 @@ use language::{
|
||||||
LanguageConfig, OffsetRangeExt, Point, Rope,
|
LanguageConfig, OffsetRangeExt, Point, Rope,
|
||||||
};
|
};
|
||||||
use live_kit_client::MacOSDisplay;
|
use live_kit_client::MacOSDisplay;
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath};
|
use project::{search::SearchQuery, DiagnosticSummary, Project, ProjectPath};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -3477,6 +3478,7 @@ async fn test_collaborating_with_diagnostics(
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("a.rs")),
|
path: Arc::from(Path::new("a.rs")),
|
||||||
},
|
},
|
||||||
|
LanguageServerId(0),
|
||||||
DiagnosticSummary {
|
DiagnosticSummary {
|
||||||
error_count: 1,
|
error_count: 1,
|
||||||
warning_count: 0,
|
warning_count: 0,
|
||||||
|
@ -3512,6 +3514,7 @@ async fn test_collaborating_with_diagnostics(
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("a.rs")),
|
path: Arc::from(Path::new("a.rs")),
|
||||||
},
|
},
|
||||||
|
LanguageServerId(0),
|
||||||
DiagnosticSummary {
|
DiagnosticSummary {
|
||||||
error_count: 1,
|
error_count: 1,
|
||||||
warning_count: 0,
|
warning_count: 0,
|
||||||
|
@ -3552,10 +3555,10 @@ async fn test_collaborating_with_diagnostics(
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("a.rs")),
|
path: Arc::from(Path::new("a.rs")),
|
||||||
},
|
},
|
||||||
|
LanguageServerId(0),
|
||||||
DiagnosticSummary {
|
DiagnosticSummary {
|
||||||
error_count: 1,
|
error_count: 1,
|
||||||
warning_count: 1,
|
warning_count: 1,
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
|
@ -3568,10 +3571,10 @@ async fn test_collaborating_with_diagnostics(
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("a.rs")),
|
path: Arc::from(Path::new("a.rs")),
|
||||||
},
|
},
|
||||||
|
LanguageServerId(0),
|
||||||
DiagnosticSummary {
|
DiagnosticSummary {
|
||||||
error_count: 1,
|
error_count: 1,
|
||||||
warning_count: 1,
|
warning_count: 1,
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
);
|
);
|
||||||
|
|
|
@ -13,4 +13,4 @@ gpui = { path = "../gpui" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
theme = { path = "../theme" }
|
theme = { path = "../theme" }
|
||||||
smallvec = "1.6"
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -38,10 +38,13 @@ smol = "1.2.5"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections", features = ["test-support"] }
|
collections = { path = "../collections", features = ["test-support"] }
|
||||||
|
fs = { path = "../fs", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
settings = { path = "../settings", features = ["test-support"] }
|
|
||||||
lsp = { path = "../lsp", features = ["test-support"] }
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
|
rpc = { path = "../rpc", features = ["test-support"] }
|
||||||
|
settings = { path = "../settings", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
|
|
|
@ -5,24 +5,29 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use async_compression::futures::bufread::GzipDecoder;
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
use async_tar::Archive;
|
use async_tar::Archive;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use futures::{future::Shared, Future, FutureExt, TryFutureExt};
|
use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
|
||||||
use gpui::{actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{
|
||||||
use language::{point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, Language, ToPointUtf16};
|
actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
|
||||||
|
};
|
||||||
|
use language::{
|
||||||
|
point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
|
||||||
|
ToPointUtf16,
|
||||||
|
};
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
use lsp::LanguageServer;
|
use lsp::{LanguageServer, LanguageServerId};
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use request::{LogMessage, StatusNotification};
|
use request::{LogMessage, StatusNotification};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::{fs, io::BufReader, stream::StreamExt};
|
use smol::{fs, io::BufReader, stream::StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
|
mem,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::{
|
use util::{
|
||||||
channel::ReleaseChannel, fs::remove_matching, github::latest_github_release, http::HttpClient,
|
fs::remove_matching, github::latest_github_release, http::HttpClient, paths, ResultExt,
|
||||||
paths, ResultExt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
|
const COPILOT_AUTH_NAMESPACE: &'static str = "copilot_auth";
|
||||||
|
@ -35,15 +40,6 @@ actions!(
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut AppContext) {
|
pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut AppContext) {
|
||||||
// Disable Copilot for stable releases.
|
|
||||||
if *cx.global::<ReleaseChannel>() == ReleaseChannel::Stable {
|
|
||||||
cx.update_global::<collections::CommandPaletteFilter, _, _>(|filter, _cx| {
|
|
||||||
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
|
|
||||||
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let copilot = cx.add_model({
|
let copilot = cx.add_model({
|
||||||
let node_runtime = node_runtime.clone();
|
let node_runtime = node_runtime.clone();
|
||||||
move |cx| Copilot::start(http, node_runtime, cx)
|
move |cx| Copilot::start(http, node_runtime, cx)
|
||||||
|
@ -98,15 +94,38 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
|
||||||
|
|
||||||
enum CopilotServer {
|
enum CopilotServer {
|
||||||
Disabled,
|
Disabled,
|
||||||
Starting {
|
Starting { task: Shared<Task<()>> },
|
||||||
task: Shared<Task<()>>,
|
|
||||||
},
|
|
||||||
Error(Arc<str>),
|
Error(Arc<str>),
|
||||||
Started {
|
Running(RunningCopilotServer),
|
||||||
server: Arc<LanguageServer>,
|
}
|
||||||
status: SignInStatus,
|
|
||||||
subscriptions_by_buffer_id: HashMap<usize, gpui::Subscription>,
|
impl CopilotServer {
|
||||||
},
|
fn as_authenticated(&mut self) -> Result<&mut RunningCopilotServer> {
|
||||||
|
let server = self.as_running()?;
|
||||||
|
if matches!(server.sign_in_status, SignInStatus::Authorized { .. }) {
|
||||||
|
Ok(server)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("must sign in before using copilot"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_running(&mut self) -> Result<&mut RunningCopilotServer> {
|
||||||
|
match self {
|
||||||
|
CopilotServer::Starting { .. } => Err(anyhow!("copilot is still starting")),
|
||||||
|
CopilotServer::Disabled => Err(anyhow!("copilot is disabled")),
|
||||||
|
CopilotServer::Error(error) => Err(anyhow!(
|
||||||
|
"copilot was not started because of an error: {}",
|
||||||
|
error
|
||||||
|
)),
|
||||||
|
CopilotServer::Running(server) => Ok(server),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RunningCopilotServer {
|
||||||
|
lsp: Arc<LanguageServer>,
|
||||||
|
sign_in_status: SignInStatus,
|
||||||
|
registered_buffers: HashMap<usize, RegisteredBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -141,8 +160,104 @@ impl Status {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
struct RegisteredBuffer {
|
||||||
|
id: usize,
|
||||||
|
uri: lsp::Url,
|
||||||
|
language_id: String,
|
||||||
|
snapshot: BufferSnapshot,
|
||||||
|
snapshot_version: i32,
|
||||||
|
_subscriptions: [gpui::Subscription; 2],
|
||||||
|
pending_buffer_change: Task<Option<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisteredBuffer {
|
||||||
|
fn report_changes(
|
||||||
|
&mut self,
|
||||||
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
cx: &mut ModelContext<Copilot>,
|
||||||
|
) -> oneshot::Receiver<(i32, BufferSnapshot)> {
|
||||||
|
let id = self.id;
|
||||||
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
|
|
||||||
|
if buffer.read(cx).version() == self.snapshot.version {
|
||||||
|
let _ = done_tx.send((self.snapshot_version, self.snapshot.clone()));
|
||||||
|
} else {
|
||||||
|
let buffer = buffer.downgrade();
|
||||||
|
let prev_pending_change =
|
||||||
|
mem::replace(&mut self.pending_buffer_change, Task::ready(None));
|
||||||
|
self.pending_buffer_change = cx.spawn_weak(|copilot, mut cx| async move {
|
||||||
|
prev_pending_change.await;
|
||||||
|
|
||||||
|
let old_version = copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
|
||||||
|
let server = copilot.server.as_authenticated().log_err()?;
|
||||||
|
let buffer = server.registered_buffers.get_mut(&id)?;
|
||||||
|
Some(buffer.snapshot.version.clone())
|
||||||
|
})?;
|
||||||
|
let new_snapshot = buffer
|
||||||
|
.upgrade(&cx)?
|
||||||
|
.read_with(&cx, |buffer, _| buffer.snapshot());
|
||||||
|
|
||||||
|
let content_changes = cx
|
||||||
|
.background()
|
||||||
|
.spawn({
|
||||||
|
let new_snapshot = new_snapshot.clone();
|
||||||
|
async move {
|
||||||
|
new_snapshot
|
||||||
|
.edits_since::<(PointUtf16, usize)>(&old_version)
|
||||||
|
.map(|edit| {
|
||||||
|
let edit_start = edit.new.start.0;
|
||||||
|
let edit_end = edit_start + (edit.old.end.0 - edit.old.start.0);
|
||||||
|
let new_text = new_snapshot
|
||||||
|
.text_for_range(edit.new.start.1..edit.new.end.1)
|
||||||
|
.collect();
|
||||||
|
lsp::TextDocumentContentChangeEvent {
|
||||||
|
range: Some(lsp::Range::new(
|
||||||
|
point_to_lsp(edit_start),
|
||||||
|
point_to_lsp(edit_end),
|
||||||
|
)),
|
||||||
|
range_length: None,
|
||||||
|
text: new_text,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
copilot.upgrade(&cx)?.update(&mut cx, |copilot, _| {
|
||||||
|
let server = copilot.server.as_authenticated().log_err()?;
|
||||||
|
let buffer = server.registered_buffers.get_mut(&id)?;
|
||||||
|
if !content_changes.is_empty() {
|
||||||
|
buffer.snapshot_version += 1;
|
||||||
|
buffer.snapshot = new_snapshot;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidChangeTextDocument>(
|
||||||
|
lsp::DidChangeTextDocumentParams {
|
||||||
|
text_document: lsp::VersionedTextDocumentIdentifier::new(
|
||||||
|
buffer.uri.clone(),
|
||||||
|
buffer.snapshot_version,
|
||||||
|
),
|
||||||
|
content_changes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
let _ = done_tx.send((buffer.snapshot_version, buffer.snapshot.clone()));
|
||||||
|
Some(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
done_rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Completion {
|
pub struct Completion {
|
||||||
|
uuid: String,
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
@ -151,6 +266,7 @@ pub struct Copilot {
|
||||||
http: Arc<dyn HttpClient>,
|
http: Arc<dyn HttpClient>,
|
||||||
node_runtime: Arc<NodeRuntime>,
|
node_runtime: Arc<NodeRuntime>,
|
||||||
server: CopilotServer,
|
server: CopilotServer,
|
||||||
|
buffers: HashMap<usize, WeakModelHandle<Buffer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Copilot {
|
impl Entity for Copilot {
|
||||||
|
@ -212,12 +328,14 @@ impl Copilot {
|
||||||
http,
|
http,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Starting { task: start_task },
|
server: CopilotServer::Starting { task: start_task },
|
||||||
|
buffers: Default::default(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
http,
|
http,
|
||||||
node_runtime,
|
node_runtime,
|
||||||
server: CopilotServer::Disabled,
|
server: CopilotServer::Disabled,
|
||||||
|
buffers: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,11 +348,12 @@ impl Copilot {
|
||||||
let this = cx.add_model(|cx| Self {
|
let this = cx.add_model(|cx| Self {
|
||||||
http: http.clone(),
|
http: http.clone(),
|
||||||
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
node_runtime: NodeRuntime::new(http, cx.background().clone()),
|
||||||
server: CopilotServer::Started {
|
server: CopilotServer::Running(RunningCopilotServer {
|
||||||
server: Arc::new(server),
|
lsp: Arc::new(server),
|
||||||
status: SignInStatus::Authorized,
|
sign_in_status: SignInStatus::Authorized,
|
||||||
subscriptions_by_buffer_id: Default::default(),
|
registered_buffers: Default::default(),
|
||||||
},
|
}),
|
||||||
|
buffers: Default::default(),
|
||||||
});
|
});
|
||||||
(this, fake_server)
|
(this, fake_server)
|
||||||
}
|
}
|
||||||
|
@ -251,7 +370,7 @@ impl Copilot {
|
||||||
let node_path = node_runtime.binary_path().await?;
|
let node_path = node_runtime.binary_path().await?;
|
||||||
let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
|
let arguments: &[OsString] = &[server_path.into(), "--stdio".into()];
|
||||||
let server = LanguageServer::new(
|
let server = LanguageServer::new(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
&node_path,
|
&node_path,
|
||||||
arguments,
|
arguments,
|
||||||
Path::new("/"),
|
Path::new("/"),
|
||||||
|
@ -286,6 +405,19 @@ impl Copilot {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
server
|
||||||
|
.request::<request::SetEditorInfo>(request::SetEditorInfoParams {
|
||||||
|
editor_info: request::EditorInfo {
|
||||||
|
name: "zed".into(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").into(),
|
||||||
|
},
|
||||||
|
editor_plugin_info: request::EditorPluginInfo {
|
||||||
|
name: "zed-copilot".into(),
|
||||||
|
version: "0.0.1".into(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
anyhow::Ok((server, status))
|
anyhow::Ok((server, status))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -294,11 +426,11 @@ impl Copilot {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
match server {
|
match server {
|
||||||
Ok((server, status)) => {
|
Ok((server, status)) => {
|
||||||
this.server = CopilotServer::Started {
|
this.server = CopilotServer::Running(RunningCopilotServer {
|
||||||
server,
|
lsp: server,
|
||||||
status: SignInStatus::SignedOut,
|
sign_in_status: SignInStatus::SignedOut,
|
||||||
subscriptions_by_buffer_id: Default::default(),
|
registered_buffers: Default::default(),
|
||||||
};
|
});
|
||||||
this.update_sign_in_status(status, cx);
|
this.update_sign_in_status(status, cx);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -311,8 +443,8 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
if let CopilotServer::Started { server, status, .. } = &mut self.server {
|
if let CopilotServer::Running(server) = &mut self.server {
|
||||||
let task = match status {
|
let task = match &server.sign_in_status {
|
||||||
SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
|
SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
|
||||||
Task::ready(Ok(())).shared()
|
Task::ready(Ok(())).shared()
|
||||||
}
|
}
|
||||||
|
@ -321,11 +453,11 @@ impl Copilot {
|
||||||
task.clone()
|
task.clone()
|
||||||
}
|
}
|
||||||
SignInStatus::SignedOut => {
|
SignInStatus::SignedOut => {
|
||||||
let server = server.clone();
|
let lsp = server.lsp.clone();
|
||||||
let task = cx
|
let task = cx
|
||||||
.spawn(|this, mut cx| async move {
|
.spawn(|this, mut cx| async move {
|
||||||
let sign_in = async {
|
let sign_in = async {
|
||||||
let sign_in = server
|
let sign_in = lsp
|
||||||
.request::<request::SignInInitiate>(
|
.request::<request::SignInInitiate>(
|
||||||
request::SignInInitiateParams {},
|
request::SignInInitiateParams {},
|
||||||
)
|
)
|
||||||
|
@ -336,8 +468,10 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
|
request::SignInInitiateResult::PromptUserDeviceFlow(flow) => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
if let CopilotServer::Started { status, .. } =
|
if let CopilotServer::Running(RunningCopilotServer {
|
||||||
&mut this.server
|
sign_in_status: status,
|
||||||
|
..
|
||||||
|
}) = &mut this.server
|
||||||
{
|
{
|
||||||
if let SignInStatus::SigningIn {
|
if let SignInStatus::SigningIn {
|
||||||
prompt: prompt_flow,
|
prompt: prompt_flow,
|
||||||
|
@ -349,7 +483,7 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let response = server
|
let response = lsp
|
||||||
.request::<request::SignInConfirm>(
|
.request::<request::SignInConfirm>(
|
||||||
request::SignInConfirmParams {
|
request::SignInConfirmParams {
|
||||||
user_code: flow.user_code,
|
user_code: flow.user_code,
|
||||||
|
@ -377,7 +511,7 @@ impl Copilot {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.shared();
|
.shared();
|
||||||
*status = SignInStatus::SigningIn {
|
server.sign_in_status = SignInStatus::SigningIn {
|
||||||
prompt: None,
|
prompt: None,
|
||||||
task: task.clone(),
|
task: task.clone(),
|
||||||
};
|
};
|
||||||
|
@ -396,10 +530,8 @@ impl Copilot {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
fn sign_out(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||||
if let CopilotServer::Started { server, status, .. } = &mut self.server {
|
self.update_sign_in_status(request::SignInStatus::NotSignedIn, cx);
|
||||||
*status = SignInStatus::SignedOut;
|
if let CopilotServer::Running(RunningCopilotServer { lsp: server, .. }) = &self.server {
|
||||||
cx.notify();
|
|
||||||
|
|
||||||
let server = server.clone();
|
let server = server.clone();
|
||||||
cx.background().spawn(async move {
|
cx.background().spawn(async move {
|
||||||
server
|
server
|
||||||
|
@ -433,6 +565,135 @@ impl Copilot {
|
||||||
cx.foreground().spawn(start_task)
|
cx.foreground().spawn(start_task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn register_buffer(&mut self, buffer: &ModelHandle<Buffer>, cx: &mut ModelContext<Self>) {
|
||||||
|
let buffer_id = buffer.id();
|
||||||
|
self.buffers.insert(buffer_id, buffer.downgrade());
|
||||||
|
|
||||||
|
if let CopilotServer::Running(RunningCopilotServer {
|
||||||
|
lsp: server,
|
||||||
|
sign_in_status: status,
|
||||||
|
registered_buffers,
|
||||||
|
..
|
||||||
|
}) = &mut self.server
|
||||||
|
{
|
||||||
|
if !matches!(status, SignInStatus::Authorized { .. }) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
registered_buffers.entry(buffer.id()).or_insert_with(|| {
|
||||||
|
let uri: lsp::Url = uri_for_buffer(buffer, cx);
|
||||||
|
let language_id = id_for_language(buffer.read(cx).language());
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
server
|
||||||
|
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem {
|
||||||
|
uri: uri.clone(),
|
||||||
|
language_id: language_id.clone(),
|
||||||
|
version: 0,
|
||||||
|
text: snapshot.text(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
|
||||||
|
RegisteredBuffer {
|
||||||
|
id: buffer_id,
|
||||||
|
uri,
|
||||||
|
language_id,
|
||||||
|
snapshot,
|
||||||
|
snapshot_version: 0,
|
||||||
|
pending_buffer_change: Task::ready(Some(())),
|
||||||
|
_subscriptions: [
|
||||||
|
cx.subscribe(buffer, |this, buffer, event, cx| {
|
||||||
|
this.handle_buffer_event(buffer, event, cx).log_err();
|
||||||
|
}),
|
||||||
|
cx.observe_release(buffer, move |this, _buffer, _cx| {
|
||||||
|
this.buffers.remove(&buffer_id);
|
||||||
|
this.unregister_buffer(buffer_id);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_buffer_event(
|
||||||
|
&mut self,
|
||||||
|
buffer: ModelHandle<Buffer>,
|
||||||
|
event: &language::Event,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
if let Some(registered_buffer) = server.registered_buffers.get_mut(&buffer.id()) {
|
||||||
|
match event {
|
||||||
|
language::Event::Edited => {
|
||||||
|
let _ = registered_buffer.report_changes(&buffer, cx);
|
||||||
|
}
|
||||||
|
language::Event::Saved => {
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidSaveTextDocument>(
|
||||||
|
lsp::DidSaveTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(
|
||||||
|
registered_buffer.uri.clone(),
|
||||||
|
),
|
||||||
|
text: None,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
language::Event::FileHandleChanged | language::Event::LanguageChanged => {
|
||||||
|
let new_language_id = id_for_language(buffer.read(cx).language());
|
||||||
|
let new_uri = uri_for_buffer(&buffer, cx);
|
||||||
|
if new_uri != registered_buffer.uri
|
||||||
|
|| new_language_id != registered_buffer.language_id
|
||||||
|
{
|
||||||
|
let old_uri = mem::replace(&mut registered_buffer.uri, new_uri);
|
||||||
|
registered_buffer.language_id = new_language_id;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(old_uri),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidOpenTextDocument>(
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
registered_buffer.uri.clone(),
|
||||||
|
registered_buffer.language_id.clone(),
|
||||||
|
registered_buffer.snapshot_version,
|
||||||
|
registered_buffer.snapshot.text(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unregister_buffer(&mut self, buffer_id: usize) {
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
if let Some(buffer) = server.registered_buffers.remove(&buffer_id) {
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.notify::<lsp::notification::DidCloseTextDocument>(
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer.uri),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn completions<T>(
|
pub fn completions<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
@ -457,6 +718,51 @@ impl Copilot {
|
||||||
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
|
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn accept_completion(
|
||||||
|
&mut self,
|
||||||
|
completion: &Completion,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let server = match self.server.as_authenticated() {
|
||||||
|
Ok(server) => server,
|
||||||
|
Err(error) => return Task::ready(Err(error)),
|
||||||
|
};
|
||||||
|
let request =
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
|
||||||
|
uuid: completion.uuid.clone(),
|
||||||
|
});
|
||||||
|
cx.background().spawn(async move {
|
||||||
|
request.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discard_completions(
|
||||||
|
&mut self,
|
||||||
|
completions: &[Completion],
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) -> Task<Result<()>> {
|
||||||
|
let server = match self.server.as_authenticated() {
|
||||||
|
Ok(server) => server,
|
||||||
|
Err(error) => return Task::ready(Err(error)),
|
||||||
|
};
|
||||||
|
let request =
|
||||||
|
server
|
||||||
|
.lsp
|
||||||
|
.request::<request::NotifyRejected>(request::NotifyRejectedParams {
|
||||||
|
uuids: completions
|
||||||
|
.iter()
|
||||||
|
.map(|completion| completion.uuid.clone())
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
cx.background().spawn(async move {
|
||||||
|
request.await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn request_completions<R, T>(
|
fn request_completions<R, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
@ -464,116 +770,48 @@ impl Copilot {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Task<Result<Vec<Completion>>>
|
) -> Task<Result<Vec<Completion>>>
|
||||||
where
|
where
|
||||||
R: lsp::request::Request<
|
R: 'static
|
||||||
Params = request::GetCompletionsParams,
|
+ lsp::request::Request<
|
||||||
Result = request::GetCompletionsResult,
|
Params = request::GetCompletionsParams,
|
||||||
>,
|
Result = request::GetCompletionsResult,
|
||||||
|
>,
|
||||||
T: ToPointUtf16,
|
T: ToPointUtf16,
|
||||||
{
|
{
|
||||||
let buffer_id = buffer.id();
|
self.register_buffer(buffer, cx);
|
||||||
let uri: lsp::Url = format!("buffer://{}", buffer_id).parse().unwrap();
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
|
||||||
let server = match &mut self.server {
|
|
||||||
CopilotServer::Starting { .. } => {
|
|
||||||
return Task::ready(Err(anyhow!("copilot is still starting")))
|
|
||||||
}
|
|
||||||
CopilotServer::Disabled => return Task::ready(Err(anyhow!("copilot is disabled"))),
|
|
||||||
CopilotServer::Error(error) => {
|
|
||||||
return Task::ready(Err(anyhow!(
|
|
||||||
"copilot was not started because of an error: {}",
|
|
||||||
error
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
CopilotServer::Started {
|
|
||||||
server,
|
|
||||||
status,
|
|
||||||
subscriptions_by_buffer_id,
|
|
||||||
} => {
|
|
||||||
if matches!(status, SignInStatus::Authorized { .. }) {
|
|
||||||
subscriptions_by_buffer_id
|
|
||||||
.entry(buffer_id)
|
|
||||||
.or_insert_with(|| {
|
|
||||||
server
|
|
||||||
.notify::<lsp::notification::DidOpenTextDocument>(
|
|
||||||
lsp::DidOpenTextDocumentParams {
|
|
||||||
text_document: lsp::TextDocumentItem {
|
|
||||||
uri: uri.clone(),
|
|
||||||
language_id: id_for_language(
|
|
||||||
buffer.read(cx).language(),
|
|
||||||
),
|
|
||||||
version: 0,
|
|
||||||
text: snapshot.text(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
|
|
||||||
let uri = uri.clone();
|
let server = match self.server.as_authenticated() {
|
||||||
cx.observe_release(buffer, move |this, _, _| {
|
Ok(server) => server,
|
||||||
if let CopilotServer::Started {
|
Err(error) => return Task::ready(Err(error)),
|
||||||
server,
|
|
||||||
subscriptions_by_buffer_id,
|
|
||||||
..
|
|
||||||
} = &mut this.server
|
|
||||||
{
|
|
||||||
server
|
|
||||||
.notify::<lsp::notification::DidCloseTextDocument>(
|
|
||||||
lsp::DidCloseTextDocumentParams {
|
|
||||||
text_document: lsp::TextDocumentIdentifier::new(
|
|
||||||
uri.clone(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.log_err();
|
|
||||||
subscriptions_by_buffer_id.remove(&buffer_id);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
server.clone()
|
|
||||||
} else {
|
|
||||||
return Task::ready(Err(anyhow!("must sign in before using copilot")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let lsp = server.lsp.clone();
|
||||||
|
let registered_buffer = server.registered_buffers.get_mut(&buffer.id()).unwrap();
|
||||||
|
let snapshot = registered_buffer.report_changes(buffer, cx);
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let uri = registered_buffer.uri.clone();
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let position = position.to_point_utf16(&snapshot);
|
let position = position.to_point_utf16(buffer);
|
||||||
let language = snapshot.language_at(position);
|
let language = buffer.language_at(position);
|
||||||
let language_name = language.map(|language| language.name());
|
let language_name = language.map(|language| language.name());
|
||||||
let language_name = language_name.as_deref();
|
let language_name = language_name.as_deref();
|
||||||
let tab_size = settings.tab_size(language_name);
|
let tab_size = settings.tab_size(language_name);
|
||||||
let hard_tabs = settings.hard_tabs(language_name);
|
let hard_tabs = settings.hard_tabs(language_name);
|
||||||
let language_id = id_for_language(language);
|
let relative_path = buffer
|
||||||
|
.file()
|
||||||
|
.map(|file| file.path().to_path_buf())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let path;
|
cx.foreground().spawn(async move {
|
||||||
let relative_path;
|
let (version, snapshot) = snapshot.await?;
|
||||||
if let Some(file) = snapshot.file() {
|
let result = lsp
|
||||||
if let Some(file) = file.as_local() {
|
|
||||||
path = file.abs_path(cx);
|
|
||||||
} else {
|
|
||||||
path = file.full_path(cx);
|
|
||||||
}
|
|
||||||
relative_path = file.path().to_path_buf();
|
|
||||||
} else {
|
|
||||||
path = PathBuf::new();
|
|
||||||
relative_path = PathBuf::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.background().spawn(async move {
|
|
||||||
let result = server
|
|
||||||
.request::<R>(request::GetCompletionsParams {
|
.request::<R>(request::GetCompletionsParams {
|
||||||
doc: request::GetCompletionsDocument {
|
doc: request::GetCompletionsDocument {
|
||||||
source: snapshot.text(),
|
uri,
|
||||||
tab_size: tab_size.into(),
|
tab_size: tab_size.into(),
|
||||||
indent_size: 1,
|
indent_size: 1,
|
||||||
insert_spaces: !hard_tabs,
|
insert_spaces: !hard_tabs,
|
||||||
uri,
|
|
||||||
path: path.to_string_lossy().into(),
|
|
||||||
relative_path: relative_path.to_string_lossy().into(),
|
relative_path: relative_path.to_string_lossy().into(),
|
||||||
language_id,
|
|
||||||
position: point_to_lsp(position),
|
position: point_to_lsp(position),
|
||||||
version: 0,
|
version: version.try_into().unwrap(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -586,6 +824,7 @@ impl Copilot {
|
||||||
let end =
|
let end =
|
||||||
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
|
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
|
||||||
Completion {
|
Completion {
|
||||||
|
uuid: completion.uuid,
|
||||||
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||||
text: completion.text,
|
text: completion.text,
|
||||||
}
|
}
|
||||||
|
@ -600,14 +839,16 @@ impl Copilot {
|
||||||
CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
|
CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
|
||||||
CopilotServer::Disabled => Status::Disabled,
|
CopilotServer::Disabled => Status::Disabled,
|
||||||
CopilotServer::Error(error) => Status::Error(error.clone()),
|
CopilotServer::Error(error) => Status::Error(error.clone()),
|
||||||
CopilotServer::Started { status, .. } => match status {
|
CopilotServer::Running(RunningCopilotServer { sign_in_status, .. }) => {
|
||||||
SignInStatus::Authorized { .. } => Status::Authorized,
|
match sign_in_status {
|
||||||
SignInStatus::Unauthorized { .. } => Status::Unauthorized,
|
SignInStatus::Authorized { .. } => Status::Authorized,
|
||||||
SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
|
SignInStatus::Unauthorized { .. } => Status::Unauthorized,
|
||||||
prompt: prompt.clone(),
|
SignInStatus::SigningIn { prompt, .. } => Status::SigningIn {
|
||||||
},
|
prompt: prompt.clone(),
|
||||||
SignInStatus::SignedOut => Status::SignedOut,
|
},
|
||||||
},
|
SignInStatus::SignedOut => Status::SignedOut,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,14 +857,34 @@ impl Copilot {
|
||||||
lsp_status: request::SignInStatus,
|
lsp_status: request::SignInStatus,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let CopilotServer::Started { status, .. } = &mut self.server {
|
self.buffers.retain(|_, buffer| buffer.is_upgradable(cx));
|
||||||
*status = match lsp_status {
|
|
||||||
|
if let Ok(server) = self.server.as_running() {
|
||||||
|
match lsp_status {
|
||||||
request::SignInStatus::Ok { .. }
|
request::SignInStatus::Ok { .. }
|
||||||
| request::SignInStatus::MaybeOk { .. }
|
| request::SignInStatus::MaybeOk { .. }
|
||||||
| request::SignInStatus::AlreadySignedIn { .. } => SignInStatus::Authorized,
|
| request::SignInStatus::AlreadySignedIn { .. } => {
|
||||||
request::SignInStatus::NotAuthorized { .. } => SignInStatus::Unauthorized,
|
server.sign_in_status = SignInStatus::Authorized;
|
||||||
request::SignInStatus::NotSignedIn => SignInStatus::SignedOut,
|
for buffer in self.buffers.values().cloned().collect::<Vec<_>>() {
|
||||||
};
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
self.register_buffer(&buffer, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request::SignInStatus::NotAuthorized { .. } => {
|
||||||
|
server.sign_in_status = SignInStatus::Unauthorized;
|
||||||
|
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||||
|
self.unregister_buffer(buffer_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request::SignInStatus::NotSignedIn => {
|
||||||
|
server.sign_in_status = SignInStatus::SignedOut;
|
||||||
|
for buffer_id in self.buffers.keys().copied().collect::<Vec<_>>() {
|
||||||
|
self.unregister_buffer(buffer_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -638,6 +899,14 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn uri_for_buffer(buffer: &ModelHandle<Buffer>, cx: &AppContext) -> lsp::Url {
|
||||||
|
if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
|
||||||
|
lsp::Url::from_file_path(file.abs_path(cx)).unwrap()
|
||||||
|
} else {
|
||||||
|
format!("buffer://{}", buffer.id()).parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn clear_copilot_dir() {
|
async fn clear_copilot_dir() {
|
||||||
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
remove_matching(&paths::COPILOT_DIR, |_| true).await
|
||||||
}
|
}
|
||||||
|
@ -709,3 +978,226 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gpui::{executor::Deterministic, TestAppContext};
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 10)]
|
||||||
|
async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||||
|
|
||||||
|
let buffer_1 = cx.add_model(|cx| Buffer::new(0, "Hello", cx));
|
||||||
|
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
|
||||||
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Hello".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let buffer_2 = cx.add_model(|cx| Buffer::new(0, "Goodbye", cx));
|
||||||
|
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
|
||||||
|
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_2_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Goodbye".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidChangeTextDocumentParams {
|
||||||
|
text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
|
||||||
|
content_changes: vec![lsp::TextDocumentContentChangeEvent {
|
||||||
|
range: Some(lsp::Range::new(
|
||||||
|
lsp::Position::new(0, 5),
|
||||||
|
lsp::Position::new(0, 5)
|
||||||
|
)),
|
||||||
|
range_length: None,
|
||||||
|
text: " world".into(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure updates to the file are reflected in the LSP.
|
||||||
|
buffer_1
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
buffer.file_updated(
|
||||||
|
Arc::new(File {
|
||||||
|
abs_path: "/root/child/buffer-1".into(),
|
||||||
|
path: Path::new("child/buffer-1").into(),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
1,
|
||||||
|
"Hello world".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure all previously-registered buffers are closed when signing out.
|
||||||
|
lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
|
||||||
|
Ok(request::SignOutResult {})
|
||||||
|
});
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure all previously-registered buffers are re-opened when signing in.
|
||||||
|
lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
|
||||||
|
Ok(request::SignInInitiateResult::AlreadySignedIn {
|
||||||
|
user: "user-1".into(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_2_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Goodbye".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidOpenTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentItem::new(
|
||||||
|
buffer_1_uri.clone(),
|
||||||
|
"plaintext".into(),
|
||||||
|
0,
|
||||||
|
"Hello world".into()
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dropping a buffer causes it to be closed on the LSP side as well.
|
||||||
|
cx.update(|_| drop(buffer_2));
|
||||||
|
assert_eq!(
|
||||||
|
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||||
|
.await,
|
||||||
|
lsp::DidCloseTextDocumentParams {
|
||||||
|
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
abs_path: PathBuf,
|
||||||
|
path: Arc<Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl language::File for File {
|
||||||
|
fn as_local(&self) -> Option<&dyn language::LocalFile> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mtime(&self) -> std::time::SystemTime {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &Arc<Path> {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_path(&self, _: &AppContext) -> PathBuf {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_deleted(&self) -> bool {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_proto(&self) -> rpc::proto::File {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl language::LocalFile for File {
|
||||||
|
fn abs_path(&self, _: &AppContext) -> PathBuf {
|
||||||
|
self.abs_path.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer_reloaded(
|
||||||
|
&self,
|
||||||
|
_: u64,
|
||||||
|
_: &clock::Global,
|
||||||
|
_: language::RopeFingerprint,
|
||||||
|
_: ::fs::LineEnding,
|
||||||
|
_: std::time::SystemTime,
|
||||||
|
_: &mut AppContext,
|
||||||
|
) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -99,14 +99,11 @@ pub struct GetCompletionsParams {
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GetCompletionsDocument {
|
pub struct GetCompletionsDocument {
|
||||||
pub source: String,
|
|
||||||
pub tab_size: u32,
|
pub tab_size: u32,
|
||||||
pub indent_size: u32,
|
pub indent_size: u32,
|
||||||
pub insert_spaces: bool,
|
pub insert_spaces: bool,
|
||||||
pub uri: lsp::Url,
|
pub uri: lsp::Url,
|
||||||
pub path: String,
|
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub language_id: String,
|
|
||||||
pub position: lsp::Position,
|
pub position: lsp::Position,
|
||||||
pub version: usize,
|
pub version: usize,
|
||||||
}
|
}
|
||||||
|
@ -169,3 +166,60 @@ impl lsp::notification::Notification for StatusNotification {
|
||||||
type Params = StatusNotificationParams;
|
type Params = StatusNotificationParams;
|
||||||
const METHOD: &'static str = "statusNotification";
|
const METHOD: &'static str = "statusNotification";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SetEditorInfo {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetEditorInfoParams {
|
||||||
|
pub editor_info: EditorInfo,
|
||||||
|
pub editor_plugin_info: EditorPluginInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for SetEditorInfo {
|
||||||
|
type Params = SetEditorInfoParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "setEditorInfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct EditorInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct EditorPluginInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NotifyAccepted {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NotifyAcceptedParams {
|
||||||
|
pub uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for NotifyAccepted {
|
||||||
|
type Params = NotifyAcceptedParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "notifyAccepted";
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NotifyRejected {}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NotifyRejectedParams {
|
||||||
|
pub uuids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl lsp::request::Request for NotifyRejected {
|
||||||
|
type Params = NotifyRejectedParams;
|
||||||
|
type Result = String;
|
||||||
|
const METHOD: &'static str = "notifyRejected";
|
||||||
|
}
|
||||||
|
|
|
@ -23,51 +23,51 @@ struct OpenGithub;
|
||||||
const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
|
const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
let copilot = Copilot::global(cx).unwrap();
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
|
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
|
||||||
|
cx.observe(&copilot, move |copilot, cx| {
|
||||||
|
let status = copilot.read(cx).status();
|
||||||
|
|
||||||
let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
|
match &status {
|
||||||
cx.observe(&copilot, move |copilot, cx| {
|
crate::Status::SigningIn { prompt } => {
|
||||||
let status = copilot.read(cx).status();
|
if let Some(code_verification_handle) = code_verification.as_mut() {
|
||||||
|
if cx.has_window(code_verification_handle.window_id()) {
|
||||||
match &status {
|
code_verification_handle.update(cx, |code_verification_view, cx| {
|
||||||
crate::Status::SigningIn { prompt } => {
|
code_verification_view.set_status(status, cx)
|
||||||
if let Some(code_verification_handle) = code_verification.as_mut() {
|
});
|
||||||
if cx.has_window(code_verification_handle.window_id()) {
|
cx.activate_window(code_verification_handle.window_id());
|
||||||
code_verification_handle.update(cx, |code_verification_view, cx| {
|
} else {
|
||||||
code_verification_view.set_status(status, cx)
|
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||||
});
|
}
|
||||||
cx.activate_window(code_verification_handle.window_id());
|
} else if let Some(_prompt) = prompt {
|
||||||
} else {
|
|
||||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
create_copilot_auth_window(cx, &status, &mut code_verification);
|
||||||
}
|
}
|
||||||
} else if let Some(_prompt) = prompt {
|
|
||||||
create_copilot_auth_window(cx, &status, &mut code_verification);
|
|
||||||
}
|
}
|
||||||
}
|
Status::Authorized | Status::Unauthorized => {
|
||||||
Status::Authorized | Status::Unauthorized => {
|
if let Some(code_verification) = code_verification.as_ref() {
|
||||||
if let Some(code_verification) = code_verification.as_ref() {
|
code_verification.update(cx, |code_verification, cx| {
|
||||||
code_verification.update(cx, |code_verification, cx| {
|
code_verification.set_status(status, cx)
|
||||||
code_verification.set_status(status, cx)
|
});
|
||||||
});
|
|
||||||
|
|
||||||
cx.platform().activate(true);
|
cx.platform().activate(true);
|
||||||
cx.activate_window(code_verification.window_id());
|
cx.activate_window(code_verification.window_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(code_verification) = code_verification.take() {
|
||||||
|
cx.remove_window(code_verification.window_id());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
})
|
||||||
if let Some(code_verification) = code_verification.take() {
|
.detach();
|
||||||
cx.remove_window(code_verification.window_id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
|code_verification: &mut CopilotCodeVerification, _: &ClickedConnect, _| {
|
|code_verification: &mut CopilotCodeVerification, _: &ClickedConnect, _| {
|
||||||
code_verification.connect_clicked = true;
|
code_verification.connect_clicked = true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_copilot_auth_window(
|
fn create_copilot_auth_window(
|
||||||
|
|
|
@ -10,10 +10,11 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
|
lsp = { path = "../lsp" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
|
@ -27,6 +28,7 @@ unindent = "0.1"
|
||||||
client = { path = "../client", features = ["test-support"] }
|
client = { path = "../client", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
language = { path = "../language", features = ["test-support"] }
|
language = { path = "../language", features = ["test-support"] }
|
||||||
|
lsp = { path = "../lsp", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
workspace = { path = "../workspace", features = ["test-support"] }
|
workspace = { path = "../workspace", features = ["test-support"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
pub mod items;
|
pub mod items;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::{BTreeMap, HashSet};
|
use collections::{BTreeSet, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
diagnostic_block_renderer,
|
diagnostic_block_renderer,
|
||||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
|
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
|
||||||
|
@ -18,6 +18,7 @@ use language::{
|
||||||
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
|
||||||
SelectionGoal,
|
SelectionGoal,
|
||||||
};
|
};
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -56,7 +57,7 @@ struct ProjectDiagnosticsEditor {
|
||||||
summary: DiagnosticSummary,
|
summary: DiagnosticSummary,
|
||||||
excerpts: ModelHandle<MultiBuffer>,
|
excerpts: ModelHandle<MultiBuffer>,
|
||||||
path_states: Vec<PathState>,
|
path_states: Vec<PathState>,
|
||||||
paths_to_update: BTreeMap<ProjectPath, usize>,
|
paths_to_update: BTreeSet<(ProjectPath, LanguageServerId)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PathState {
|
struct PathState {
|
||||||
|
@ -72,6 +73,7 @@ struct Jump {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DiagnosticGroupState {
|
struct DiagnosticGroupState {
|
||||||
|
language_server_id: LanguageServerId,
|
||||||
primary_diagnostic: DiagnosticEntry<language::Anchor>,
|
primary_diagnostic: DiagnosticEntry<language::Anchor>,
|
||||||
primary_excerpt_ix: usize,
|
primary_excerpt_ix: usize,
|
||||||
excerpts: Vec<ExcerptId>,
|
excerpts: Vec<ExcerptId>,
|
||||||
|
@ -116,7 +118,7 @@ impl View for ProjectDiagnosticsEditor {
|
||||||
}),
|
}),
|
||||||
"summary": self.summary,
|
"summary": self.summary,
|
||||||
"paths_to_update": self.paths_to_update.iter().map(|(path, server_id)|
|
"paths_to_update": self.paths_to_update.iter().map(|(path, server_id)|
|
||||||
(path.path.to_string_lossy(), server_id)
|
(path.path.to_string_lossy(), server_id.0)
|
||||||
).collect::<Vec<_>>(),
|
).collect::<Vec<_>>(),
|
||||||
"paths_states": self.path_states.iter().map(|state|
|
"paths_states": self.path_states.iter().map(|state|
|
||||||
json!({
|
json!({
|
||||||
|
@ -149,7 +151,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
path,
|
path,
|
||||||
} => {
|
} => {
|
||||||
this.paths_to_update
|
this.paths_to_update
|
||||||
.insert(path.clone(), *language_server_id);
|
.insert((path.clone(), *language_server_id));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
})
|
})
|
||||||
|
@ -168,7 +170,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
let project = project_handle.read(cx);
|
let project = project_handle.read(cx);
|
||||||
let paths_to_update = project
|
let paths_to_update = project
|
||||||
.diagnostic_summaries(cx)
|
.diagnostic_summaries(cx)
|
||||||
.map(|e| (e.0, e.1.language_server_id))
|
.map(|(path, server_id, _)| (path, server_id))
|
||||||
.collect();
|
.collect();
|
||||||
let summary = project.diagnostic_summary(cx);
|
let summary = project.diagnostic_summary(cx);
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
@ -196,9 +198,13 @@ impl ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_excerpts(&mut self, language_server_id: Option<usize>, cx: &mut ViewContext<Self>) {
|
fn update_excerpts(
|
||||||
|
&mut self,
|
||||||
|
language_server_id: Option<LanguageServerId>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
self.paths_to_update.retain(|path, server_id| {
|
self.paths_to_update.retain(|(path, server_id)| {
|
||||||
if language_server_id
|
if language_server_id
|
||||||
.map_or(true, |language_server_id| language_server_id == *server_id)
|
.map_or(true, |language_server_id| language_server_id == *server_id)
|
||||||
{
|
{
|
||||||
|
@ -215,7 +221,9 @@ impl ProjectDiagnosticsEditor {
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
|
.update(&mut cx, |project, cx| project.open_buffer(path.clone(), cx))
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, cx| this.populate_excerpts(path, buffer, cx))
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.populate_excerpts(path, language_server_id, buffer, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Result::<_, anyhow::Error>::Ok(())
|
Result::<_, anyhow::Error>::Ok(())
|
||||||
}
|
}
|
||||||
|
@ -227,6 +235,7 @@ impl ProjectDiagnosticsEditor {
|
||||||
fn populate_excerpts(
|
fn populate_excerpts(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: ProjectPath,
|
path: ProjectPath,
|
||||||
|
language_server_id: Option<LanguageServerId>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
@ -265,9 +274,9 @@ impl ProjectDiagnosticsEditor {
|
||||||
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
|
let excerpts_snapshot = self.excerpts.update(cx, |excerpts, excerpts_cx| {
|
||||||
let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
|
let mut old_groups = path_state.diagnostic_groups.iter().enumerate().peekable();
|
||||||
let mut new_groups = snapshot
|
let mut new_groups = snapshot
|
||||||
.diagnostic_groups()
|
.diagnostic_groups(language_server_id)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|group| {
|
.filter(|(_, group)| {
|
||||||
group.entries[group.primary_ix].diagnostic.severity
|
group.entries[group.primary_ix].diagnostic.severity
|
||||||
<= DiagnosticSeverity::WARNING
|
<= DiagnosticSeverity::WARNING
|
||||||
})
|
})
|
||||||
|
@ -279,12 +288,27 @@ impl ProjectDiagnosticsEditor {
|
||||||
match (old_groups.peek(), new_groups.peek()) {
|
match (old_groups.peek(), new_groups.peek()) {
|
||||||
(None, None) => break,
|
(None, None) => break,
|
||||||
(None, Some(_)) => to_insert = new_groups.next(),
|
(None, Some(_)) => to_insert = new_groups.next(),
|
||||||
(Some(_), None) => to_remove = old_groups.next(),
|
(Some((_, old_group)), None) => {
|
||||||
(Some((_, old_group)), Some(new_group)) => {
|
if language_server_id.map_or(true, |id| id == old_group.language_server_id)
|
||||||
|
{
|
||||||
|
to_remove = old_groups.next();
|
||||||
|
} else {
|
||||||
|
to_keep = old_groups.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some((_, old_group)), Some((_, new_group))) => {
|
||||||
let old_primary = &old_group.primary_diagnostic;
|
let old_primary = &old_group.primary_diagnostic;
|
||||||
let new_primary = &new_group.entries[new_group.primary_ix];
|
let new_primary = &new_group.entries[new_group.primary_ix];
|
||||||
match compare_diagnostics(old_primary, new_primary, &snapshot) {
|
match compare_diagnostics(old_primary, new_primary, &snapshot) {
|
||||||
Ordering::Less => to_remove = old_groups.next(),
|
Ordering::Less => {
|
||||||
|
if language_server_id
|
||||||
|
.map_or(true, |id| id == old_group.language_server_id)
|
||||||
|
{
|
||||||
|
to_remove = old_groups.next();
|
||||||
|
} else {
|
||||||
|
to_keep = old_groups.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
to_keep = old_groups.next();
|
to_keep = old_groups.next();
|
||||||
new_groups.next();
|
new_groups.next();
|
||||||
|
@ -294,8 +318,9 @@ impl ProjectDiagnosticsEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(group) = to_insert {
|
if let Some((language_server_id, group)) = to_insert {
|
||||||
let mut group_state = DiagnosticGroupState {
|
let mut group_state = DiagnosticGroupState {
|
||||||
|
language_server_id,
|
||||||
primary_diagnostic: group.entries[group.primary_ix].clone(),
|
primary_diagnostic: group.entries[group.primary_ix].clone(),
|
||||||
primary_excerpt_ix: 0,
|
primary_excerpt_ix: 0,
|
||||||
excerpts: Default::default(),
|
excerpts: Default::default(),
|
||||||
|
@ -773,26 +798,24 @@ mod tests {
|
||||||
};
|
};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
|
||||||
|
use project::FakeFs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use unindent::Unindent as _;
|
use unindent::Unindent as _;
|
||||||
use workspace::AppState;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_diagnostics(cx: &mut TestAppContext) {
|
async fn test_diagnostics(cx: &mut TestAppContext) {
|
||||||
let app_state = cx.update(AppState::test);
|
Settings::test_async(cx);
|
||||||
app_state
|
let fs = FakeFs::new(cx.background());
|
||||||
.fs
|
fs.insert_tree(
|
||||||
.as_fake()
|
"/test",
|
||||||
.insert_tree(
|
json!({
|
||||||
"/test",
|
"consts.rs": "
|
||||||
json!({
|
|
||||||
"consts.rs": "
|
|
||||||
const a: i32 = 'a';
|
const a: i32 = 'a';
|
||||||
const b: i32 = c;
|
const b: i32 = c;
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
|
|
||||||
"main.rs": "
|
"main.rs": "
|
||||||
fn main() {
|
fn main() {
|
||||||
let x = vec![];
|
let x = vec![];
|
||||||
let y = vec![];
|
let y = vec![];
|
||||||
|
@ -804,19 +827,20 @@ mod tests {
|
||||||
d(x);
|
d(x);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
.unindent(),
|
.unindent(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
|
let language_server_id = LanguageServerId(0);
|
||||||
|
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
||||||
// Create some diagnostics
|
// Create some diagnostics
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.update_diagnostic_entries(
|
.update_diagnostic_entries(
|
||||||
0,
|
language_server_id,
|
||||||
PathBuf::from("/test/main.rs"),
|
PathBuf::from("/test/main.rs"),
|
||||||
None,
|
None,
|
||||||
vec![
|
vec![
|
||||||
|
@ -965,10 +989,10 @@ mod tests {
|
||||||
|
|
||||||
// Diagnostics are added for another earlier path.
|
// Diagnostics are added for another earlier path.
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.disk_based_diagnostics_started(0, cx);
|
project.disk_based_diagnostics_started(language_server_id, cx);
|
||||||
project
|
project
|
||||||
.update_diagnostic_entries(
|
.update_diagnostic_entries(
|
||||||
0,
|
language_server_id,
|
||||||
PathBuf::from("/test/consts.rs"),
|
PathBuf::from("/test/consts.rs"),
|
||||||
None,
|
None,
|
||||||
vec![DiagnosticEntry {
|
vec![DiagnosticEntry {
|
||||||
|
@ -985,7 +1009,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project.disk_based_diagnostics_finished(0, cx);
|
project.disk_based_diagnostics_finished(language_server_id, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
view.next_notification(cx).await;
|
view.next_notification(cx).await;
|
||||||
|
@ -1065,10 +1089,10 @@ mod tests {
|
||||||
|
|
||||||
// Diagnostics are added to the first path
|
// Diagnostics are added to the first path
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project.disk_based_diagnostics_started(0, cx);
|
project.disk_based_diagnostics_started(language_server_id, cx);
|
||||||
project
|
project
|
||||||
.update_diagnostic_entries(
|
.update_diagnostic_entries(
|
||||||
0,
|
language_server_id,
|
||||||
PathBuf::from("/test/consts.rs"),
|
PathBuf::from("/test/consts.rs"),
|
||||||
None,
|
None,
|
||||||
vec![
|
vec![
|
||||||
|
@ -1101,7 +1125,7 @@ mod tests {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project.disk_based_diagnostics_finished(0, cx);
|
project.disk_based_diagnostics_finished(language_server_id, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
view.next_notification(cx).await;
|
view.next_notification(cx).await;
|
||||||
|
@ -1181,6 +1205,272 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
|
||||||
|
Settings::test_async(cx);
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/test",
|
||||||
|
json!({
|
||||||
|
"main.js": "
|
||||||
|
a();
|
||||||
|
b();
|
||||||
|
c();
|
||||||
|
d();
|
||||||
|
e();
|
||||||
|
".unindent()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let server_id_1 = LanguageServerId(100);
|
||||||
|
let server_id_2 = LanguageServerId(101);
|
||||||
|
let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
|
||||||
|
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
|
|
||||||
|
let view = cx.add_view(&workspace, |cx| {
|
||||||
|
ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Two language servers start updating diagnostics
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.disk_based_diagnostics_started(server_id_1, cx);
|
||||||
|
project.disk_based_diagnostics_started(server_id_2, cx);
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
server_id_1,
|
||||||
|
PathBuf::from("/test/main.js"),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 1)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
message: "error 1".to_string(),
|
||||||
|
severity: DiagnosticSeverity::WARNING,
|
||||||
|
is_primary: true,
|
||||||
|
is_disk_based: true,
|
||||||
|
group_id: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
server_id_2,
|
||||||
|
PathBuf::from("/test/main.js"),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(1, 0))..Unclipped(PointUtf16::new(1, 1)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
message: "warning 1".to_string(),
|
||||||
|
severity: DiagnosticSeverity::ERROR,
|
||||||
|
is_primary: true,
|
||||||
|
is_disk_based: true,
|
||||||
|
group_id: 2,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// The first language server finishes
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.disk_based_diagnostics_finished(server_id_1, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only the first language server's diagnostics are shown.
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor_blocks(&view.editor, cx),
|
||||||
|
[
|
||||||
|
(0, "path header block".into()),
|
||||||
|
(2, "diagnostic header".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
concat!(
|
||||||
|
"\n", // filename
|
||||||
|
"\n", // padding
|
||||||
|
// diagnostic group 1
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"a();\n", //
|
||||||
|
"b();",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The second language server finishes
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.disk_based_diagnostics_finished(server_id_2, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Both language server's diagnostics are shown.
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor_blocks(&view.editor, cx),
|
||||||
|
[
|
||||||
|
(0, "path header block".into()),
|
||||||
|
(2, "diagnostic header".into()),
|
||||||
|
(6, "collapsed context".into()),
|
||||||
|
(7, "diagnostic header".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
concat!(
|
||||||
|
"\n", // filename
|
||||||
|
"\n", // padding
|
||||||
|
// diagnostic group 1
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"a();\n", // location
|
||||||
|
"b();\n", //
|
||||||
|
"\n", // collapsed context
|
||||||
|
// diagnostic group 2
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"a();\n", // context
|
||||||
|
"b();\n", //
|
||||||
|
"c();", // context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Both language servers start updating diagnostics, and the first server finishes.
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.disk_based_diagnostics_started(server_id_1, cx);
|
||||||
|
project.disk_based_diagnostics_started(server_id_2, cx);
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
server_id_1,
|
||||||
|
PathBuf::from("/test/main.js"),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(2, 0))..Unclipped(PointUtf16::new(2, 1)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
message: "warning 2".to_string(),
|
||||||
|
severity: DiagnosticSeverity::WARNING,
|
||||||
|
is_primary: true,
|
||||||
|
is_disk_based: true,
|
||||||
|
group_id: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
server_id_2,
|
||||||
|
PathBuf::from("/test/main.rs"),
|
||||||
|
None,
|
||||||
|
vec![],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
project.disk_based_diagnostics_finished(server_id_1, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only the first language server's diagnostics are updated.
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor_blocks(&view.editor, cx),
|
||||||
|
[
|
||||||
|
(0, "path header block".into()),
|
||||||
|
(2, "diagnostic header".into()),
|
||||||
|
(7, "collapsed context".into()),
|
||||||
|
(8, "diagnostic header".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
concat!(
|
||||||
|
"\n", // filename
|
||||||
|
"\n", // padding
|
||||||
|
// diagnostic group 1
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"a();\n", // location
|
||||||
|
"b();\n", //
|
||||||
|
"c();\n", // context
|
||||||
|
"\n", // collapsed context
|
||||||
|
// diagnostic group 2
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"b();\n", // context
|
||||||
|
"c();\n", //
|
||||||
|
"d();", // context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The second language server finishes.
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
server_id_2,
|
||||||
|
PathBuf::from("/test/main.js"),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(3, 0))..Unclipped(PointUtf16::new(3, 1)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
message: "warning 2".to_string(),
|
||||||
|
severity: DiagnosticSeverity::WARNING,
|
||||||
|
is_primary: true,
|
||||||
|
is_disk_based: true,
|
||||||
|
group_id: 1,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
project.disk_based_diagnostics_finished(server_id_2, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Both language servers' diagnostics are updated.
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
editor_blocks(&view.editor, cx),
|
||||||
|
[
|
||||||
|
(0, "path header block".into()),
|
||||||
|
(2, "diagnostic header".into()),
|
||||||
|
(7, "collapsed context".into()),
|
||||||
|
(8, "diagnostic header".into()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
view.editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||||
|
concat!(
|
||||||
|
"\n", // filename
|
||||||
|
"\n", // padding
|
||||||
|
// diagnostic group 1
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"b();\n", // location
|
||||||
|
"c();\n", //
|
||||||
|
"d();\n", // context
|
||||||
|
"\n", // collapsed context
|
||||||
|
// diagnostic group 2
|
||||||
|
"\n", // primary message
|
||||||
|
"\n", // padding
|
||||||
|
"c();\n", // context
|
||||||
|
"d();\n", //
|
||||||
|
"e();", // context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
|
fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
|
||||||
let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
|
let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
|
||||||
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
|
let mut cx = presenter.build_layout_context(Default::default(), false, cx);
|
||||||
|
|
|
@ -7,6 +7,7 @@ use gpui::{
|
||||||
ViewHandle, WeakViewHandle,
|
ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::Diagnostic;
|
use language::Diagnostic;
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use workspace::{item::ItemHandle, StatusItemView};
|
use workspace::{item::ItemHandle, StatusItemView};
|
||||||
|
@ -15,7 +16,7 @@ pub struct DiagnosticIndicator {
|
||||||
summary: project::DiagnosticSummary,
|
summary: project::DiagnosticSummary,
|
||||||
active_editor: Option<WeakViewHandle<Editor>>,
|
active_editor: Option<WeakViewHandle<Editor>>,
|
||||||
current_diagnostic: Option<Diagnostic>,
|
current_diagnostic: Option<Diagnostic>,
|
||||||
in_progress_checks: HashSet<usize>,
|
in_progress_checks: HashSet<LanguageServerId>,
|
||||||
_observe_active_editor: Option<Subscription>,
|
_observe_active_editor: Option<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ postage = { workspace = true }
|
||||||
rand = { version = "0.8.3", optional = true }
|
rand = { version = "0.8.3", optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
tree-sitter-rust = { version = "*", optional = true }
|
tree-sitter-rust = { version = "*", optional = true }
|
||||||
tree-sitter-html = { version = "*", optional = true }
|
tree-sitter-html = { version = "*", optional = true }
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub use language::{char_kind, CharKind};
|
||||||
use language::{
|
use language::{
|
||||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
||||||
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
|
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
|
||||||
Point, Rope, Selection, SelectionGoal, TransactionId,
|
Point, Selection, SelectionGoal, TransactionId,
|
||||||
};
|
};
|
||||||
use link_go_to_definition::{
|
use link_go_to_definition::{
|
||||||
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
||||||
|
@ -1037,6 +1037,10 @@ impl Default for CopilotState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CopilotState {
|
impl CopilotState {
|
||||||
|
fn active_completion(&self) -> Option<&copilot::Completion> {
|
||||||
|
self.completions.get(self.active_completion_index)
|
||||||
|
}
|
||||||
|
|
||||||
fn text_for_active_completion(
|
fn text_for_active_completion(
|
||||||
&self,
|
&self,
|
||||||
cursor: Anchor,
|
cursor: Anchor,
|
||||||
|
@ -1044,7 +1048,7 @@ impl CopilotState {
|
||||||
) -> Option<&str> {
|
) -> Option<&str> {
|
||||||
use language::ToOffset as _;
|
use language::ToOffset as _;
|
||||||
|
|
||||||
let completion = self.completions.get(self.active_completion_index)?;
|
let completion = self.active_completion()?;
|
||||||
let excerpt_id = self.excerpt_id?;
|
let excerpt_id = self.excerpt_id?;
|
||||||
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
|
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
|
||||||
if excerpt_id != cursor.excerpt_id
|
if excerpt_id != cursor.excerpt_id
|
||||||
|
@ -1097,7 +1101,7 @@ impl CopilotState {
|
||||||
|
|
||||||
fn push_completion(&mut self, new_completion: copilot::Completion) {
|
fn push_completion(&mut self, new_completion: copilot::Completion) {
|
||||||
for completion in &self.completions {
|
for completion in &self.completions {
|
||||||
if *completion == new_completion {
|
if completion.text == new_completion.text && completion.range == new_completion.range {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1496,7 +1500,7 @@ impl Editor {
|
||||||
self.refresh_code_actions(cx);
|
self.refresh_code_actions(cx);
|
||||||
self.refresh_document_highlights(cx);
|
self.refresh_document_highlights(cx);
|
||||||
refresh_matching_bracket_highlights(self, cx);
|
refresh_matching_bracket_highlights(self, cx);
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
self.blink_manager.update(cx, BlinkManager::pause_blinking);
|
||||||
|
@ -1870,7 +1874,7 @@ impl Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.hide_copilot_suggestion(cx).is_some() {
|
if self.discard_copilot_suggestion(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2969,7 +2973,7 @@ impl Editor {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cycle_suggestions(
|
fn cycle_copilot_suggestions(
|
||||||
&mut self,
|
&mut self,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -3020,7 +3024,7 @@ impl Editor {
|
||||||
|
|
||||||
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
|
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
self.cycle_suggestions(Direction::Next, cx);
|
self.cycle_copilot_suggestions(Direction::Next, cx);
|
||||||
} else {
|
} else {
|
||||||
self.refresh_copilot_suggestions(false, cx);
|
self.refresh_copilot_suggestions(false, cx);
|
||||||
}
|
}
|
||||||
|
@ -3032,15 +3036,45 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
self.cycle_suggestions(Direction::Prev, cx);
|
self.cycle_copilot_suggestions(Direction::Prev, cx);
|
||||||
} else {
|
} else {
|
||||||
self.refresh_copilot_suggestions(false, cx);
|
self.refresh_copilot_suggestions(false, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if let Some(text) = self.hide_copilot_suggestion(cx) {
|
if let Some(suggestion) = self
|
||||||
self.insert_with_autoindent_mode(&text.to_string(), None, cx);
|
.display_map
|
||||||
|
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx))
|
||||||
|
{
|
||||||
|
if let Some((copilot, completion)) =
|
||||||
|
Copilot::global(cx).zip(self.copilot_state.active_completion())
|
||||||
|
{
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
|
||||||
|
cx.notify();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
if self.has_active_copilot_suggestion(cx) {
|
||||||
|
if let Some(copilot) = Copilot::global(cx) {
|
||||||
|
copilot
|
||||||
|
.update(cx, |copilot, cx| {
|
||||||
|
copilot.discard_completions(&self.copilot_state.completions, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.display_map
|
||||||
|
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
||||||
|
cx.notify();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -3051,18 +3085,6 @@ impl Editor {
|
||||||
self.display_map.read(cx).has_suggestion()
|
self.display_map.read(cx).has_suggestion()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Rope> {
|
|
||||||
if self.has_active_copilot_suggestion(cx) {
|
|
||||||
let old_suggestion = self
|
|
||||||
.display_map
|
|
||||||
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
|
|
||||||
cx.notify();
|
|
||||||
old_suggestion.map(|suggestion| suggestion.text)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let selection = self.selections.newest_anchor();
|
let selection = self.selections.newest_anchor();
|
||||||
|
@ -3072,7 +3094,7 @@ impl Editor {
|
||||||
|| !self.completion_tasks.is_empty()
|
|| !self.completion_tasks.is_empty()
|
||||||
|| selection.start != selection.end
|
|| selection.start != selection.end
|
||||||
{
|
{
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
} else if let Some(text) = self
|
} else if let Some(text) = self
|
||||||
.copilot_state
|
.copilot_state
|
||||||
.text_for_active_completion(cursor, &snapshot)
|
.text_for_active_completion(cursor, &snapshot)
|
||||||
|
@ -3088,13 +3110,13 @@ impl Editor {
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else {
|
} else {
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
|
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.copilot_state = Default::default();
|
self.copilot_state = Default::default();
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_code_actions_indicator(
|
pub fn render_code_actions_indicator(
|
||||||
|
@ -3212,7 +3234,7 @@ impl Editor {
|
||||||
self.completion_tasks.clear();
|
self.completion_tasks.clear();
|
||||||
}
|
}
|
||||||
self.context_menu = Some(menu);
|
self.context_menu = Some(menu);
|
||||||
self.hide_copilot_suggestion(cx);
|
self.discard_copilot_suggestion(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6643,6 +6665,7 @@ impl Editor {
|
||||||
multi_buffer::Event::DiagnosticsUpdated => {
|
multi_buffer::Event::DiagnosticsUpdated => {
|
||||||
self.refresh_active_diagnostics(cx);
|
self.refresh_active_diagnostics(cx);
|
||||||
}
|
}
|
||||||
|
multi_buffer::Event::LanguageChanged => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4371,7 +4371,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
cx.set_state(
|
cx.set_state(
|
||||||
&[
|
&[
|
||||||
"one ", //
|
"one ", //
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"three ", //
|
"three ", //
|
||||||
"four", //
|
"four", //
|
||||||
]
|
]
|
||||||
|
@ -4446,7 +4446,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
&[
|
&[
|
||||||
"one", //
|
"one", //
|
||||||
"", //
|
"", //
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"", //
|
"", //
|
||||||
"three", //
|
"three", //
|
||||||
"four", //
|
"four", //
|
||||||
|
@ -4461,7 +4461,7 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
|
||||||
cx.assert_editor_state(
|
cx.assert_editor_state(
|
||||||
&[
|
&[
|
||||||
"one ", //
|
"one ", //
|
||||||
"twoˇ", //
|
"twoˇ", //
|
||||||
"three ", //
|
"three ", //
|
||||||
"four", //
|
"four", //
|
||||||
]
|
]
|
||||||
|
|
|
@ -436,6 +436,7 @@ mod tests {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
||||||
use language::{Diagnostic, DiagnosticSet};
|
use language::{Diagnostic, DiagnosticSet};
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use project::HoverBlock;
|
use project::HoverBlock;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
|
|
||||||
|
@ -620,7 +621,7 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
&snapshot,
|
&snapshot,
|
||||||
);
|
);
|
||||||
buffer.update_diagnostics(set, cx);
|
buffer.update_diagnostics(LanguageServerId(0), set, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hover pops diagnostic immediately
|
// Hover pops diagnostic immediately
|
||||||
|
|
|
@ -64,6 +64,7 @@ pub enum Event {
|
||||||
},
|
},
|
||||||
Edited,
|
Edited,
|
||||||
Reloaded,
|
Reloaded,
|
||||||
|
LanguageChanged,
|
||||||
Reparsed,
|
Reparsed,
|
||||||
Saved,
|
Saved,
|
||||||
FileHandleChanged,
|
FileHandleChanged,
|
||||||
|
@ -1302,6 +1303,7 @@ impl MultiBuffer {
|
||||||
language::Event::Saved => Event::Saved,
|
language::Event::Saved => Event::Saved,
|
||||||
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
language::Event::FileHandleChanged => Event::FileHandleChanged,
|
||||||
language::Event::Reloaded => Event::Reloaded,
|
language::Event::Reloaded => Event::Reloaded,
|
||||||
|
language::Event::LanguageChanged => Event::LanguageChanged,
|
||||||
language::Event::Reparsed => Event::Reparsed,
|
language::Event::Reparsed => Event::Reparsed,
|
||||||
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
language::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated,
|
||||||
language::Event::Closed => Event::Closed,
|
language::Event::Closed => Event::Closed,
|
||||||
|
@ -2762,6 +2764,15 @@ impl MultiBufferSnapshot {
|
||||||
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
|
.and_then(|(buffer, offset)| buffer.language_scope_at(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn language_indent_size_at<T: ToOffset>(
|
||||||
|
&self,
|
||||||
|
position: T,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<IndentSize> {
|
||||||
|
let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?;
|
||||||
|
Some(buffer_snapshot.language_indent_size_at(offset, cx))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
self.is_dirty
|
self.is_dirty
|
||||||
}
|
}
|
||||||
|
@ -2789,7 +2800,7 @@ impl MultiBufferSnapshot {
|
||||||
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a
|
||||||
where
|
where
|
||||||
T: 'a + ToOffset,
|
T: 'a + ToOffset,
|
||||||
O: 'a + text::FromAnchor,
|
O: 'a + text::FromAnchor + Ord,
|
||||||
{
|
{
|
||||||
self.as_singleton()
|
self.as_singleton()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -44,7 +44,7 @@ seahash = "4.1"
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
time = { version = "0.3", features = ["serde", "serde-well-known"] }
|
||||||
tiny-skia = "0.5"
|
tiny-skia = "0.5"
|
||||||
|
|
|
@ -50,7 +50,7 @@ serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
similar = "1.3"
|
similar = "1.3"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
tree-sitter = "0.20"
|
tree-sitter = "0.20"
|
||||||
tree-sitter-rust = { version = "*", optional = true }
|
tree-sitter-rust = { version = "*", optional = true }
|
||||||
|
|
|
@ -16,9 +16,11 @@ use clock::ReplicaId;
|
||||||
use fs::LineEnding;
|
use fs::LineEnding;
|
||||||
use futures::FutureExt as _;
|
use futures::FutureExt as _;
|
||||||
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
|
use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use similar::{ChangeTag, TextDiff};
|
use similar::{ChangeTag, TextDiff};
|
||||||
|
use smallvec::SmallVec;
|
||||||
use smol::future::yield_now;
|
use smol::future::yield_now;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
@ -71,7 +73,7 @@ pub struct Buffer {
|
||||||
syntax_map: Mutex<SyntaxMap>,
|
syntax_map: Mutex<SyntaxMap>,
|
||||||
parsing_in_background: bool,
|
parsing_in_background: bool,
|
||||||
parse_count: usize,
|
parse_count: usize,
|
||||||
diagnostics: DiagnosticSet,
|
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
|
||||||
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
remote_selections: TreeMap<ReplicaId, SelectionSet>,
|
||||||
selections_update_count: usize,
|
selections_update_count: usize,
|
||||||
diagnostics_update_count: usize,
|
diagnostics_update_count: usize,
|
||||||
|
@ -88,7 +90,7 @@ pub struct BufferSnapshot {
|
||||||
pub git_diff: git::diff::BufferDiff,
|
pub git_diff: git::diff::BufferDiff,
|
||||||
pub(crate) syntax: SyntaxSnapshot,
|
pub(crate) syntax: SyntaxSnapshot,
|
||||||
file: Option<Arc<dyn File>>,
|
file: Option<Arc<dyn File>>,
|
||||||
diagnostics: DiagnosticSet,
|
diagnostics: SmallVec<[(LanguageServerId, DiagnosticSet); 2]>,
|
||||||
diagnostics_update_count: usize,
|
diagnostics_update_count: usize,
|
||||||
file_update_count: usize,
|
file_update_count: usize,
|
||||||
git_diff_update_count: usize,
|
git_diff_update_count: usize,
|
||||||
|
@ -156,6 +158,7 @@ pub struct Completion {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CodeAction {
|
pub struct CodeAction {
|
||||||
|
pub server_id: LanguageServerId,
|
||||||
pub range: Range<Anchor>,
|
pub range: Range<Anchor>,
|
||||||
pub lsp_action: lsp::CodeAction,
|
pub lsp_action: lsp::CodeAction,
|
||||||
}
|
}
|
||||||
|
@ -163,16 +166,20 @@ pub struct CodeAction {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
Buffer(text::Operation),
|
Buffer(text::Operation),
|
||||||
|
|
||||||
UpdateDiagnostics {
|
UpdateDiagnostics {
|
||||||
|
server_id: LanguageServerId,
|
||||||
diagnostics: Arc<[DiagnosticEntry<Anchor>]>,
|
diagnostics: Arc<[DiagnosticEntry<Anchor>]>,
|
||||||
lamport_timestamp: clock::Lamport,
|
lamport_timestamp: clock::Lamport,
|
||||||
},
|
},
|
||||||
|
|
||||||
UpdateSelections {
|
UpdateSelections {
|
||||||
selections: Arc<[Selection<Anchor>]>,
|
selections: Arc<[Selection<Anchor>]>,
|
||||||
lamport_timestamp: clock::Lamport,
|
lamport_timestamp: clock::Lamport,
|
||||||
line_mode: bool,
|
line_mode: bool,
|
||||||
cursor_shape: CursorShape,
|
cursor_shape: CursorShape,
|
||||||
},
|
},
|
||||||
|
|
||||||
UpdateCompletionTriggers {
|
UpdateCompletionTriggers {
|
||||||
triggers: Vec<String>,
|
triggers: Vec<String>,
|
||||||
lamport_timestamp: clock::Lamport,
|
lamport_timestamp: clock::Lamport,
|
||||||
|
@ -187,6 +194,7 @@ pub enum Event {
|
||||||
Saved,
|
Saved,
|
||||||
FileHandleChanged,
|
FileHandleChanged,
|
||||||
Reloaded,
|
Reloaded,
|
||||||
|
LanguageChanged,
|
||||||
Reparsed,
|
Reparsed,
|
||||||
DiagnosticsUpdated,
|
DiagnosticsUpdated,
|
||||||
Closed,
|
Closed,
|
||||||
|
@ -407,6 +415,7 @@ impl Buffer {
|
||||||
) -> Task<Vec<proto::Operation>> {
|
) -> Task<Vec<proto::Operation>> {
|
||||||
let mut operations = Vec::new();
|
let mut operations = Vec::new();
|
||||||
operations.extend(self.deferred_ops.iter().map(proto::serialize_operation));
|
operations.extend(self.deferred_ops.iter().map(proto::serialize_operation));
|
||||||
|
|
||||||
operations.extend(self.remote_selections.iter().map(|(_, set)| {
|
operations.extend(self.remote_selections.iter().map(|(_, set)| {
|
||||||
proto::serialize_operation(&Operation::UpdateSelections {
|
proto::serialize_operation(&Operation::UpdateSelections {
|
||||||
selections: set.selections.clone(),
|
selections: set.selections.clone(),
|
||||||
|
@ -415,10 +424,15 @@ impl Buffer {
|
||||||
cursor_shape: set.cursor_shape,
|
cursor_shape: set.cursor_shape,
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics {
|
|
||||||
diagnostics: self.diagnostics.iter().cloned().collect(),
|
for (server_id, diagnostics) in &self.diagnostics {
|
||||||
lamport_timestamp: self.diagnostics_timestamp,
|
operations.push(proto::serialize_operation(&Operation::UpdateDiagnostics {
|
||||||
}));
|
lamport_timestamp: self.diagnostics_timestamp,
|
||||||
|
server_id: *server_id,
|
||||||
|
diagnostics: diagnostics.iter().cloned().collect(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
operations.push(proto::serialize_operation(
|
operations.push(proto::serialize_operation(
|
||||||
&Operation::UpdateCompletionTriggers {
|
&Operation::UpdateCompletionTriggers {
|
||||||
triggers: self.completion_triggers.clone(),
|
triggers: self.completion_triggers.clone(),
|
||||||
|
@ -536,6 +550,7 @@ impl Buffer {
|
||||||
self.syntax_map.lock().clear();
|
self.syntax_map.lock().clear();
|
||||||
self.language = language;
|
self.language = language;
|
||||||
self.reparse(cx);
|
self.reparse(cx);
|
||||||
|
cx.emit(Event::LanguageChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
|
pub fn set_language_registry(&mut self, language_registry: Arc<LanguageRegistry>) {
|
||||||
|
@ -863,13 +878,19 @@ impl Buffer {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(&mut self, diagnostics: DiagnosticSet, cx: &mut ModelContext<Self>) {
|
pub fn update_diagnostics(
|
||||||
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
diagnostics: DiagnosticSet,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
let lamport_timestamp = self.text.lamport_clock.tick();
|
let lamport_timestamp = self.text.lamport_clock.tick();
|
||||||
let op = Operation::UpdateDiagnostics {
|
let op = Operation::UpdateDiagnostics {
|
||||||
|
server_id,
|
||||||
diagnostics: diagnostics.iter().cloned().collect(),
|
diagnostics: diagnostics.iter().cloned().collect(),
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
};
|
};
|
||||||
self.apply_diagnostic_update(diagnostics, lamport_timestamp, cx);
|
self.apply_diagnostic_update(server_id, diagnostics, lamport_timestamp, cx);
|
||||||
self.send_operation(op, cx);
|
self.send_operation(op, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1577,11 +1598,13 @@ impl Buffer {
|
||||||
unreachable!("buffer operations should never be applied at this layer")
|
unreachable!("buffer operations should never be applied at this layer")
|
||||||
}
|
}
|
||||||
Operation::UpdateDiagnostics {
|
Operation::UpdateDiagnostics {
|
||||||
|
server_id,
|
||||||
diagnostics: diagnostic_set,
|
diagnostics: diagnostic_set,
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
} => {
|
} => {
|
||||||
let snapshot = self.snapshot();
|
let snapshot = self.snapshot();
|
||||||
self.apply_diagnostic_update(
|
self.apply_diagnostic_update(
|
||||||
|
server_id,
|
||||||
DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot),
|
DiagnosticSet::from_sorted_entries(diagnostic_set.iter().cloned(), &snapshot),
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
cx,
|
cx,
|
||||||
|
@ -1623,12 +1646,16 @@ impl Buffer {
|
||||||
|
|
||||||
fn apply_diagnostic_update(
|
fn apply_diagnostic_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
diagnostics: DiagnosticSet,
|
diagnostics: DiagnosticSet,
|
||||||
lamport_timestamp: clock::Lamport,
|
lamport_timestamp: clock::Lamport,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if lamport_timestamp > self.diagnostics_timestamp {
|
if lamport_timestamp > self.diagnostics_timestamp {
|
||||||
self.diagnostics = diagnostics;
|
match self.diagnostics.binary_search_by_key(&server_id, |e| e.0) {
|
||||||
|
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
|
||||||
|
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
|
||||||
|
};
|
||||||
self.diagnostics_timestamp = lamport_timestamp;
|
self.diagnostics_timestamp = lamport_timestamp;
|
||||||
self.diagnostics_update_count += 1;
|
self.diagnostics_update_count += 1;
|
||||||
self.text.lamport_clock.observe(lamport_timestamp);
|
self.text.lamport_clock.observe(lamport_timestamp);
|
||||||
|
@ -2502,14 +2529,55 @@ impl BufferSnapshot {
|
||||||
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
|
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
|
||||||
where
|
where
|
||||||
T: 'a + Clone + ToOffset,
|
T: 'a + Clone + ToOffset,
|
||||||
O: 'a + FromAnchor,
|
O: 'a + FromAnchor + Ord,
|
||||||
{
|
{
|
||||||
self.diagnostics.range(search_range, self, true, reversed)
|
let mut iterators: Vec<_> = self
|
||||||
|
.diagnostics
|
||||||
|
.iter()
|
||||||
|
.map(|(_, collection)| {
|
||||||
|
collection
|
||||||
|
.range::<T, O>(search_range.clone(), self, true, reversed)
|
||||||
|
.peekable()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
std::iter::from_fn(move || {
|
||||||
|
let (next_ix, _) = iterators
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(ix, iter)| Some((ix, iter.peek()?)))
|
||||||
|
.min_by(|(_, a), (_, b)| a.range.start.cmp(&b.range.start))?;
|
||||||
|
iterators[next_ix].next()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostic_groups(&self) -> Vec<DiagnosticGroup<Anchor>> {
|
pub fn diagnostic_groups(
|
||||||
|
&self,
|
||||||
|
language_server_id: Option<LanguageServerId>,
|
||||||
|
) -> Vec<(LanguageServerId, DiagnosticGroup<Anchor>)> {
|
||||||
let mut groups = Vec::new();
|
let mut groups = Vec::new();
|
||||||
self.diagnostics.groups(&mut groups, self);
|
|
||||||
|
if let Some(language_server_id) = language_server_id {
|
||||||
|
if let Ok(ix) = self
|
||||||
|
.diagnostics
|
||||||
|
.binary_search_by_key(&language_server_id, |e| e.0)
|
||||||
|
{
|
||||||
|
self.diagnostics[ix]
|
||||||
|
.1
|
||||||
|
.groups(language_server_id, &mut groups, self);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (language_server_id, diagnostics) in self.diagnostics.iter() {
|
||||||
|
diagnostics.groups(*language_server_id, &mut groups, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.sort_by(|(id_a, group_a), (id_b, group_b)| {
|
||||||
|
let a_start = &group_a.entries[group_a.primary_ix].range.start;
|
||||||
|
let b_start = &group_b.entries[group_b.primary_ix].range.start;
|
||||||
|
a_start.cmp(b_start, self).then_with(|| id_a.cmp(&id_b))
|
||||||
|
});
|
||||||
|
|
||||||
groups
|
groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2520,7 +2588,9 @@ impl BufferSnapshot {
|
||||||
where
|
where
|
||||||
O: 'a + FromAnchor,
|
O: 'a + FromAnchor,
|
||||||
{
|
{
|
||||||
self.diagnostics.group(group_id, self)
|
self.diagnostics
|
||||||
|
.iter()
|
||||||
|
.flat_map(move |(_, set)| set.group(group_id, self))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diagnostics_update_count(&self) -> usize {
|
pub fn diagnostics_update_count(&self) -> usize {
|
||||||
|
|
|
@ -1866,7 +1866,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
|
||||||
buffer,
|
buffer,
|
||||||
);
|
);
|
||||||
log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
|
log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
|
||||||
buffer.update_diagnostics(diagnostics, cx);
|
buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
|
||||||
});
|
});
|
||||||
mutation_count -= 1;
|
mutation_count -= 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::Diagnostic;
|
use crate::Diagnostic;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{Ordering, Reverse},
|
cmp::{Ordering, Reverse},
|
||||||
iter,
|
iter,
|
||||||
|
@ -129,7 +130,12 @@ impl DiagnosticSet {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn groups(&self, output: &mut Vec<DiagnosticGroup<Anchor>>, buffer: &text::BufferSnapshot) {
|
pub fn groups(
|
||||||
|
&self,
|
||||||
|
language_server_id: LanguageServerId,
|
||||||
|
output: &mut Vec<(LanguageServerId, DiagnosticGroup<Anchor>)>,
|
||||||
|
buffer: &text::BufferSnapshot,
|
||||||
|
) {
|
||||||
let mut groups = HashMap::default();
|
let mut groups = HashMap::default();
|
||||||
for entry in self.diagnostics.iter() {
|
for entry in self.diagnostics.iter() {
|
||||||
groups
|
groups
|
||||||
|
@ -144,16 +150,22 @@ impl DiagnosticSet {
|
||||||
entries
|
entries
|
||||||
.iter()
|
.iter()
|
||||||
.position(|entry| entry.diagnostic.is_primary)
|
.position(|entry| entry.diagnostic.is_primary)
|
||||||
.map(|primary_ix| DiagnosticGroup {
|
.map(|primary_ix| {
|
||||||
entries,
|
(
|
||||||
primary_ix,
|
language_server_id,
|
||||||
|
DiagnosticGroup {
|
||||||
|
entries,
|
||||||
|
primary_ix,
|
||||||
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
output[start_ix..].sort_unstable_by(|a, b| {
|
output[start_ix..].sort_unstable_by(|(id_a, group_a), (id_b, group_b)| {
|
||||||
a.entries[a.primary_ix]
|
group_a.entries[group_a.primary_ix]
|
||||||
.range
|
.range
|
||||||
.start
|
.start
|
||||||
.cmp(&b.entries[b.primary_ix].range.start, buffer)
|
.cmp(&group_b.entries[group_b.primary_ix].range.start, buffer)
|
||||||
|
.then_with(|| id_a.cmp(&id_b))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ use futures::channel::mpsc;
|
||||||
pub use buffer::Operation;
|
pub use buffer::Operation;
|
||||||
pub use buffer::*;
|
pub use buffer::*;
|
||||||
pub use diagnostic_set::DiagnosticEntry;
|
pub use diagnostic_set::DiagnosticEntry;
|
||||||
|
pub use lsp::LanguageServerId;
|
||||||
pub use outline::{Outline, OutlineItem};
|
pub use outline::{Outline, OutlineItem};
|
||||||
pub use tree_sitter::{Parser, Tree};
|
pub use tree_sitter::{Parser, Tree};
|
||||||
|
|
||||||
|
@ -414,7 +415,7 @@ pub struct BracketPair {
|
||||||
pub struct Language {
|
pub struct Language {
|
||||||
pub(crate) config: LanguageConfig,
|
pub(crate) config: LanguageConfig,
|
||||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||||
pub(crate) adapter: Option<Arc<CachedLspAdapter>>,
|
pub(crate) adapters: Vec<Arc<CachedLspAdapter>>,
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fake_adapter: Option<(
|
fake_adapter: Option<(
|
||||||
|
@ -492,7 +493,7 @@ struct AvailableLanguage {
|
||||||
path: &'static str,
|
path: &'static str,
|
||||||
config: LanguageConfig,
|
config: LanguageConfig,
|
||||||
grammar: tree_sitter::Language,
|
grammar: tree_sitter::Language,
|
||||||
lsp_adapter: Option<Arc<dyn LspAdapter>>,
|
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||||
get_queries: fn(&str) -> LanguageQueries,
|
get_queries: fn(&str) -> LanguageQueries,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,6 +514,7 @@ pub struct LanguageRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LanguageRegistryState {
|
struct LanguageRegistryState {
|
||||||
|
next_language_server_id: usize,
|
||||||
languages: Vec<Arc<Language>>,
|
languages: Vec<Arc<Language>>,
|
||||||
available_languages: Vec<AvailableLanguage>,
|
available_languages: Vec<AvailableLanguage>,
|
||||||
next_available_language_id: AvailableLanguageId,
|
next_available_language_id: AvailableLanguageId,
|
||||||
|
@ -522,11 +524,17 @@ struct LanguageRegistryState {
|
||||||
version: usize,
|
version: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PendingLanguageServer {
|
||||||
|
pub server_id: LanguageServerId,
|
||||||
|
pub task: Task<Result<lsp::LanguageServer>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl LanguageRegistry {
|
impl LanguageRegistry {
|
||||||
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
|
pub fn new(login_shell_env_loaded: Task<()>) -> Self {
|
||||||
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
|
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
|
||||||
Self {
|
Self {
|
||||||
state: RwLock::new(LanguageRegistryState {
|
state: RwLock::new(LanguageRegistryState {
|
||||||
|
next_language_server_id: 0,
|
||||||
languages: vec![PLAIN_TEXT.clone()],
|
languages: vec![PLAIN_TEXT.clone()],
|
||||||
available_languages: Default::default(),
|
available_languages: Default::default(),
|
||||||
next_available_language_id: 0,
|
next_available_language_id: 0,
|
||||||
|
@ -558,7 +566,7 @@ impl LanguageRegistry {
|
||||||
path: &'static str,
|
path: &'static str,
|
||||||
config: LanguageConfig,
|
config: LanguageConfig,
|
||||||
grammar: tree_sitter::Language,
|
grammar: tree_sitter::Language,
|
||||||
lsp_adapter: Option<Arc<dyn LspAdapter>>,
|
lsp_adapters: Vec<Arc<dyn LspAdapter>>,
|
||||||
get_queries: fn(&str) -> LanguageQueries,
|
get_queries: fn(&str) -> LanguageQueries,
|
||||||
) {
|
) {
|
||||||
let state = &mut *self.state.write();
|
let state = &mut *self.state.write();
|
||||||
|
@ -567,7 +575,7 @@ impl LanguageRegistry {
|
||||||
path,
|
path,
|
||||||
config,
|
config,
|
||||||
grammar,
|
grammar,
|
||||||
lsp_adapter,
|
lsp_adapters,
|
||||||
get_queries,
|
get_queries,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -590,12 +598,13 @@ impl LanguageRegistry {
|
||||||
state
|
state
|
||||||
.available_languages
|
.available_languages
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|l| l.lsp_adapter.clone())
|
.flat_map(|l| l.lsp_adapters.clone())
|
||||||
.chain(
|
.chain(
|
||||||
state
|
state
|
||||||
.languages
|
.languages
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|l| l.adapter.as_ref().map(|a| a.adapter.clone())),
|
.flat_map(|language| &language.adapters)
|
||||||
|
.map(|adapter| adapter.adapter.clone()),
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
};
|
};
|
||||||
|
@ -721,7 +730,7 @@ impl LanguageRegistry {
|
||||||
let queries = (language.get_queries)(&language.path);
|
let queries = (language.get_queries)(&language.path);
|
||||||
let language =
|
let language =
|
||||||
Language::new(language.config, Some(language.grammar))
|
Language::new(language.config, Some(language.grammar))
|
||||||
.with_lsp_adapter(language.lsp_adapter)
|
.with_lsp_adapters(language.lsp_adapters)
|
||||||
.await;
|
.await;
|
||||||
let name = language.name();
|
let name = language.name();
|
||||||
match language.with_queries(queries) {
|
match language.with_queries(queries) {
|
||||||
|
@ -776,16 +785,15 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
pub fn start_language_server(
|
pub fn start_language_server(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
server_id: usize,
|
|
||||||
language: Arc<Language>,
|
language: Arc<Language>,
|
||||||
|
adapter: Arc<CachedLspAdapter>,
|
||||||
root_path: Arc<Path>,
|
root_path: Arc<Path>,
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Option<Task<Result<lsp::LanguageServer>>> {
|
) -> Option<PendingLanguageServer> {
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
if language.fake_adapter.is_some() {
|
if language.fake_adapter.is_some() {
|
||||||
let language = language;
|
let task = cx.spawn(|cx| async move {
|
||||||
return Some(cx.spawn(|cx| async move {
|
|
||||||
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
|
||||||
let (server, mut fake_server) = lsp::LanguageServer::fake(
|
let (server, mut fake_server) = lsp::LanguageServer::fake(
|
||||||
fake_adapter.name.to_string(),
|
fake_adapter.name.to_string(),
|
||||||
|
@ -810,7 +818,10 @@ impl LanguageRegistry {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
let server_id = self.state.write().next_language_server_id();
|
||||||
|
return Some(PendingLanguageServer { server_id, task });
|
||||||
}
|
}
|
||||||
|
|
||||||
let download_dir = self
|
let download_dir = self
|
||||||
|
@ -820,11 +831,16 @@ impl LanguageRegistry {
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
let adapter = language.adapter.clone()?;
|
let language = language.clone();
|
||||||
|
let http_client = http_client.clone();
|
||||||
|
let download_dir = download_dir.clone();
|
||||||
|
let root_path = root_path.clone();
|
||||||
|
let adapter = adapter.clone();
|
||||||
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone();
|
||||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
||||||
|
let server_id = self.state.write().next_language_server_id();
|
||||||
|
|
||||||
Some(cx.spawn(|cx| async move {
|
let task = cx.spawn(|cx| async move {
|
||||||
login_shell_env_loaded.await;
|
login_shell_env_loaded.await;
|
||||||
|
|
||||||
let mut lock = this.lsp_binary_paths.lock();
|
let mut lock = this.lsp_binary_paths.lock();
|
||||||
|
@ -856,7 +872,9 @@ impl LanguageRegistry {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}))
|
});
|
||||||
|
|
||||||
|
Some(PendingLanguageServer { server_id, task })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_server_binary_statuses(
|
pub fn language_server_binary_statuses(
|
||||||
|
@ -867,6 +885,10 @@ impl LanguageRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageRegistryState {
|
impl LanguageRegistryState {
|
||||||
|
fn next_language_server_id(&mut self) -> LanguageServerId {
|
||||||
|
LanguageServerId(post_inc(&mut self.next_language_server_id))
|
||||||
|
}
|
||||||
|
|
||||||
fn add(&mut self, language: Arc<Language>) {
|
fn add(&mut self, language: Arc<Language>) {
|
||||||
if let Some(theme) = self.theme.as_ref() {
|
if let Some(theme) = self.theme.as_ref() {
|
||||||
language.set_theme(&theme.editor.syntax);
|
language.set_theme(&theme.editor.syntax);
|
||||||
|
@ -974,15 +996,15 @@ impl Language {
|
||||||
highlight_map: Default::default(),
|
highlight_map: Default::default(),
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
adapter: None,
|
adapters: Vec::new(),
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
fake_adapter: None,
|
fake_adapter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lsp_adapter(&self) -> Option<Arc<CachedLspAdapter>> {
|
pub fn lsp_adapters(&self) -> &[Arc<CachedLspAdapter>] {
|
||||||
self.adapter.clone()
|
&self.adapters
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> Option<usize> {
|
pub fn id(&self) -> Option<usize> {
|
||||||
|
@ -1209,9 +1231,9 @@ impl Language {
|
||||||
Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
|
Arc::get_mut(self.grammar.as_mut().unwrap()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn with_lsp_adapter(mut self, lsp_adapter: Option<Arc<dyn LspAdapter>>) -> Self {
|
pub async fn with_lsp_adapters(mut self, lsp_adapters: Vec<Arc<dyn LspAdapter>>) -> Self {
|
||||||
if let Some(adapter) = lsp_adapter {
|
for adapter in lsp_adapters {
|
||||||
self.adapter = Some(CachedLspAdapter::new(adapter).await);
|
self.adapters.push(CachedLspAdapter::new(adapter).await);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1224,7 +1246,7 @@ impl Language {
|
||||||
let (servers_tx, servers_rx) = mpsc::unbounded();
|
let (servers_tx, servers_rx) = mpsc::unbounded();
|
||||||
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
self.fake_adapter = Some((servers_tx, fake_lsp_adapter.clone()));
|
||||||
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
let adapter = CachedLspAdapter::new(Arc::new(fake_lsp_adapter)).await;
|
||||||
self.adapter = Some(adapter);
|
self.adapters = vec![adapter];
|
||||||
servers_rx
|
servers_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1233,28 +1255,31 @@ impl Language {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
|
pub async fn disk_based_diagnostic_sources(&self) -> &[String] {
|
||||||
match self.adapter.as_ref() {
|
match self.adapters.first().as_ref() {
|
||||||
Some(adapter) => &adapter.disk_based_diagnostic_sources,
|
Some(adapter) => &adapter.disk_based_diagnostic_sources,
|
||||||
None => &[],
|
None => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
|
pub async fn disk_based_diagnostics_progress_token(&self) -> Option<&str> {
|
||||||
if let Some(adapter) = self.adapter.as_ref() {
|
for adapter in &self.adapters {
|
||||||
adapter.disk_based_diagnostics_progress_token.as_deref()
|
let token = adapter.disk_based_diagnostics_progress_token.as_deref();
|
||||||
} else {
|
if token.is_some() {
|
||||||
None
|
return token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
|
pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
|
||||||
if let Some(processor) = self.adapter.as_ref() {
|
for adapter in &self.adapters {
|
||||||
processor.process_diagnostics(diagnostics).await;
|
adapter.process_diagnostics(diagnostics).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
|
pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
|
||||||
if let Some(adapter) = self.adapter.as_ref() {
|
for adapter in &self.adapters {
|
||||||
adapter.process_completion(completion).await;
|
adapter.process_completion(completion).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1263,7 +1288,8 @@ impl Language {
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
completion: &lsp::CompletionItem,
|
completion: &lsp::CompletionItem,
|
||||||
) -> Option<CodeLabel> {
|
) -> Option<CodeLabel> {
|
||||||
self.adapter
|
self.adapters
|
||||||
|
.first()
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
.label_for_completion(completion, self)
|
.label_for_completion(completion, self)
|
||||||
.await
|
.await
|
||||||
|
@ -1274,7 +1300,8 @@ impl Language {
|
||||||
name: &str,
|
name: &str,
|
||||||
kind: lsp::SymbolKind,
|
kind: lsp::SymbolKind,
|
||||||
) -> Option<CodeLabel> {
|
) -> Option<CodeLabel> {
|
||||||
self.adapter
|
self.adapters
|
||||||
|
.first()
|
||||||
.as_ref()?
|
.as_ref()?
|
||||||
.label_for_symbol(name, kind, self)
|
.label_for_symbol(name, kind, self)
|
||||||
.await
|
.await
|
||||||
|
@ -1559,7 +1586,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
tree_sitter_javascript::language(),
|
tree_sitter_javascript::language(),
|
||||||
None,
|
vec![],
|
||||||
|_| Default::default(),
|
|_| Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1595,7 +1622,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
tree_sitter_json::language(),
|
tree_sitter_json::language(),
|
||||||
None,
|
vec![],
|
||||||
|_| Default::default(),
|
|_| Default::default(),
|
||||||
);
|
);
|
||||||
languages.register(
|
languages.register(
|
||||||
|
@ -1606,7 +1633,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
tree_sitter_rust::language(),
|
tree_sitter_rust::language(),
|
||||||
None,
|
vec![],
|
||||||
|_| Default::default(),
|
|_| Default::default(),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use lsp::DiagnosticSeverity;
|
use lsp::{DiagnosticSeverity, LanguageServerId};
|
||||||
use rpc::proto;
|
use rpc::proto;
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
use text::*;
|
use text::*;
|
||||||
|
@ -40,6 +40,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
|
||||||
crate::Operation::Buffer(text::Operation::Edit(edit)) => {
|
crate::Operation::Buffer(text::Operation::Edit(edit)) => {
|
||||||
proto::operation::Variant::Edit(serialize_edit_operation(edit))
|
proto::operation::Variant::Edit(serialize_edit_operation(edit))
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::Operation::Buffer(text::Operation::Undo {
|
crate::Operation::Buffer(text::Operation::Undo {
|
||||||
undo,
|
undo,
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
|
@ -58,6 +59,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
crate::Operation::UpdateSelections {
|
crate::Operation::UpdateSelections {
|
||||||
selections,
|
selections,
|
||||||
line_mode,
|
line_mode,
|
||||||
|
@ -70,14 +72,18 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
|
||||||
line_mode: *line_mode,
|
line_mode: *line_mode,
|
||||||
cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
|
cursor_shape: serialize_cursor_shape(cursor_shape) as i32,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
crate::Operation::UpdateDiagnostics {
|
crate::Operation::UpdateDiagnostics {
|
||||||
diagnostics,
|
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
|
server_id,
|
||||||
|
diagnostics,
|
||||||
} => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
|
} => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
|
||||||
replica_id: lamport_timestamp.replica_id as u32,
|
replica_id: lamport_timestamp.replica_id as u32,
|
||||||
lamport_timestamp: lamport_timestamp.value,
|
lamport_timestamp: lamport_timestamp.value,
|
||||||
|
server_id: server_id.0 as u64,
|
||||||
diagnostics: serialize_diagnostics(diagnostics.iter()),
|
diagnostics: serialize_diagnostics(diagnostics.iter()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
crate::Operation::UpdateCompletionTriggers {
|
crate::Operation::UpdateCompletionTriggers {
|
||||||
triggers,
|
triggers,
|
||||||
lamport_timestamp,
|
lamport_timestamp,
|
||||||
|
@ -267,11 +273,12 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
|
||||||
}
|
}
|
||||||
proto::operation::Variant::UpdateDiagnostics(message) => {
|
proto::operation::Variant::UpdateDiagnostics(message) => {
|
||||||
crate::Operation::UpdateDiagnostics {
|
crate::Operation::UpdateDiagnostics {
|
||||||
diagnostics: deserialize_diagnostics(message.diagnostics),
|
|
||||||
lamport_timestamp: clock::Lamport {
|
lamport_timestamp: clock::Lamport {
|
||||||
replica_id: message.replica_id as ReplicaId,
|
replica_id: message.replica_id as ReplicaId,
|
||||||
value: message.lamport_timestamp,
|
value: message.lamport_timestamp,
|
||||||
},
|
},
|
||||||
|
server_id: LanguageServerId(message.server_id as usize),
|
||||||
|
diagnostics: deserialize_diagnostics(message.diagnostics),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proto::operation::Variant::UpdateCompletionTriggers(message) => {
|
proto::operation::Variant::UpdateCompletionTriggers(message) => {
|
||||||
|
@ -462,6 +469,7 @@ pub async fn deserialize_completion(
|
||||||
|
|
||||||
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
||||||
proto::CodeAction {
|
proto::CodeAction {
|
||||||
|
server_id: action.server_id.0 as u64,
|
||||||
start: Some(serialize_anchor(&action.range.start)),
|
start: Some(serialize_anchor(&action.range.start)),
|
||||||
end: Some(serialize_anchor(&action.range.end)),
|
end: Some(serialize_anchor(&action.range.end)),
|
||||||
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
|
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
|
||||||
|
@ -479,6 +487,7 @@ pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction>
|
||||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||||
let lsp_action = serde_json::from_slice(&action.lsp_action)?;
|
let lsp_action = serde_json::from_slice(&action.lsp_action)?;
|
||||||
Ok(CodeAction {
|
Ok(CodeAction {
|
||||||
|
server_id: LanguageServerId(action.server_id as usize),
|
||||||
range: start..end,
|
range: start..end,
|
||||||
lsp_action,
|
lsp_action,
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,6 +16,7 @@ use smol::{
|
||||||
process::{self, Child},
|
process::{self, Child},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
io::Write,
|
io::Write,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
@ -35,7 +36,7 @@ type NotificationHandler = Box<dyn Send + FnMut(Option<usize>, &str, AsyncAppCon
|
||||||
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
type ResponseHandler = Box<dyn Send + FnOnce(Result<&str, Error>)>;
|
||||||
|
|
||||||
pub struct LanguageServer {
|
pub struct LanguageServer {
|
||||||
server_id: usize,
|
server_id: LanguageServerId,
|
||||||
next_id: AtomicUsize,
|
next_id: AtomicUsize,
|
||||||
outbound_tx: channel::Sender<Vec<u8>>,
|
outbound_tx: channel::Sender<Vec<u8>>,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -51,6 +52,10 @@ pub struct LanguageServer {
|
||||||
_server: Option<Child>,
|
_server: Option<Child>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct LanguageServerId(pub usize);
|
||||||
|
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
method: &'static str,
|
method: &'static str,
|
||||||
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
|
@ -107,7 +112,7 @@ struct Error {
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
pub fn new<T: AsRef<std::ffi::OsStr>>(
|
pub fn new<T: AsRef<std::ffi::OsStr>>(
|
||||||
server_id: usize,
|
server_id: LanguageServerId,
|
||||||
binary_path: &Path,
|
binary_path: &Path,
|
||||||
arguments: &[T],
|
arguments: &[T],
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
|
@ -158,7 +163,7 @@ impl LanguageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_internal<Stdin, Stdout, F>(
|
fn new_internal<Stdin, Stdout, F>(
|
||||||
server_id: usize,
|
server_id: LanguageServerId,
|
||||||
stdin: Stdin,
|
stdin: Stdin,
|
||||||
stdout: Stdout,
|
stdout: Stdout,
|
||||||
server: Option<Child>,
|
server: Option<Child>,
|
||||||
|
@ -581,7 +586,7 @@ impl LanguageServer {
|
||||||
&self.capabilities
|
&self.capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_id(&self) -> usize {
|
pub fn server_id(&self) -> LanguageServerId {
|
||||||
self.server_id
|
self.server_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -685,6 +690,12 @@ impl Subscription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LanguageServerId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Drop for Subscription {
|
impl Drop for Subscription {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.notification_handlers.lock().remove(self.method);
|
self.notification_handlers.lock().remove(self.method);
|
||||||
|
@ -720,7 +731,7 @@ impl LanguageServer {
|
||||||
let (notifications_tx, notifications_rx) = channel::unbounded();
|
let (notifications_tx, notifications_rx) = channel::unbounded();
|
||||||
|
|
||||||
let server = Self::new_internal(
|
let server = Self::new_internal(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
stdin_writer,
|
stdin_writer,
|
||||||
stdout_reader,
|
stdout_reader,
|
||||||
None,
|
None,
|
||||||
|
@ -731,7 +742,7 @@ impl LanguageServer {
|
||||||
);
|
);
|
||||||
let fake = FakeLanguageServer {
|
let fake = FakeLanguageServer {
|
||||||
server: Arc::new(Self::new_internal(
|
server: Arc::new(Self::new_internal(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
stdout_writer,
|
stdout_writer,
|
||||||
stdin_reader,
|
stdin_reader,
|
||||||
None,
|
None,
|
||||||
|
|
|
@ -19,6 +19,7 @@ test-support = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
text = { path = "../text" }
|
text = { path = "../text" }
|
||||||
|
copilot = { path = "../copilot" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
|
|
|
@ -12,7 +12,7 @@ use language::{
|
||||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
|
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction,
|
||||||
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
|
Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Unclipped,
|
||||||
};
|
};
|
||||||
use lsp::{DocumentHighlightKind, LanguageServer, ServerCapabilities};
|
use lsp::{DocumentHighlightKind, LanguageServer, LanguageServerId, ServerCapabilities};
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
@ -33,21 +33,25 @@ pub(crate) trait LspCommand: 'static + Sized {
|
||||||
language_server: &Arc<LanguageServer>,
|
language_server: &Arc<LanguageServer>,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> <Self::LspRequest as lsp::request::Request>::Params;
|
) -> <Self::LspRequest as lsp::request::Request>::Params;
|
||||||
|
|
||||||
async fn response_from_lsp(
|
async fn response_from_lsp(
|
||||||
self,
|
self,
|
||||||
message: <Self::LspRequest as lsp::request::Request>::Result,
|
message: <Self::LspRequest as lsp::request::Request>::Result,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Self::Response>;
|
) -> Result<Self::Response>;
|
||||||
|
|
||||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
|
||||||
|
|
||||||
async fn from_proto(
|
async fn from_proto(
|
||||||
message: Self::ProtoRequest,
|
message: Self::ProtoRequest,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Self>;
|
) -> Result<Self>;
|
||||||
|
|
||||||
fn response_to_proto(
|
fn response_to_proto(
|
||||||
response: Self::Response,
|
response: Self::Response,
|
||||||
project: &mut Project,
|
project: &mut Project,
|
||||||
|
@ -55,6 +59,7 @@ pub(crate) trait LspCommand: 'static + Sized {
|
||||||
buffer_version: &clock::Global,
|
buffer_version: &clock::Global,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
|
) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
|
||||||
|
|
||||||
async fn response_from_proto(
|
async fn response_from_proto(
|
||||||
self,
|
self,
|
||||||
message: <Self::ProtoRequest as proto::RequestMessage>::Response,
|
message: <Self::ProtoRequest as proto::RequestMessage>::Response,
|
||||||
|
@ -62,6 +67,7 @@ pub(crate) trait LspCommand: 'static + Sized {
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Self::Response>;
|
) -> Result<Self::Response>;
|
||||||
|
|
||||||
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
|
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +143,7 @@ impl LspCommand for PrepareRename {
|
||||||
message: Option<lsp::PrepareRenameResponse>,
|
message: Option<lsp::PrepareRenameResponse>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Option<Range<Anchor>>> {
|
) -> Result<Option<Range<Anchor>>> {
|
||||||
buffer.read_with(&cx, |buffer, _| {
|
buffer.read_with(&cx, |buffer, _| {
|
||||||
|
@ -263,10 +270,12 @@ impl LspCommand for PerformRename {
|
||||||
message: Option<lsp::WorkspaceEdit>,
|
message: Option<lsp::WorkspaceEdit>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<ProjectTransaction> {
|
) -> Result<ProjectTransaction> {
|
||||||
if let Some(edit) = message {
|
if let Some(edit) = message {
|
||||||
let (lsp_adapter, lsp_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
|
let (lsp_adapter, lsp_server) =
|
||||||
|
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
|
||||||
Project::deserialize_workspace_edit(
|
Project::deserialize_workspace_edit(
|
||||||
project,
|
project,
|
||||||
edit,
|
edit,
|
||||||
|
@ -380,9 +389,10 @@ impl LspCommand for GetDefinition {
|
||||||
message: Option<lsp::GotoDefinitionResponse>,
|
message: Option<lsp::GotoDefinitionResponse>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Vec<LocationLink>> {
|
) -> Result<Vec<LocationLink>> {
|
||||||
location_links_from_lsp(message, project, buffer, cx).await
|
location_links_from_lsp(message, project, buffer, server_id, cx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
|
||||||
|
@ -472,9 +482,10 @@ impl LspCommand for GetTypeDefinition {
|
||||||
message: Option<lsp::GotoTypeDefinitionResponse>,
|
message: Option<lsp::GotoTypeDefinitionResponse>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Vec<LocationLink>> {
|
) -> Result<Vec<LocationLink>> {
|
||||||
location_links_from_lsp(message, project, buffer, cx).await
|
location_links_from_lsp(message, project, buffer, server_id, cx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
|
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
|
||||||
|
@ -537,12 +548,13 @@ impl LspCommand for GetTypeDefinition {
|
||||||
fn language_server_for_buffer(
|
fn language_server_for_buffer(
|
||||||
project: &ModelHandle<Project>,
|
project: &ModelHandle<Project>,
|
||||||
buffer: &ModelHandle<Buffer>,
|
buffer: &ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
|
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
|
||||||
project
|
project
|
||||||
.read_with(cx, |project, cx| {
|
.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.language_server_for_buffer(buffer.read(cx), cx)
|
.language_server_for_buffer(buffer.read(cx), server_id, cx)
|
||||||
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("no language server found for buffer"))
|
.ok_or_else(|| anyhow!("no language server found for buffer"))
|
||||||
|
@ -614,6 +626,7 @@ async fn location_links_from_lsp(
|
||||||
message: Option<lsp::GotoDefinitionResponse>,
|
message: Option<lsp::GotoDefinitionResponse>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<Vec<LocationLink>> {
|
) -> Result<Vec<LocationLink>> {
|
||||||
let message = match message {
|
let message = match message {
|
||||||
|
@ -642,7 +655,8 @@ async fn location_links_from_lsp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (lsp_adapter, language_server) = language_server_for_buffer(&project, &buffer, &mut cx)?;
|
let (lsp_adapter, language_server) =
|
||||||
|
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
|
||||||
let mut definitions = Vec::new();
|
let mut definitions = Vec::new();
|
||||||
for (origin_range, target_uri, target_range) in unresolved_links {
|
for (origin_range, target_uri, target_range) in unresolved_links {
|
||||||
let target_buffer_handle = project
|
let target_buffer_handle = project
|
||||||
|
@ -756,11 +770,12 @@ impl LspCommand for GetReferences {
|
||||||
locations: Option<Vec<lsp::Location>>,
|
locations: Option<Vec<lsp::Location>>,
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
mut cx: AsyncAppContext,
|
mut cx: AsyncAppContext,
|
||||||
) -> Result<Vec<Location>> {
|
) -> Result<Vec<Location>> {
|
||||||
let mut references = Vec::new();
|
let mut references = Vec::new();
|
||||||
let (lsp_adapter, language_server) =
|
let (lsp_adapter, language_server) =
|
||||||
language_server_for_buffer(&project, &buffer, &mut cx)?;
|
language_server_for_buffer(&project, &buffer, server_id, &mut cx)?;
|
||||||
|
|
||||||
if let Some(locations) = locations {
|
if let Some(locations) = locations {
|
||||||
for lsp_location in locations {
|
for lsp_location in locations {
|
||||||
|
@ -917,6 +932,7 @@ impl LspCommand for GetDocumentHighlights {
|
||||||
lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
|
lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Vec<DocumentHighlight>> {
|
) -> Result<Vec<DocumentHighlight>> {
|
||||||
buffer.read_with(&cx, |buffer, _| {
|
buffer.read_with(&cx, |buffer, _| {
|
||||||
|
@ -1062,6 +1078,7 @@ impl LspCommand for GetHover {
|
||||||
message: Option<lsp::Hover>,
|
message: Option<lsp::Hover>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Self::Response> {
|
) -> Result<Self::Response> {
|
||||||
Ok(message.and_then(|hover| {
|
Ok(message.and_then(|hover| {
|
||||||
|
@ -1283,6 +1300,7 @@ impl LspCommand for GetCompletions {
|
||||||
completions: Option<lsp::CompletionResponse>,
|
completions: Option<lsp::CompletionResponse>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
buffer: ModelHandle<Buffer>,
|
buffer: ModelHandle<Buffer>,
|
||||||
|
_: LanguageServerId,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
) -> Result<Vec<Completion>> {
|
) -> Result<Vec<Completion>> {
|
||||||
let completions = if let Some(completions) = completions {
|
let completions = if let Some(completions) = completions {
|
||||||
|
@ -1502,6 +1520,7 @@ impl LspCommand for GetCodeActions {
|
||||||
actions: Option<lsp::CodeActionResponse>,
|
actions: Option<lsp::CodeActionResponse>,
|
||||||
_: ModelHandle<Project>,
|
_: ModelHandle<Project>,
|
||||||
_: ModelHandle<Buffer>,
|
_: ModelHandle<Buffer>,
|
||||||
|
server_id: LanguageServerId,
|
||||||
_: AsyncAppContext,
|
_: AsyncAppContext,
|
||||||
) -> Result<Vec<CodeAction>> {
|
) -> Result<Vec<CodeAction>> {
|
||||||
Ok(actions
|
Ok(actions
|
||||||
|
@ -1510,6 +1529,7 @@ impl LspCommand for GetCodeActions {
|
||||||
.filter_map(|entry| {
|
.filter_map(|entry| {
|
||||||
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
|
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
|
||||||
Some(CodeAction {
|
Some(CodeAction {
|
||||||
|
server_id,
|
||||||
range: self.range.clone(),
|
range: self.range.clone(),
|
||||||
lsp_action,
|
lsp_action,
|
||||||
})
|
})
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -303,6 +303,7 @@ async fn test_managing_language_servers(
|
||||||
|
|
||||||
rust_buffer2.update(cx, |buffer, cx| {
|
rust_buffer2.update(cx, |buffer, cx| {
|
||||||
buffer.update_diagnostics(
|
buffer.update_diagnostics(
|
||||||
|
LanguageServerId(0),
|
||||||
DiagnosticSet::from_sorted_entries(
|
DiagnosticSet::from_sorted_entries(
|
||||||
vec![DiagnosticEntry {
|
vec![DiagnosticEntry {
|
||||||
diagnostic: Default::default(),
|
diagnostic: Default::default(),
|
||||||
|
@ -399,7 +400,7 @@ async fn test_managing_language_servers(
|
||||||
.text_document,
|
.text_document,
|
||||||
lsp::TextDocumentItem {
|
lsp::TextDocumentItem {
|
||||||
uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
|
uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(),
|
||||||
version: 1,
|
version: 0,
|
||||||
text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
|
text: rust_buffer.read_with(cx, |buffer, _| buffer.text()),
|
||||||
language_id: Default::default()
|
language_id: Default::default()
|
||||||
}
|
}
|
||||||
|
@ -426,7 +427,7 @@ async fn test_managing_language_servers(
|
||||||
},
|
},
|
||||||
lsp::TextDocumentItem {
|
lsp::TextDocumentItem {
|
||||||
uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
|
uri: lsp::Url::from_file_path("/the-root/test3.json").unwrap(),
|
||||||
version: 1,
|
version: 0,
|
||||||
text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
|
text: rust_buffer2.read_with(cx, |buffer, _| buffer.text()),
|
||||||
language_id: Default::default()
|
language_id: Default::default()
|
||||||
}
|
}
|
||||||
|
@ -581,7 +582,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.update_diagnostics(
|
.update_diagnostics(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: Url::from_file_path("/dir/a.rs").unwrap(),
|
uri: Url::from_file_path("/dir/a.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
|
@ -598,7 +599,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project
|
project
|
||||||
.update_diagnostics(
|
.update_diagnostics(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: Url::from_file_path("/dir/b.rs").unwrap(),
|
uri: Url::from_file_path("/dir/b.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
|
@ -674,7 +675,7 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
project.update(cx, |project, cx| {
|
project.update(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.update_diagnostics(
|
.update_diagnostics(
|
||||||
0,
|
LanguageServerId(0),
|
||||||
lsp::PublishDiagnosticsParams {
|
lsp::PublishDiagnosticsParams {
|
||||||
uri: Url::from_file_path("/root/other.rs").unwrap(),
|
uri: Url::from_file_path("/root/other.rs").unwrap(),
|
||||||
version: None,
|
version: None,
|
||||||
|
@ -766,7 +767,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiskBasedDiagnosticsStarted {
|
Event::DiskBasedDiagnosticsStarted {
|
||||||
language_server_id: 0,
|
language_server_id: LanguageServerId(0),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -783,7 +784,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiagnosticsUpdated {
|
Event::DiagnosticsUpdated {
|
||||||
language_server_id: 0,
|
language_server_id: LanguageServerId(0),
|
||||||
path: (worktree_id, Path::new("a.rs")).into()
|
path: (worktree_id, Path::new("a.rs")).into()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -792,7 +793,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiskBasedDiagnosticsFinished {
|
Event::DiskBasedDiagnosticsFinished {
|
||||||
language_server_id: 0
|
language_server_id: LanguageServerId(0)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -830,7 +831,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiagnosticsUpdated {
|
Event::DiagnosticsUpdated {
|
||||||
language_server_id: 0,
|
language_server_id: LanguageServerId(0),
|
||||||
path: (worktree_id, Path::new("a.rs")).into()
|
path: (worktree_id, Path::new("a.rs")).into()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -891,7 +892,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiskBasedDiagnosticsStarted {
|
Event::DiskBasedDiagnosticsStarted {
|
||||||
language_server_id: 1
|
language_server_id: LanguageServerId(1)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
project.read_with(cx, |project, _| {
|
project.read_with(cx, |project, _| {
|
||||||
|
@ -899,7 +900,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
project
|
project
|
||||||
.language_servers_running_disk_based_diagnostics()
|
.language_servers_running_disk_based_diagnostics()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
[1]
|
[LanguageServerId(1)]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -909,7 +910,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events.next().await.unwrap(),
|
events.next().await.unwrap(),
|
||||||
Event::DiskBasedDiagnosticsFinished {
|
Event::DiskBasedDiagnosticsFinished {
|
||||||
language_server_id: 1
|
language_server_id: LanguageServerId(1)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
project.read_with(cx, |project, _| {
|
project.read_with(cx, |project, _| {
|
||||||
|
@ -917,7 +918,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
project
|
project
|
||||||
.language_servers_running_disk_based_diagnostics()
|
.language_servers_running_disk_based_diagnostics()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
[0; 0]
|
[LanguageServerId(0); 0]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1402,6 +1403,8 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
project
|
project
|
||||||
.update_buffer_diagnostics(
|
.update_buffer_diagnostics(
|
||||||
&buffer,
|
&buffer,
|
||||||
|
LanguageServerId(0),
|
||||||
|
None,
|
||||||
vec![
|
vec![
|
||||||
DiagnosticEntry {
|
DiagnosticEntry {
|
||||||
range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
|
range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)),
|
||||||
|
@ -1420,7 +1423,6 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
None,
|
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1447,6 +1449,64 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
|
||||||
|
println!("hello from stdout");
|
||||||
|
eprintln!("hello from stderr");
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
|
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
LanguageServerId(0),
|
||||||
|
Path::new("/dir/a.rs").to_owned(),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
severity: DiagnosticSeverity::ERROR,
|
||||||
|
is_primary: true,
|
||||||
|
message: "syntax error a1".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
project
|
||||||
|
.update_diagnostic_entries(
|
||||||
|
LanguageServerId(1),
|
||||||
|
Path::new("/dir/a.rs").to_owned(),
|
||||||
|
None,
|
||||||
|
vec![DiagnosticEntry {
|
||||||
|
range: Unclipped(PointUtf16::new(0, 0))..Unclipped(PointUtf16::new(0, 3)),
|
||||||
|
diagnostic: Diagnostic {
|
||||||
|
severity: DiagnosticSeverity::ERROR,
|
||||||
|
is_primary: true,
|
||||||
|
message: "syntax error b1".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
project.diagnostic_summary(cx),
|
||||||
|
DiagnosticSummary {
|
||||||
|
error_count: 2,
|
||||||
|
warning_count: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
|
async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
|
@ -1573,6 +1633,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||||
new_text: "".into(),
|
new_text: "".into(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
LanguageServerId(0),
|
||||||
Some(lsp_document_version),
|
Some(lsp_document_version),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1667,6 +1728,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
|
||||||
new_text: "".into(),
|
new_text: "".into(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
LanguageServerId(0),
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -1770,6 +1832,7 @@ async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
|
||||||
.unindent(),
|
.unindent(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
LanguageServerId(0),
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -2258,7 +2321,7 @@ async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
tree_sitter_rust::language(),
|
tree_sitter_rust::language(),
|
||||||
None,
|
vec![],
|
||||||
|_| Default::default(),
|
|_| Default::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2948,7 +3011,9 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||||
};
|
};
|
||||||
|
|
||||||
project
|
project
|
||||||
.update(cx, |p, cx| p.update_diagnostics(0, message, &[], cx))
|
.update(cx, |p, cx| {
|
||||||
|
p.update_diagnostics(LanguageServerId(0), message, &[], cx)
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
let buffer = buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ use language::{
|
||||||
},
|
},
|
||||||
Buffer, DiagnosticEntry, File as _, PointUtf16, Rope, RopeFingerprint, Unclipped,
|
Buffer, DiagnosticEntry, File as _, PointUtf16, Rope, RopeFingerprint, Unclipped,
|
||||||
};
|
};
|
||||||
|
use lsp::LanguageServerId;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::{
|
use postage::{
|
||||||
barrier,
|
barrier,
|
||||||
|
@ -50,7 +51,7 @@ use std::{
|
||||||
},
|
},
|
||||||
time::{Duration, SystemTime},
|
time::{Duration, SystemTime},
|
||||||
};
|
};
|
||||||
use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet};
|
use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeSet};
|
||||||
use util::{paths::HOME, ResultExt, TryFutureExt};
|
use util::{paths::HOME, ResultExt, TryFutureExt};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||||
|
@ -67,8 +68,14 @@ pub struct LocalWorktree {
|
||||||
is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
|
is_scanning: (watch::Sender<bool>, watch::Receiver<bool>),
|
||||||
_background_scanner_task: Task<()>,
|
_background_scanner_task: Task<()>,
|
||||||
share: Option<ShareState>,
|
share: Option<ShareState>,
|
||||||
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>>,
|
diagnostics: HashMap<
|
||||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
Arc<Path>,
|
||||||
|
Vec<(
|
||||||
|
LanguageServerId,
|
||||||
|
Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
|
@ -82,7 +89,7 @@ pub struct RemoteWorktree {
|
||||||
updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
|
updates_tx: Option<UnboundedSender<proto::UpdateWorktree>>,
|
||||||
snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
|
snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>,
|
||||||
replica_id: ReplicaId,
|
replica_id: ReplicaId,
|
||||||
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
|
diagnostic_summaries: HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>,
|
||||||
visible: bool,
|
visible: bool,
|
||||||
disconnected: bool,
|
disconnected: bool,
|
||||||
}
|
}
|
||||||
|
@ -463,13 +470,17 @@ impl Worktree {
|
||||||
|
|
||||||
pub fn diagnostic_summaries(
|
pub fn diagnostic_summaries(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = (Arc<Path>, DiagnosticSummary)> + '_ {
|
) -> impl Iterator<Item = (Arc<Path>, LanguageServerId, DiagnosticSummary)> + '_ {
|
||||||
match self {
|
match self {
|
||||||
Worktree::Local(worktree) => &worktree.diagnostic_summaries,
|
Worktree::Local(worktree) => &worktree.diagnostic_summaries,
|
||||||
Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
|
Worktree::Remote(worktree) => &worktree.diagnostic_summaries,
|
||||||
}
|
}
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(path, summary)| (path.0.clone(), *summary))
|
.flat_map(|(path, summaries)| {
|
||||||
|
summaries
|
||||||
|
.iter()
|
||||||
|
.map(move |(&server_id, &summary)| (path.clone(), server_id, summary))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abs_path(&self) -> Arc<Path> {
|
pub fn abs_path(&self) -> Arc<Path> {
|
||||||
|
@ -514,31 +525,54 @@ impl LocalWorktree {
|
||||||
pub fn diagnostics_for_path(
|
pub fn diagnostics_for_path(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Option<Vec<DiagnosticEntry<Unclipped<PointUtf16>>>> {
|
) -> Vec<(
|
||||||
self.diagnostics.get(path).cloned()
|
LanguageServerId,
|
||||||
|
Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||||
|
)> {
|
||||||
|
self.diagnostics.get(path).cloned().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
language_server_id: usize,
|
server_id: LanguageServerId,
|
||||||
worktree_path: Arc<Path>,
|
worktree_path: Arc<Path>,
|
||||||
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
|
||||||
_: &mut ModelContext<Worktree>,
|
_: &mut ModelContext<Worktree>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
self.diagnostics.remove(&worktree_path);
|
let summaries_by_server_id = self
|
||||||
let old_summary = self
|
|
||||||
.diagnostic_summaries
|
.diagnostic_summaries
|
||||||
.remove(&PathKey(worktree_path.clone()))
|
.entry(worktree_path.clone())
|
||||||
|
.or_default();
|
||||||
|
|
||||||
|
let old_summary = summaries_by_server_id
|
||||||
|
.remove(&server_id)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let new_summary = DiagnosticSummary::new(language_server_id, &diagnostics);
|
|
||||||
if !new_summary.is_empty() {
|
let new_summary = DiagnosticSummary::new(&diagnostics);
|
||||||
self.diagnostic_summaries
|
if new_summary.is_empty() {
|
||||||
.insert(PathKey(worktree_path.clone()), new_summary);
|
if let Some(diagnostics_by_server_id) = self.diagnostics.get_mut(&worktree_path) {
|
||||||
self.diagnostics.insert(worktree_path.clone(), diagnostics);
|
if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
|
||||||
|
diagnostics_by_server_id.remove(ix);
|
||||||
|
}
|
||||||
|
if diagnostics_by_server_id.is_empty() {
|
||||||
|
self.diagnostics.remove(&worktree_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
summaries_by_server_id.insert(server_id, new_summary);
|
||||||
|
let diagnostics_by_server_id =
|
||||||
|
self.diagnostics.entry(worktree_path.clone()).or_default();
|
||||||
|
match diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
|
||||||
|
Ok(ix) => {
|
||||||
|
diagnostics_by_server_id[ix] = (server_id, diagnostics);
|
||||||
|
}
|
||||||
|
Err(ix) => {
|
||||||
|
diagnostics_by_server_id.insert(ix, (server_id, diagnostics));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let updated = !old_summary.is_empty() || !new_summary.is_empty();
|
if !old_summary.is_empty() || !new_summary.is_empty() {
|
||||||
if updated {
|
|
||||||
if let Some(share) = self.share.as_ref() {
|
if let Some(share) = self.share.as_ref() {
|
||||||
self.client
|
self.client
|
||||||
.send(proto::UpdateDiagnosticSummary {
|
.send(proto::UpdateDiagnosticSummary {
|
||||||
|
@ -546,7 +580,7 @@ impl LocalWorktree {
|
||||||
worktree_id: self.id().to_proto(),
|
worktree_id: self.id().to_proto(),
|
||||||
summary: Some(proto::DiagnosticSummary {
|
summary: Some(proto::DiagnosticSummary {
|
||||||
path: worktree_path.to_string_lossy().to_string(),
|
path: worktree_path.to_string_lossy().to_string(),
|
||||||
language_server_id: language_server_id as u64,
|
language_server_id: server_id.0 as u64,
|
||||||
error_count: new_summary.error_count as u32,
|
error_count: new_summary.error_count as u32,
|
||||||
warning_count: new_summary.warning_count as u32,
|
warning_count: new_summary.warning_count as u32,
|
||||||
}),
|
}),
|
||||||
|
@ -555,7 +589,7 @@ impl LocalWorktree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(updated)
|
Ok(!old_summary.is_empty() || !new_summary.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
|
fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
|
||||||
|
@ -945,13 +979,15 @@ impl LocalWorktree {
|
||||||
let (resume_updates_tx, mut resume_updates_rx) = watch::channel();
|
let (resume_updates_tx, mut resume_updates_rx) = watch::channel();
|
||||||
let worktree_id = cx.model_id() as u64;
|
let worktree_id = cx.model_id() as u64;
|
||||||
|
|
||||||
for (path, summary) in self.diagnostic_summaries.iter() {
|
for (path, summaries) in &self.diagnostic_summaries {
|
||||||
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
|
for (&server_id, summary) in summaries {
|
||||||
project_id,
|
if let Err(e) = self.client.send(proto::UpdateDiagnosticSummary {
|
||||||
worktree_id,
|
project_id,
|
||||||
summary: Some(summary.to_proto(&path.0)),
|
worktree_id,
|
||||||
}) {
|
summary: Some(summary.to_proto(server_id, &path)),
|
||||||
return Task::ready(Err(e));
|
}) {
|
||||||
|
return Task::ready(Err(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1109,15 +1145,24 @@ impl RemoteWorktree {
|
||||||
path: Arc<Path>,
|
path: Arc<Path>,
|
||||||
summary: &proto::DiagnosticSummary,
|
summary: &proto::DiagnosticSummary,
|
||||||
) {
|
) {
|
||||||
|
let server_id = LanguageServerId(summary.language_server_id as usize);
|
||||||
let summary = DiagnosticSummary {
|
let summary = DiagnosticSummary {
|
||||||
language_server_id: summary.language_server_id as usize,
|
|
||||||
error_count: summary.error_count as usize,
|
error_count: summary.error_count as usize,
|
||||||
warning_count: summary.warning_count as usize,
|
warning_count: summary.warning_count as usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
if summary.is_empty() {
|
if summary.is_empty() {
|
||||||
self.diagnostic_summaries.remove(&PathKey(path));
|
if let Some(summaries) = self.diagnostic_summaries.get_mut(&path) {
|
||||||
|
summaries.remove(&server_id);
|
||||||
|
if summaries.is_empty() {
|
||||||
|
self.diagnostic_summaries.remove(&path);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.diagnostic_summaries.insert(PathKey(path), summary);
|
self.diagnostic_summaries
|
||||||
|
.entry(path)
|
||||||
|
.or_default()
|
||||||
|
.insert(server_id, summary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ path = "src/rope.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
|
bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
arrayvec = "0.7.1"
|
arrayvec = "0.7.1"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
|
@ -684,9 +684,10 @@ message SearchProjectResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
message CodeAction {
|
message CodeAction {
|
||||||
Anchor start = 1;
|
uint64 server_id = 1;
|
||||||
Anchor end = 2;
|
Anchor start = 2;
|
||||||
bytes lsp_action = 3;
|
Anchor end = 3;
|
||||||
|
bytes lsp_action = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ProjectTransaction {
|
message ProjectTransaction {
|
||||||
|
@ -860,7 +861,8 @@ message IncomingContactRequest {
|
||||||
message UpdateDiagnostics {
|
message UpdateDiagnostics {
|
||||||
uint32 replica_id = 1;
|
uint32 replica_id = 1;
|
||||||
uint32 lamport_timestamp = 2;
|
uint32 lamport_timestamp = 2;
|
||||||
repeated Diagnostic diagnostics = 3;
|
uint64 server_id = 3;
|
||||||
|
repeated Diagnostic diagnostics = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Follow {
|
message Follow {
|
||||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 51;
|
pub const PROTOCOL_VERSION: u32 = 52;
|
||||||
|
|
|
@ -25,7 +25,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2"
|
smol = "1.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -10,4 +10,4 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -17,7 +17,7 @@ theme = { path = "../theme" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
|
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a51dbe25d67e84d6ed4261e640d3954fbdd9be45" }
|
||||||
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
mio-extras = "2.0.6"
|
mio-extras = "2.0.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
|
@ -21,7 +21,7 @@ workspace = { path = "../workspace" }
|
||||||
db = { path = "../db" }
|
db = { path = "../db" }
|
||||||
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
|
||||||
terminal = { path = "../terminal" }
|
terminal = { path = "../terminal" }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
mio-extras = "2.0.6"
|
mio-extras = "2.0.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
|
@ -24,7 +24,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
postage = { workspace = true }
|
postage = { workspace = true }
|
||||||
rand = { version = "0.8.3", optional = true }
|
rand = { version = "0.8.3", optional = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,4 @@ settings = { path = "../settings" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
project = { path = "../project" }
|
project = { path = "../project" }
|
||||||
|
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
|
|
|
@ -47,7 +47,7 @@ postage = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_derive = { workspace = true }
|
serde_derive = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
indoc = "1.0.4"
|
indoc = "1.0.4"
|
||||||
uuid = { version = "1.1.2", features = ["v4"] }
|
uuid = { version = "1.1.2", features = ["v4"] }
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.83.0"
|
version = "0.84.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -96,7 +96,7 @@ serde_derive = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_path_to_error = "0.1.4"
|
serde_path_to_error = "0.1.4"
|
||||||
simplelog = "0.9"
|
simplelog = "0.9"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { workspace = true }
|
||||||
smol = "1.2.5"
|
smol = "1.2.5"
|
||||||
tempdir = { version = "0.3.7" }
|
tempdir = { version = "0.3.7" }
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
|
|
|
@ -37,121 +37,107 @@ pub fn init(
|
||||||
themes: Arc<ThemeRegistry>,
|
themes: Arc<ThemeRegistry>,
|
||||||
node_runtime: Arc<NodeRuntime>,
|
node_runtime: Arc<NodeRuntime>,
|
||||||
) {
|
) {
|
||||||
for (name, grammar, lsp_adapter) in [
|
fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
|
||||||
|
Arc::new(adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
let languages_list = [
|
||||||
(
|
(
|
||||||
"c",
|
"c",
|
||||||
tree_sitter_c::language(),
|
tree_sitter_c::language(),
|
||||||
Some(Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>),
|
vec![adapter_arc(c::CLspAdapter)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"cpp",
|
"cpp",
|
||||||
tree_sitter_cpp::language(),
|
tree_sitter_cpp::language(),
|
||||||
Some(Arc::new(c::CLspAdapter)),
|
vec![adapter_arc(c::CLspAdapter)],
|
||||||
),
|
|
||||||
(
|
|
||||||
"css",
|
|
||||||
tree_sitter_css::language(),
|
|
||||||
None, //
|
|
||||||
),
|
),
|
||||||
|
("css", tree_sitter_css::language(), vec![]),
|
||||||
(
|
(
|
||||||
"elixir",
|
"elixir",
|
||||||
tree_sitter_elixir::language(),
|
tree_sitter_elixir::language(),
|
||||||
Some(Arc::new(elixir::ElixirLspAdapter)),
|
vec![adapter_arc(elixir::ElixirLspAdapter)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"go",
|
"go",
|
||||||
tree_sitter_go::language(),
|
tree_sitter_go::language(),
|
||||||
Some(Arc::new(go::GoLspAdapter)),
|
vec![adapter_arc(go::GoLspAdapter)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"json",
|
"json",
|
||||||
tree_sitter_json::language(),
|
tree_sitter_json::language(),
|
||||||
Some(Arc::new(json::JsonLspAdapter::new(
|
vec![adapter_arc(json::JsonLspAdapter::new(
|
||||||
node_runtime.clone(),
|
node_runtime.clone(),
|
||||||
languages.clone(),
|
languages.clone(),
|
||||||
themes.clone(),
|
themes.clone(),
|
||||||
))),
|
))],
|
||||||
),
|
|
||||||
(
|
|
||||||
"markdown",
|
|
||||||
tree_sitter_markdown::language(),
|
|
||||||
None, //
|
|
||||||
),
|
),
|
||||||
|
("markdown", tree_sitter_markdown::language(), vec![]),
|
||||||
(
|
(
|
||||||
"python",
|
"python",
|
||||||
tree_sitter_python::language(),
|
tree_sitter_python::language(),
|
||||||
Some(Arc::new(python::PythonLspAdapter::new(
|
vec![adapter_arc(python::PythonLspAdapter::new(
|
||||||
node_runtime.clone(),
|
node_runtime.clone(),
|
||||||
))),
|
))],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"rust",
|
"rust",
|
||||||
tree_sitter_rust::language(),
|
tree_sitter_rust::language(),
|
||||||
Some(Arc::new(rust::RustLspAdapter)),
|
vec![adapter_arc(rust::RustLspAdapter)],
|
||||||
),
|
|
||||||
(
|
|
||||||
"toml",
|
|
||||||
tree_sitter_toml::language(),
|
|
||||||
None, //
|
|
||||||
),
|
),
|
||||||
|
("toml", tree_sitter_toml::language(), vec![]),
|
||||||
(
|
(
|
||||||
"tsx",
|
"tsx",
|
||||||
tree_sitter_typescript::language_tsx(),
|
tree_sitter_typescript::language_tsx(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
|
||||||
node_runtime.clone(),
|
node_runtime.clone(),
|
||||||
))),
|
))],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"typescript",
|
"typescript",
|
||||||
tree_sitter_typescript::language_typescript(),
|
tree_sitter_typescript::language_typescript(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
|
||||||
node_runtime.clone(),
|
node_runtime.clone(),
|
||||||
))),
|
))],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"javascript",
|
"javascript",
|
||||||
tree_sitter_typescript::language_tsx(),
|
tree_sitter_typescript::language_tsx(),
|
||||||
Some(Arc::new(typescript::TypeScriptLspAdapter::new(
|
vec![adapter_arc(typescript::TypeScriptLspAdapter::new(
|
||||||
node_runtime.clone(),
|
node_runtime.clone(),
|
||||||
))),
|
))],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"html",
|
"html",
|
||||||
tree_sitter_html::language(),
|
tree_sitter_html::language(),
|
||||||
Some(Arc::new(html::HtmlLspAdapter::new(node_runtime.clone()))),
|
vec![adapter_arc(html::HtmlLspAdapter::new(node_runtime.clone()))],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"ruby",
|
"ruby",
|
||||||
tree_sitter_ruby::language(),
|
tree_sitter_ruby::language(),
|
||||||
Some(Arc::new(ruby::RubyLanguageServer)),
|
vec![adapter_arc(ruby::RubyLanguageServer)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"erb",
|
"erb",
|
||||||
tree_sitter_embedded_template::language(),
|
tree_sitter_embedded_template::language(),
|
||||||
Some(Arc::new(ruby::RubyLanguageServer)),
|
vec![adapter_arc(ruby::RubyLanguageServer)],
|
||||||
),
|
|
||||||
(
|
|
||||||
"scheme",
|
|
||||||
tree_sitter_scheme::language(),
|
|
||||||
None, //
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"racket",
|
|
||||||
tree_sitter_racket::language(),
|
|
||||||
None, //
|
|
||||||
),
|
),
|
||||||
|
("scheme", tree_sitter_scheme::language(), vec![]),
|
||||||
|
("racket", tree_sitter_racket::language(), vec![]),
|
||||||
(
|
(
|
||||||
"lua",
|
"lua",
|
||||||
tree_sitter_lua::language(),
|
tree_sitter_lua::language(),
|
||||||
Some(Arc::new(lua::LuaLspAdapter)),
|
vec![adapter_arc(lua::LuaLspAdapter)],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"yaml",
|
"yaml",
|
||||||
tree_sitter_yaml::language(),
|
tree_sitter_yaml::language(),
|
||||||
Some(Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))),
|
vec![adapter_arc(yaml::YamlLspAdapter::new(node_runtime.clone()))],
|
||||||
),
|
),
|
||||||
] {
|
];
|
||||||
languages.register(name, load_config(name), grammar, lsp_adapter, load_queries);
|
|
||||||
|
for (name, grammar, lsp_adapters) in languages_list {
|
||||||
|
languages.register(name, load_config(name), grammar, lsp_adapters, load_queries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +149,7 @@ pub async fn language(
|
||||||
) -> Arc<Language> {
|
) -> Arc<Language> {
|
||||||
Arc::new(
|
Arc::new(
|
||||||
Language::new(load_config(name), Some(grammar))
|
Language::new(load_config(name), Some(grammar))
|
||||||
.with_lsp_adapter(lsp_adapter)
|
.with_lsp_adapters(lsp_adapter.into_iter().collect())
|
||||||
.await
|
.await
|
||||||
.with_queries(load_queries(name))
|
.with_queries(load_queries(name))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::StreamExt;
|
use futures::{future::BoxFuture, FutureExt};
|
||||||
|
use gpui::AppContext;
|
||||||
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
|
||||||
use lsp::CodeActionKind;
|
use lsp::CodeActionKind;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use serde_json::json;
|
use serde_json::{json, Value};
|
||||||
use smol::fs;
|
use smol::fs;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
|
future,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use util::http::HttpClient;
|
use util::http::HttpClient;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
vec![
|
vec![
|
||||||
server_path.into(),
|
server_path.into(),
|
||||||
"--stdio".into(),
|
"--stdio".into(),
|
||||||
|
@ -24,6 +26,10 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||||
|
vec![server_path.into(), "--stdio".into()]
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TypeScriptLspAdapter {
|
pub struct TypeScriptLspAdapter {
|
||||||
node: Arc<NodeRuntime>,
|
node: Arc<NodeRuntime>,
|
||||||
}
|
}
|
||||||
|
@ -37,7 +43,7 @@ impl TypeScriptLspAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Versions {
|
struct TypeScriptVersions {
|
||||||
typescript_version: String,
|
typescript_version: String,
|
||||||
server_version: String,
|
server_version: String,
|
||||||
}
|
}
|
||||||
|
@ -52,7 +58,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
&self,
|
&self,
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
Ok(Box::new(Versions {
|
Ok(Box::new(TypeScriptVersions {
|
||||||
typescript_version: self.node.npm_package_latest_version("typescript").await?,
|
typescript_version: self.node.npm_package_latest_version("typescript").await?,
|
||||||
server_version: self
|
server_version: self
|
||||||
.node
|
.node
|
||||||
|
@ -67,7 +73,7 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
_: Arc<dyn HttpClient>,
|
_: Arc<dyn HttpClient>,
|
||||||
container_dir: PathBuf,
|
container_dir: PathBuf,
|
||||||
) -> Result<LanguageServerBinary> {
|
) -> Result<LanguageServerBinary> {
|
||||||
let versions = versions.downcast::<Versions>().unwrap();
|
let versions = versions.downcast::<TypeScriptVersions>().unwrap();
|
||||||
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
||||||
|
|
||||||
if fs::metadata(&server_path).await.is_err() {
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
@ -87,37 +93,28 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
|
|
||||||
Ok(LanguageServerBinary {
|
Ok(LanguageServerBinary {
|
||||||
path: self.node.binary_path().await?,
|
path: self.node.binary_path().await?,
|
||||||
arguments: server_binary_arguments(&server_path),
|
arguments: typescript_server_binary_arguments(&server_path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
(|| async move {
|
(|| async move {
|
||||||
let mut last_version_dir = None;
|
let old_server_path = container_dir.join(Self::OLD_SERVER_PATH);
|
||||||
let mut entries = fs::read_dir(&container_dir).await?;
|
let new_server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
||||||
while let Some(entry) = entries.next().await {
|
|
||||||
let entry = entry?;
|
|
||||||
if entry.file_type().await?.is_dir() {
|
|
||||||
last_version_dir = Some(entry.path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
|
|
||||||
let old_server_path = last_version_dir.join(Self::OLD_SERVER_PATH);
|
|
||||||
let new_server_path = last_version_dir.join(Self::NEW_SERVER_PATH);
|
|
||||||
if new_server_path.exists() {
|
if new_server_path.exists() {
|
||||||
Ok(LanguageServerBinary {
|
Ok(LanguageServerBinary {
|
||||||
path: self.node.binary_path().await?,
|
path: self.node.binary_path().await?,
|
||||||
arguments: server_binary_arguments(&new_server_path),
|
arguments: typescript_server_binary_arguments(&new_server_path),
|
||||||
})
|
})
|
||||||
} else if old_server_path.exists() {
|
} else if old_server_path.exists() {
|
||||||
Ok(LanguageServerBinary {
|
Ok(LanguageServerBinary {
|
||||||
path: self.node.binary_path().await?,
|
path: self.node.binary_path().await?,
|
||||||
arguments: server_binary_arguments(&old_server_path),
|
arguments: typescript_server_binary_arguments(&old_server_path),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"missing executable in directory {:?}",
|
"missing executable in directory {:?}",
|
||||||
last_version_dir
|
container_dir
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -170,6 +167,136 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct EsLintLspAdapter {
|
||||||
|
node: Arc<NodeRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EsLintLspAdapter {
|
||||||
|
const SERVER_PATH: &'static str =
|
||||||
|
"node_modules/vscode-langservers-extracted/lib/eslint-language-server/eslintServer.js";
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn new(node: Arc<NodeRuntime>) -> Self {
|
||||||
|
EsLintLspAdapter { node }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for EsLintLspAdapter {
|
||||||
|
fn workspace_configuration(&self, _: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
|
||||||
|
Some(
|
||||||
|
future::ready(json!({
|
||||||
|
"": {
|
||||||
|
"validate": "on",
|
||||||
|
"packageManager": "npm",
|
||||||
|
"useESLintClass": false,
|
||||||
|
"experimental": {
|
||||||
|
"useFlatConfig": false
|
||||||
|
},
|
||||||
|
"codeActionOnSave": {
|
||||||
|
"mode": "all"
|
||||||
|
},
|
||||||
|
"format": false,
|
||||||
|
"quiet": false,
|
||||||
|
"onIgnoredFiles": "off",
|
||||||
|
"options": {},
|
||||||
|
"rulesCustomizations": [],
|
||||||
|
"run": "onType",
|
||||||
|
"problems": {
|
||||||
|
"shortenToSingleLine": false
|
||||||
|
},
|
||||||
|
"nodePath": null,
|
||||||
|
"workspaceFolder": {
|
||||||
|
"name": "testing_ts",
|
||||||
|
"uri": "file:///Users/julia/Stuff/testing_ts"
|
||||||
|
},
|
||||||
|
"codeAction": {
|
||||||
|
"disableRuleComment": {
|
||||||
|
"enable": true,
|
||||||
|
"location": "separateLine",
|
||||||
|
"commentStyle": "line"
|
||||||
|
},
|
||||||
|
"showDocumentation": {
|
||||||
|
"enable": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("eslint".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
_: Arc<dyn HttpClient>,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
Ok(Box::new(
|
||||||
|
self.node
|
||||||
|
.npm_package_latest_version("vscode-langservers-extracted")
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
versions: Box<dyn 'static + Send + Any>,
|
||||||
|
_: Arc<dyn HttpClient>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = versions.downcast::<String>().unwrap();
|
||||||
|
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||||
|
|
||||||
|
if fs::metadata(&server_path).await.is_err() {
|
||||||
|
self.node
|
||||||
|
.npm_install_packages(
|
||||||
|
[("vscode-langservers-extracted", version.as_str())],
|
||||||
|
&container_dir,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: eslint_server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(&self, container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
(|| async move {
|
||||||
|
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||||
|
if server_path.exists() {
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: self.node.binary_path().await?,
|
||||||
|
arguments: eslint_server_binary_arguments(&server_path),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!(
|
||||||
|
"missing executable in directory {:?}",
|
||||||
|
container_dir
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn label_for_completion(
|
||||||
|
&self,
|
||||||
|
_item: &lsp::CompletionItem,
|
||||||
|
_language: &Arc<language::Language>,
|
||||||
|
) -> Option<language::CodeLabel> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
|
|
|
@ -21,7 +21,7 @@ use log::LevelFilter;
|
||||||
use node_runtime::NodeRuntime;
|
use node_runtime::NodeRuntime;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Fs;
|
use project::Fs;
|
||||||
use serde_json::json;
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::{
|
use settings::{
|
||||||
self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
|
self, settings_file::SettingsFile, KeymapFileContent, Settings, SettingsFileContent,
|
||||||
WorkingDirectory,
|
WorkingDirectory,
|
||||||
|
@ -317,6 +317,30 @@ fn init_logger() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct LocationData {
|
||||||
|
file: String,
|
||||||
|
line: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct Panic {
|
||||||
|
thread: String,
|
||||||
|
payload: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
location_data: Option<LocationData>,
|
||||||
|
backtrace: Vec<String>,
|
||||||
|
// TODO
|
||||||
|
// stripped_backtrace: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct PanicRequest {
|
||||||
|
panic: Panic,
|
||||||
|
version: String,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn init_panic_hook(app_version: String) {
|
fn init_panic_hook(app_version: String) {
|
||||||
let is_pty = stdout_is_a_pty();
|
let is_pty = stdout_is_a_pty();
|
||||||
panic::set_hook(Box::new(move |info| {
|
panic::set_hook(Box::new(move |info| {
|
||||||
|
@ -333,39 +357,38 @@ fn init_panic_hook(app_version: String) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let message = match info.location() {
|
let panic_data = Panic {
|
||||||
Some(location) => {
|
thread: thread.into(),
|
||||||
format!(
|
payload: payload.into(),
|
||||||
"thread '{}' panicked at '{}'\n{}:{}\n{:?}",
|
location_data: info.location().map(|location| LocationData {
|
||||||
thread,
|
file: location.file().into(),
|
||||||
payload,
|
line: location.line(),
|
||||||
location.file(),
|
}),
|
||||||
location.line(),
|
backtrace: format!("{:?}", backtrace)
|
||||||
backtrace
|
.split("\n")
|
||||||
)
|
.map(|line| line.to_string())
|
||||||
}
|
.collect(),
|
||||||
None => format!(
|
// modified_backtrace: None,
|
||||||
"thread '{}' panicked at '{}'\n{:?}",
|
|
||||||
thread, payload, backtrace
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_pty {
|
if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
|
||||||
eprintln!("{}", message);
|
if is_pty {
|
||||||
return;
|
eprintln!("{}", panic_data_json);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
|
let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
|
||||||
let panic_file_path =
|
let panic_file_path =
|
||||||
paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, timestamp));
|
paths::LOGS_DIR.join(format!("zed-{}-{}.panic", app_version, timestamp));
|
||||||
let panic_file = std::fs::OpenOptions::new()
|
let panic_file = std::fs::OpenOptions::new()
|
||||||
.append(true)
|
.append(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(&panic_file_path)
|
.open(&panic_file_path)
|
||||||
.log_err();
|
.log_err();
|
||||||
if let Some(mut panic_file) = panic_file {
|
if let Some(mut panic_file) = panic_file {
|
||||||
write!(&mut panic_file, "{}", message).log_err();
|
write!(&mut panic_file, "{}", panic_data_json).log_err();
|
||||||
panic_file.flush().log_err();
|
panic_file.flush().log_err();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -402,15 +425,17 @@ fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if diagnostics_telemetry {
|
if diagnostics_telemetry {
|
||||||
let text = smol::fs::read_to_string(&child_path)
|
let panic_data_text = smol::fs::read_to_string(&child_path)
|
||||||
.await
|
.await
|
||||||
.context("error reading panic file")?;
|
.context("error reading panic file")?;
|
||||||
let body = serde_json::to_string(&json!({
|
|
||||||
"text": text,
|
let body = serde_json::to_string(&PanicRequest {
|
||||||
"version": version,
|
panic: serde_json::from_str(&panic_data_text)?,
|
||||||
"token": ZED_SECRET_CLIENT_TOKEN,
|
version: version.to_string(),
|
||||||
}))
|
token: ZED_SECRET_CLIENT_TOKEN.into(),
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let request = Request::post(&panic_report_url)
|
let request = Request::post(&panic_report_url)
|
||||||
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
.redirect_policy(isahc::config::RedirectPolicy::Follow)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue