Compare commits

..

4 Commits

Author SHA1 Message Date
Conrad Irwin
2cede52c87 sp 2024-07-08 20:02:19 -06:00
Thorsten Ball
fd8c0d78d4 linux/x11: Check for GTK_FRAME_EXTENTS atom as CSD support 2024-07-05 17:21:45 +02:00
Thorsten Ball
ca72b4aea2 linux/x11: Move compositor check to client, make more extensive 2024-07-05 16:01:16 +02:00
Sebastijan Kelnerič
f1cc51081d Check for compositor support 2024-07-04 11:55:54 +02:00
230 changed files with 8706 additions and 8780 deletions

View File

@@ -1,13 +1,6 @@
.git
.github
**/.gitignore
**/.gitkeep
.gitattributes
.mailmap
**/target
zed.xcworkspace
.DS_Store
compose.yml
plugins/bin
script/node_modules
styles/node_modules

View File

@@ -32,10 +32,9 @@ body:
required: false
- type: textarea
attributes:
label: If applicable, attach your Zed.log file to this issue.
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
description: |
macOS: `~/Library/Logs/Zed/Zed.log`
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
Drag Zed.log into the text input below.
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
value: |
<details><summary>Zed.log</summary><pre>

View File

@@ -2,7 +2,7 @@
Release Notes:
- Added/Fixed/Improved ... ([#NNNNN](https://github.com/zed-industries/zed/issues/NNNNN)).
- Added/Fixed/Improved ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/zed/issues/<public_issue_number_if_exists>)).
Optionally, include screenshots / media showcasing your addition that can be included in the release notes.

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
/.direnv
.idea
**/target
**/cargo-target

View File

@@ -14,12 +14,6 @@
},
"JSON": {
"tab_size": 2,
"preferred_line_length": 100,
"formatter": "prettier"
},
"JSONC": {
"tab_size": 2,
"preferred_line_length": 100,
"formatter": "prettier"
},
"JavaScript": {

89
Cargo.lock generated
View File

@@ -341,8 +341,9 @@ dependencies = [
[[package]]
name = "ashpd"
version = "0.9.0"
source = "git+https://github.com/bilelmoussaoui/ashpd?rev=29f2e1a#29f2e1a6f4b0911f504658f5f4630c02e01b13f2"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093"
dependencies = [
"async-fs 2.1.1",
"async-net 2.0.0",
@@ -3932,7 +3933,6 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
"snippet_provider",
"task",
"theme",
"toml 0.8.10",
@@ -4889,9 +4889,11 @@ dependencies = [
"log",
"media",
"metal",
"mio 1.0.0",
"num_cpus",
"objc",
"oo7",
"open",
"parking",
"parking_lot",
"pathfinder_geometry",
@@ -5689,7 +5691,7 @@ dependencies = [
"fnv",
"lazy_static",
"libc",
"mio",
"mio 0.8.11",
"rand 0.8.5",
"serde",
"tempfile",
@@ -5703,6 +5705,25 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]]
name = "isahc"
version = "1.7.2"
@@ -6619,6 +6640,19 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4929e1f84c5e54c3ec6141cd5d8b5a5c055f031f80cf78f2072920173cb4d880"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "miow"
version = "0.6.0"
@@ -6857,7 +6891,7 @@ dependencies = [
"kqueue",
"libc",
"log",
"mio",
"mio 0.8.11",
"walkdir",
"windows-sys 0.48.0",
]
@@ -7191,6 +7225,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "open"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449f0ff855d85ddbf1edd5b646d65249ead3f5e422aaa86b7d2d0b049b103e32"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]]
name = "open_ai"
version = "0.1.0"
@@ -7356,7 +7401,6 @@ dependencies = [
"db",
"editor",
"file_icons",
"fuzzy",
"gpui",
"itertools 0.11.0",
"language",
@@ -8042,7 +8086,6 @@ dependencies = [
"similar",
"smol",
"snippet",
"snippet_provider",
"task",
"tempfile",
"terminal",
@@ -8496,7 +8539,7 @@ dependencies = [
"task",
"terminal_view",
"ui",
"ui_input",
"ui_text_field",
"util",
"workspace",
]
@@ -9859,22 +9902,6 @@ dependencies = [
"smallvec",
]
[[package]]
name = "snippet_provider"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"fs",
"futures 0.3.28",
"gpui",
"parking_lot",
"serde",
"serde_json",
"snippet",
"util",
]
[[package]]
name = "socket2"
version = "0.4.9"
@@ -10255,7 +10282,6 @@ dependencies = [
"story",
"strum",
"theme",
"title_bar",
"ui",
]
@@ -11086,7 +11112,7 @@ dependencies = [
"backtrace",
"bytes 1.5.0",
"libc",
"mio",
"mio 0.8.11",
"num_cpus",
"parking_lot",
"pin-project-lite",
@@ -11711,7 +11737,7 @@ dependencies = [
]
[[package]]
name = "ui_input"
name = "ui_text_field"
version = "0.1.0"
dependencies = [
"editor",
@@ -13656,7 +13682,6 @@ dependencies = [
"settings",
"simplelog",
"smol",
"snippet_provider",
"supermaven",
"tab_switcher",
"task",
@@ -13695,9 +13720,9 @@ dependencies = [
[[package]]
name = "zed_clojure"
version = "0.0.3"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6",
"zed_extension_api 0.0.4",
]
[[package]]
@@ -13809,14 +13834,14 @@ dependencies = [
[[package]]
name = "zed_lua"
version = "0.0.3"
version = "0.0.2"
dependencies = [
"zed_extension_api 0.0.6",
]
[[package]]
name = "zed_ocaml"
version = "0.0.2"
version = "0.0.1"
dependencies = [
"zed_extension_api 0.0.6",
]

View File

@@ -88,7 +88,6 @@ members = [
"crates/semantic_version",
"crates/settings",
"crates/snippet",
"crates/snippet_provider",
"crates/sqlez",
"crates/sqlez_macros",
"crates/story",
@@ -109,7 +108,7 @@ members = [
"crates/time_format",
"crates/title_bar",
"crates/ui",
"crates/ui_input",
"crates/ui_text_field",
"crates/util",
"crates/vcs_menu",
"crates/vim",
@@ -240,7 +239,6 @@ semantic_index = { path = "crates/semantic_index" }
semantic_version = { path = "crates/semantic_version" }
settings = { path = "crates/settings" }
snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" }
sqlez = { path = "crates/sqlez" }
sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
@@ -261,7 +259,7 @@ theme_selector = { path = "crates/theme_selector" }
time_format = { path = "crates/time_format" }
title_bar = { path = "crates/title_bar" }
ui = { path = "crates/ui" }
ui_input = { path = "crates/ui_input" }
ui_text_field = { path = "crates/ui_text_field" }
util = { path = "crates/util" }
vcs_menu = { path = "crates/vcs_menu" }
vim = { path = "crates/vim" }
@@ -274,9 +272,9 @@ zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23"
any_vec = "0.13"
anyhow = "1.0.57"
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "29f2e1a" }
ashpd = "0.8.0"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = { version = "0.1" }
async-dispatcher = { version = "0.1"}
async-fs = "1.6"
async-recursion = "1.0.0"
async-tar = "0.4.2"
@@ -317,9 +315,7 @@ image = "0.25.1"
indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "1"
# We explicitly disable http2 support in isahc.
isahc = { version = "1.7.2", default-features = false, features = [
"text-decoding",
] }
isahc = { version = "1.7.2", default-features = false, features = [ "text-decoding" ] }
itertools = "0.11.0"
lazy_static = "1.4.0"
libc = "0.2"
@@ -345,9 +341,7 @@ rand = "0.8.5"
refineable = { path = "./crates/refineable" }
regex = "1.5"
repair_json = "0.1.0"
runtimelib = { version = "0.12", default-features = false, features = [
"async-dispatcher-runtime",
] }
runtimelib = { version="0.12", default-features = false, features = ["async-dispatcher-runtime"] }
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
schemars = "0.8"

View File

@@ -4,36 +4,42 @@
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).
--------
## Installation
### Installation
You can [download](https://zed.dev/download) Zed today for macOS (v10.15+).
Support for additional platforms is on our [roadmap](https://zed.dev/roadmap):
<a href="https://repology.org/project/zed-editor/versions">
<img src="https://repology.org/badge/vertical-allrepos/zed-editor.svg?minversion=0.143.5" alt="Packaging status" align="right">
</a>
On macOS and Linux you can [download Zed directly](https://zed.dev/download) or [install Zed via your local package manager](https://zed.dev/docs/linux#installing-via-a-package-manager).
Other platforms are not yet available:
- Linux ([tracking issue](https://github.com/zed-industries/zed/issues/7015))
- Windows ([tracking issue](https://github.com/zed-industries/zed/issues/5394))
- Web ([tracking issue](https://github.com/zed-industries/zed/issues/5396))
### Developing Zed
For macOS users, you can also install Zed using [Homebrew](https://brew.sh/):
```sh
brew install --cask zed
```
Alternatively, to install the Preview release:
```sh
brew install --cask zed@preview
```
## Developing Zed
- [Building Zed for macOS](./docs/src/development/macos.md)
- [Building Zed for Linux](./docs/src/development/linux.md)
- [Building Zed for Windows](./docs/src/development/windows.md)
- [Running Collaboration Locally](./docs/src/development/local-collaboration.md)
### Contributing
## Contributing
See [CONTRIBUTING.md](./CONTRIBUTING.md) for ways you can contribute to Zed.
Also... we're hiring! Check out our [jobs](https://zed.dev/jobs) page for open roles.
### Licensing
## Licensing
License information for third party dependencies must be correctly provided for CI to pass.

View File

@@ -1,8 +1,6 @@
// Default Keymap (Atom) for Zed on MacOS
[
{
"bindings": {
"ctrl-alt-cmd-l": "workspace::Reload",
"cmd-k cmd-p": "workspace::ActivatePreviousPane",
"cmd-k cmd-n": "workspace::ActivateNextPane"
}
@@ -10,22 +8,24 @@
{
"context": "Editor",
"bindings": {
"ctrl-shift-l": "language_selector::Toggle",
"cmd-|": "pane::RevealInProjectPanel",
"cmd-b": "editor::GoToDefinition",
"alt-cmd-b": "editor::GoToDefinitionSplit",
"cmd-<": "editor::ScrollCursorCenter",
"cmd-g": ["editor::SelectNext", { "replace_newest": true }],
"cmd-shift-g": ["editor::SelectPrevious", { "replace_newest": true }],
"cmd-g": [
"editor::SelectNext",
{
"replace_newest": true
}
],
"ctrl-cmd-g": [
"editor::SelectPrevious",
{
"replace_newest": true
}
],
"ctrl-shift-down": "editor::AddSelectionBelow",
"ctrl-shift-up": "editor::AddSelectionAbove",
"cmd-shift-backspace": "editor::DeleteToBeginningOfLine",
"cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase",
"alt-enter": "editor::Newline",
"cmd-shift-d": "editor::DuplicateLineDown",
"ctrl-cmd-up": "editor::MoveLineUp",
"ctrl-cmd-down": "editor::MoveLineDown",
"ctrl-shift-m": "markdown::OpenPreviewToTheSide"
}
},
@@ -74,22 +74,21 @@
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-x": "project_panel::Cut",
"cmd-c": "project_panel::Copy",
"cmd-v": "project_panel::Paste"
"cmd-v": "project_panel::Paste",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"alt-b": "project_panel::CollapseSelectedEntry",
"ctrl-]": "project_panel::ExpandSelectedEntry",
"ctrl-f": "project_panel::ExpandSelectedEntry",
"ctrl-shift-c": "project_panel::CopyPath"
}
},
{
"context": "ProjectPanel && not_editing",
"bindings": {
"ctrl-shift-c": "project_panel::CopyPath",
"ctrl-[": "project_panel::CollapseSelectedEntry",
"ctrl-b": "project_panel::CollapseSelectedEntry",
"ctrl-]": "project_panel::ExpandSelectedEntry",
"ctrl-f": "project_panel::ExpandSelectedEntry",
"a": "project_panel::NewFile",
"d": "project_panel::Duplicate",
"home": "menu::SelectFirst",
"end": "menu::SelectLast",
"shift-a": "project_panel::NewDirectory"
"shift-a": "project_panel::NewDirectory",
"shift-d": "project_panel::Duplicate"
}
}
]

View File

@@ -3,14 +3,10 @@
{
"bindings": {
"up": "menu::SelectPrev",
"shift-tab": "menu::SelectPrev",
"home": "menu::SelectFirst",
"pageup": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext",
"tab": "menu::SelectNext",
"end": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
@@ -45,10 +41,9 @@
"tab": "editor::Tab",
"shift-tab": "editor::TabPrev",
"ctrl-k": "editor::CutToEndOfLine",
// "ctrl-t": "editor::Transpose",
"ctrl-t": "editor::Transpose",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd",
"shift-delete": "editor::Cut",
"ctrl-x": "editor::Cut",
"ctrl-insert": "editor::Copy",
"ctrl-c": "editor::Copy",
@@ -88,19 +83,54 @@
"ctrl-a": "editor::SelectAll",
"ctrl-l": "editor::SelectLine",
"ctrl-shift-i": "editor::Format",
// "cmd-shift-left": ["editor::SelectToBeginningOfLine", {"stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
// "cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
// "alt-v": ["editor::MovePageUp", { "center_cursor": true }],
// "cmd-shift-left": [
// "editor::SelectToBeginningOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
"shift-home": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
// "ctrl-shift-a": [
// "editor::SelectToBeginningOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
// "cmd-shift-right": [
// "editor::SelectToEndOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
"shift-end": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
// "ctrl-shift-e": [
// "editor::SelectToEndOfLine",
// {
// "stop_at_soft_wraps": true
// }
// ],
// "alt-v": [
// "editor::MovePageUp",
// {
// "center_cursor": true
// }
// ],
"ctrl-alt-space": "editor::ShowCharacterPalette",
"ctrl-;": "editor::ToggleLineNumbers",
"ctrl-k ctrl-r": "editor::RevertSelectedHunks",
"ctrl-'": "editor::ToggleHunkDiff",
"ctrl-\"": "editor::ExpandAllHunkDiffs",
"alt-g b": "editor::ToggleGitBlame"
"ctrl-alt-g b": "editor::ToggleGitBlame"
}
},
{
@@ -112,8 +142,18 @@
"ctrl-enter": "editor::NewlineAbove",
"alt-z": "editor::ToggleSoftWrap",
"ctrl-f": "buffer_search::Deploy",
"ctrl-h": ["buffer_search::Deploy", { "replace_enabled": true }],
// "cmd-e": ["buffer_search::Deploy", { "focus": false }],
"ctrl-h": [
"buffer_search::Deploy",
{
"replace_enabled": true
}
],
// "cmd-e": [
// "buffer_search::Deploy",
// {
// "focus": false
// }
// ],
"ctrl->": "assistant::QuoteSelection",
"ctrl-<": "assistant::InsertIntoEditor",
"ctrl-alt-e": "editor::SelectEnclosingSymbol"
@@ -228,7 +268,6 @@
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-w": "pane::CloseActiveItem",
"ctrl-f4": "pane::CloseActiveItem",
"alt-ctrl-t": "pane::CloseInactiveItems",
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k u": "pane::CloseCleanItems",
@@ -271,13 +310,38 @@
"ctrl-shift-right": "editor::SelectToNextWordEnd",
"ctrl-shift-up": "editor::SelectLargerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-shift-down": "editor::SelectSmallerSyntaxNode", //todo(linux) tmp keybinding
"ctrl-d": ["editor::SelectNext", { "replace_newest": false }],
"ctrl-d": [
"editor::SelectNext",
{
"replace_newest": false
}
],
"ctrl-shift-l": "editor::SelectAllMatches",
"ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": false }],
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }],
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }],
"ctrl-shift-d": [
"editor::SelectPrevious",
{
"replace_newest": false
}
],
"ctrl-k ctrl-d": [
"editor::SelectNext",
{
"replace_newest": true
}
],
"ctrl-k ctrl-shift-d": [
"editor::SelectPrevious",
{
"replace_newest": true
}
],
"ctrl-k ctrl-i": "editor::Hover",
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-/": [
"editor::ToggleComments",
{
"advance_downwards": false
}
],
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
@@ -285,23 +349,16 @@
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
"ctrl-shift-f10": "editor::GoToDefinitionSplit",
"ctrl-f12": "editor::GoToTypeDefinition",
"shift-f12": "editor::GoToImplementation",
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-shift-\\": "editor::MoveToEnclosingBracket",
"ctrl-shift-[": "editor::Fold",
"ctrl-shift-]": "editor::UnfoldLines",
"ctrl-space": "editor::ShowCompletions",
"ctrl-.": "editor::ToggleCodeActions",
"alt-ctrl-r": "editor::RevealInFileManager",
"ctrl-k r": "editor::RevealInFileManager",
"ctrl-k p": "editor::CopyPath",
"ctrl-\\": "pane::SplitRight",
"ctrl-k v": "markdown::OpenPreviewToTheSide",
"ctrl-shift-v": "markdown::OpenPreview",
"alt-ctrl-r": "editor::RevealInFinder",
"ctrl-alt-shift-c": "editor::DisplayCursorNames"
}
},
@@ -326,10 +383,8 @@
"alt-9": ["pane::ActivateItem", 8],
"alt-0": "pane::ActivateLastItem",
"ctrl-alt--": "pane::GoBack",
"ctrl-alt-shift--": "pane::GoForward",
"ctrl-alt-_": "pane::GoForward",
"ctrl-shift-t": "pane::ReopenClosedItem",
"f3": "search::SelectNextMatch",
"shift-f3": "search::SelectPrevMatch",
"ctrl-shift-f": "project_search::ToggleFocus"
}
},
@@ -337,7 +392,12 @@
"context": "Workspace",
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }],
// "alt-cmd-o": [
// "projects::OpenRecent",
// {
// "create_new_window": true
// }
// ]
"alt-ctrl-o": "projects::OpenRecent",
"alt-ctrl-shift-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
@@ -356,21 +416,25 @@
"alt-7": ["workspace::ActivatePane", 6],
"alt-8": ["workspace::ActivatePane", 7],
"alt-9": ["workspace::ActivatePane", 8],
"ctrl-alt-b": "workspace::ToggleRightDock",
"ctrl-alt-b": "workspace::ToggleLeftDock",
"ctrl-b": "workspace::ToggleLeftDock",
"ctrl-j": "workspace::ToggleBottomDock",
"ctrl-alt-y": "workspace::CloseAllDocks",
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-h": [
"pane::DeploySearch",
{
"replace_enabled": true
}
],
"ctrl-k ctrl-s": "zed::OpenKeymap",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
"ctrl-shift-t": "project_symbols::Toggle",
"ctrl-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"ctrl-e": "file_finder::Toggle",
"ctrl-shift-p": "command_palette::Toggle",
"f1": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
"ctrl-shift-e": "project_panel::ToggleFocus",
"ctrl-shift-b": "outline_panel::ToggleFocus",
@@ -386,7 +450,6 @@
"ctrl-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-shift-x": "zed::Extensions",
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn"
}
@@ -403,7 +466,7 @@
"ctrl-alt-delete": "editor::DeleteToNextSubwordEnd",
"ctrl-alt-d": "editor::DeleteToNextSubwordEnd",
"ctrl-alt-left": "editor::MoveToPreviousSubwordStart",
// "ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-alt-b": "editor::MoveToPreviousSubwordStart",
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
@@ -502,12 +565,11 @@
{
"context": "OutlinePanel",
"bindings": {
"escape": "menu::Cancel",
"left": "outline_panel::CollapseSelectedEntry",
"right": "outline_panel::ExpandSelectedEntry",
"ctrl-alt-c": "outline_panel::CopyPath",
"alt-ctrl-shift-c": "outline_panel::CopyRelativePath",
"alt-ctrl-r": "outline_panel::RevealInFileManager",
"alt-ctrl-r": "outline_panel::RevealInFinder",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
@@ -534,7 +596,7 @@
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-ctrl-r": "project_panel::RevealInFileManager",
"alt-ctrl-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",

View File

@@ -3,14 +3,10 @@
{
"bindings": {
"up": "menu::SelectPrev",
"shift-tab": "menu::SelectPrev",
"home": "menu::SelectFirst",
"pageup": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext",
"tab": "menu::SelectNext",
"end": "menu::SelectLast",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext",
@@ -113,14 +109,54 @@
"cmd-a": "editor::SelectAll",
"cmd-l": "editor::SelectLine",
"cmd-shift-i": "editor::Format",
"cmd-shift-left": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"ctrl-shift-a": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true }],
"cmd-shift-right": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-shift-e": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }],
"ctrl-v": ["editor::MovePageDown", { "center_cursor": true }],
"alt-v": ["editor::MovePageUp", { "center_cursor": true }],
"cmd-shift-left": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
"shift-home": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-a": [
"editor::SelectToBeginningOfLine",
{
"stop_at_soft_wraps": true
}
],
"cmd-shift-right": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"shift-end": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-e": [
"editor::SelectToEndOfLine",
{
"stop_at_soft_wraps": true
}
],
"ctrl-v": [
"editor::MovePageDown",
{
"center_cursor": true
}
],
"alt-v": [
"editor::MovePageUp",
{
"center_cursor": true
}
],
"ctrl-cmd-space": "editor::ShowCharacterPalette",
"cmd-;": "editor::ToggleLineNumbers",
"cmd-alt-z": "editor::RevertSelectedHunks",
@@ -135,22 +171,32 @@
"enter": "editor::Newline",
"shift-enter": "editor::Newline",
"cmd-shift-enter": "editor::NewlineAbove",
"cmd-enter": "editor::NewlineBelow",
"alt-z": "editor::ToggleSoftWrap",
"cmd-f": "buffer_search::Deploy",
"cmd-alt-f": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-alt-l": ["buffer_search::Deploy", { "selection_search_enabled": true }],
"cmd-e": ["buffer_search::Deploy", { "focus": false }],
"cmd-alt-f": [
"buffer_search::Deploy",
{
"replace_enabled": true
}
],
"cmd-alt-l": [
"buffer_search::Deploy",
{
"selection_search_enabled": true
}
],
"cmd-e": [
"buffer_search::Deploy",
{
"focus": false
}
],
"cmd->": "assistant::QuoteSelection",
"cmd-<": "assistant::InsertIntoEditor",
"cmd-alt-e": "editor::SelectEnclosingSymbol"
}
},
{
"context": "Editor && mode == full && !jupyter",
"bindings": {
"cmd-enter": "editor::NewlineBelow"
}
},
{
"context": "Editor && mode == full && inline_completion",
"bindings": {
@@ -239,7 +285,6 @@
"context": "ProjectSearchBar",
"bindings": {
"escape": "project_search::ToggleFocus",
"cmd-shift-j": "project_search::ToggleFilters",
"cmd-shift-f": "search::FocusSearch",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
@@ -264,7 +309,6 @@
"context": "ProjectSearchView",
"bindings": {
"escape": "project_search::ToggleFocus",
"cmd-shift-j": "project_search::ToggleFilters",
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex"
@@ -312,13 +356,38 @@
"alt-shift-down": "editor::DuplicateLineDown",
"ctrl-shift-right": "editor::SelectLargerSyntaxNode",
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode",
"cmd-d": ["editor::SelectNext", { "replace_newest": false }],
"cmd-d": [
"editor::SelectNext",
{
"replace_newest": false
}
],
"cmd-shift-l": "editor::SelectAllMatches",
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }],
"cmd-k cmd-d": ["editor::SelectNext", { "replace_newest": true }],
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }],
"ctrl-cmd-d": [
"editor::SelectPrevious",
{
"replace_newest": false
}
],
"cmd-k cmd-d": [
"editor::SelectNext",
{
"replace_newest": true
}
],
"cmd-k ctrl-cmd-d": [
"editor::SelectPrevious",
{
"replace_newest": true
}
],
"cmd-k cmd-i": "editor::Hover",
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
"cmd-/": [
"editor::ToggleComments",
{
"advance_downwards": false
}
],
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
@@ -331,17 +400,11 @@
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket",
"cmd-shift-\\": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"ctrl-space": "editor::ShowCompletions",
"cmd-.": "editor::ToggleCodeActions",
"alt-cmd-r": "editor::RevealInFileManager",
"cmd-k r": "editor::RevealInFileManager",
"cmd-k p": "editor::CopyPath",
"cmd-\\": "pane::SplitRight",
"cmd-k v": "markdown::OpenPreviewToTheSide",
"cmd-shift-v": "markdown::OpenPreview",
"alt-cmd-r": "editor::RevealInFinder",
"ctrl-cmd-c": "editor::DisplayCursorNames"
}
},
@@ -366,7 +429,7 @@
"ctrl-9": ["pane::ActivateItem", 8],
"ctrl-0": "pane::ActivateLastItem",
"ctrl--": "pane::GoBack",
"ctrl-shift--": "pane::GoForward",
"ctrl-_": "pane::GoForward",
"cmd-shift-t": "pane::ReopenClosedItem",
"cmd-shift-f": "project_search::ToggleFocus"
}
@@ -375,7 +438,12 @@
"context": "Workspace",
"bindings": {
// Change the default action on `menu::Confirm` by setting the parameter
// "alt-cmd-o": ["projects::OpenRecent", {"create_new_window": true }],
// "alt-cmd-o": [
// "projects::OpenRecent",
// {
// "create_new_window": true
// }
// ]
"alt-cmd-o": "projects::OpenRecent",
"alt-cmd-b": "branches::OpenRecent",
"ctrl-~": "workspace::NewTerminal",
@@ -399,7 +467,12 @@
"cmd-j": "workspace::ToggleBottomDock",
"alt-cmd-y": "workspace::CloseAllDocks",
"cmd-shift-f": "pane::DeploySearch",
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"cmd-shift-h": [
"pane::DeploySearch",
{
"replace_enabled": true
}
],
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
@@ -422,7 +495,6 @@
"cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"],
"cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"],
"cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"],
"cmd-shift-x": "zed::Extensions",
"alt-t": "task::Rerun",
"alt-shift-t": "task::Spawn"
}
@@ -523,12 +595,11 @@
{
"context": "OutlinePanel",
"bindings": {
"escape": "menu::Cancel",
"left": "outline_panel::CollapseSelectedEntry",
"right": "outline_panel::ExpandSelectedEntry",
"cmd-alt-c": "outline_panel::CopyPath",
"alt-cmd-shift-c": "outline_panel::CopyRelativePath",
"alt-cmd-r": "outline_panel::RevealInFileManager",
"alt-cmd-r": "outline_panel::RevealInFinder",
"space": "outline_panel::Open",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev"
@@ -548,14 +619,12 @@
"cmd-alt-c": "project_panel::CopyPath",
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
"enter": "project_panel::Rename",
"f2": "project_panel::Rename",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFileManager",
"cmd-alt-backspace": ["project_panel::Delete", { "skip_prompt": false }],
"alt-cmd-r": "project_panel::RevealInFinder",
"alt-shift-f": "project_panel::NewSearchInDirectory",
"shift-down": "menu::SelectNext",
"shift-up": "menu::SelectPrev",
@@ -568,12 +637,6 @@
"space": "project_panel::Open"
}
},
{
"context": "Editor && jupyter",
"bindings": {
"cmd-enter": "repl::Run"
}
},
{
"context": "CollabPanel && not_editing",
"bindings": {
@@ -644,14 +707,10 @@
"escape": ["terminal::SendKeystroke", "escape"],
"enter": ["terminal::SendKeystroke", "enter"],
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
"cmd-up": "terminal::ScrollPageUp",
"cmd-down": "terminal::ScrollPageDown",
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown",
"shift-up": "terminal::ScrollLineUp",
"shift-down": "terminal::ScrollLineDown",
"cmd-home": "terminal::ScrollToTop",
"cmd-end": "terminal::ScrollToBottom",
"shift-home": "terminal::ScrollToTop",
"shift-end": "terminal::ScrollToBottom"
}

View File

@@ -21,9 +21,24 @@
"cmd--": "editor::Fold",
"cmd-+": "editor::UnfoldLines",
"alt-shift-g": "editor::SplitSelectionIntoLines",
"ctrl-g": ["editor::SelectNext", { "replace_newest": false }],
"ctrl-cmd-g": ["editor::SelectPrevious", { "replace_newest": false }],
"cmd-/": ["editor::ToggleComments", { "advance_downwards": true }],
"ctrl-g": [
"editor::SelectNext",
{
"replace_newest": false
}
],
"ctrl-cmd-g": [
"editor::SelectPrevious",
{
"replace_newest": false
}
],
"cmd-/": [
"editor::ToggleComments",
{
"advance_downwards": true
}
],
"alt-up": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"shift-alt-up": "editor::MoveLineUp",
@@ -39,7 +54,7 @@
"cmd-shift-b": "editor::GoToTypeDefinition",
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPrevDiagnostic",
"cmd-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPrevHunk",
"cmd-home": "editor::MoveToBeginning",

View File

@@ -1,93 +0,0 @@
// Default Keymap (Atom) for Zed on Linux
[
{
"bindings": {
"ctrl-shift-f5": "workspace::Reload", // window:reload
"ctrl-k ctrl-n": "workspace::ActivatePreviousPane", // window:focus-next-pane
"ctrl-k ctrl-p": "workspace::ActivateNextPane" // window:focus-previous-pane
}
},
{
"context": "Editor",
"bindings": {
"ctrl-shift-l": "language_selector::Toggle", // grammar-selector:show
"ctrl-|": "pane::RevealInProjectPanel", // tree-view:reveal-active-file
"ctrl-b": "editor::GoToDefinition", // fuzzy-finder:toggle-buffer-finder
"ctrl-alt-b": "editor::GoToDefinitionSplit", // N/A: From JetBrains
"ctrl-<": "editor::ScrollCursorCenter", // editor:scroll-to-cursor
"f3": ["editor::SelectNext", { "replace_newest": true }], // find-and-replace:find-next
"shift-f3": ["editor::SelectPrevious", { "replace_newest": true }], //find-and-replace:find-previous
"alt-shift-down": "editor::AddSelectionBelow", // editor:add-selection-below
"alt-shift-up": "editor::AddSelectionAbove", // editor:add-selection-above
"ctrl-k ctrl-u": "editor::ConvertToUpperCase", // editor:upper-case
"ctrl-k ctrl-l": "editor::ConvertToLowerCase", // editor:lower-case
"ctrl-j": "editor::JoinLines", // editor:join-lines
"ctrl-shift-d": "editor::DuplicateLineDown", // editor:duplicate-lines
"ctrl-up": "editor::MoveLineUp", // editor:move-line-up
"ctrl-down": "editor::MoveLineDown", // editor:move-line-down
"ctrl-shift-m": "markdown::OpenPreviewToTheSide" // markdown-preview:toggle
}
},
{
"context": "Editor && mode == full",
"bindings": {
"ctrl-r": "outline::Toggle" // symbols-view:toggle-project-symbols
}
},
{
"context": "BufferSearchBar",
"bindings": {
"ctrl-f3": "search::SelectNextMatch", // find-and-replace:find-next-selected
"ctrl-shift-f3": "search::SelectPrevMatch" // find-and-replace:find-previous-selected
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-\\": "workspace::ToggleLeftDock", // tree-view:toggle
"ctrl-k ctrl-b": "workspace::ToggleLeftDock", // tree-view:toggle
"ctrl-t": "file_finder::Toggle", // fuzzy-finder:toggle-file-finder
"ctrl-r": "project_symbols::Toggle" // symbols-view:toggle-project-symbols
}
},
{
"context": "Pane",
"bindings": {
// "ctrl-0": "project_panel::ToggleFocus", // tree-view:toggle-focus
"ctrl-1": ["pane::ActivateItem", 0], // tree-view:open-selected-entry-in-pane-1
"ctrl-2": ["pane::ActivateItem", 1], // tree-view:open-selected-entry-in-pane-2
"ctrl-3": ["pane::ActivateItem", 2], // tree-view:open-selected-entry-in-pane-3
"ctrl-4": ["pane::ActivateItem", 3], // tree-view:open-selected-entry-in-pane-4
"ctrl-5": ["pane::ActivateItem", 4], // tree-view:open-selected-entry-in-pane-5
"ctrl-6": ["pane::ActivateItem", 5], // tree-view:open-selected-entry-in-pane-6
"ctrl-7": ["pane::ActivateItem", 6], // tree-view:open-selected-entry-in-pane-7
"ctrl-8": ["pane::ActivateItem", 7], // tree-view:open-selected-entry-in-pane-8
"ctrl-9": ["pane::ActivateItem", 8] // tree-view:open-selected-entry-in-pane-9
}
},
{
"context": "ProjectPanel",
"bindings": {
"f2": "project_panel::Rename", // tree-view:rename
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"ctrl-x": "project_panel::Cut", // tree-view:cut
"ctrl-c": "project_panel::Copy", // tree-view:copy
"ctrl-v": "project_panel::Paste" // tree-view:paste
}
},
{
"context": "ProjectPanel && not_editing",
"bindings": {
"ctrl-shift-c": "project_panel::CopyPath", // tree-view:copy-full-path
"ctrl-[": "project_panel::CollapseSelectedEntry", // tree-view:collapse-directory
"ctrl-b": "project_panel::CollapseSelectedEntry", // tree-view:collapse-directory
"ctrl-]": "project_panel::ExpandSelectedEntry", // tree-view:expand-item
"ctrl-f": "project_panel::ExpandSelectedEntry", // tree-view:expand-item
"a": "project_panel::NewFile", // tree-view:add-file
"d": "project_panel::Duplicate", // tree-view:duplicate
"home": "menu::SelectFirst", // core:move-to-top
"end": "menu::SelectLast", // core:move-to-bottom
"shift-a": "project_panel::NewDirectory" // tree-view:add-folder
}
}
]

View File

@@ -1,90 +0,0 @@
[
{
"bindings": {
"ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-shift-]": "pane::ActivateNextItem"
}
},
{
"context": "Editor",
"bindings": {
"ctrl->": "zed::IncreaseBufferFontSize",
"ctrl-<": "zed::DecreaseBufferFontSize",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-d": "editor::DuplicateLineDown",
"ctrl-y": "editor::DeleteLine",
"ctrl-pagedown": "editor::MovePageDown",
"ctrl-pageup": "editor::MovePageUp",
// "ctrl-alt-shift-b": "editor::SelectToPreviousWordStart",
"ctrl-alt-enter": "editor::NewlineAbove",
"shift-enter": "editor::NewlineBelow",
// "ctrl--": "editor::Fold", // TODO: `ctrl-numpad--` (numpad not implemented)
// "ctrl-+": "editor::UnfoldLines", // TODO: `ctrl-numpad+` (numpad not implemented)
"alt-shift-g": "editor::SplitSelectionIntoLines",
"alt-j": ["editor::SelectNext", { "replace_newest": false }],
"alt-shift-j": ["editor::SelectPrevious", { "replace_newest": false }],
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": true }],
"alt-up": "editor::SelectLargerSyntaxNode",
"alt-down": "editor::SelectSmallerSyntaxNode",
"shift-alt-up": "editor::MoveLineUp",
"shift-alt-down": "editor::MoveLineDown",
"ctrl-alt-l": "editor::Format",
"shift-f6": "editor::Rename",
"ctrl-alt-left": "pane::GoBack",
"ctrl-alt-right": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"ctrl-alt-f7": "editor::FindAllReferences",
// "ctrl-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
// "ctrl-alt-b": "editor::GoToDefinitionSplit", // Conflicts with workspace::ToggleLeftDock
"ctrl-shift-b": "editor::GoToTypeDefinition",
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic",
"shift-f2": "editor::GoToPrevDiagnostic",
"ctrl-alt-shift-down": "editor::GoToHunk",
"ctrl-alt-shift-up": "editor::GoToPrevHunk",
"ctrl-home": "editor::MoveToBeginning",
"ctrl-end": "editor::MoveToEnd",
"ctrl-shift-home": "editor::SelectToBeginning",
"ctrl-shift-end": "editor::SelectToEnd"
}
},
{
"context": "Editor && mode == full",
"bindings": {
"ctrl-f12": "outline::Toggle",
"alt-7": "outline::Toggle",
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-g": "go_to_line::Toggle",
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"ctrl-alt-shift-n": "project_symbols::Toggle",
"alt-1": "workspace::ToggleLeftDock",
"ctrl-e": "tab_switcher::Toggle",
"alt-6": "diagnostics::Deploy"
}
},
{
"context": "Pane",
"bindings": {
"ctrl-alt-left": "pane::GoBack",
"ctrl-alt-right": "pane::GoForward"
}
},
{
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
}
]

View File

@@ -1,53 +0,0 @@
[
{
"bindings": {
"ctrl-shift-[": "pane::ActivatePrevItem",
"ctrl-shift-]": "pane::ActivateNextItem",
"ctrl-pagedown": "pane::ActivatePrevItem",
"ctrl-pageup": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivatePrevItem"
}
},
{
"context": "Editor",
"bindings": {
"ctrl-shift-up": "editor::AddSelectionAbove",
"ctrl-shift-down": "editor::AddSelectionBelow",
"ctrl-shift-m": "editor::SelectLargerSyntaxNode",
"ctrl-shift-l": "editor::SplitSelectionIntoLines",
"ctrl-shift-a": "editor::SelectLargerSyntaxNode",
"ctrl-shift-d": "editor::DuplicateLineDown",
"f12": "editor::GoToDefinition",
"ctrl-f12": "editor::GoToDefinitionSplit",
"shift-f12": "editor::FindAllReferences",
"ctrl-shift-f12": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPrevHunk",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"
}
},
{
"context": "Editor && mode == full",
"bindings": {
"ctrl-r": "outline::Toggle"
}
},
{
"context": "Pane",
"bindings": {
"f4": "search::SelectNextMatch",
"shift-f4": "search::SelectPrevMatch"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-k ctrl-b": "workspace::ToggleLeftDock",
// "ctrl-0": "project_panel::ToggleFocus", // normally resets zoom
"shift-ctrl-r": "project_symbols::Toggle"
}
}
]

View File

@@ -6,7 +6,8 @@
"ctrl-pagedown": "pane::ActivatePrevItem",
"ctrl-pageup": "pane::ActivateNextItem",
"ctrl-shift-tab": "pane::ActivateNextItem",
"ctrl-tab": "pane::ActivatePrevItem"
"ctrl-tab": "pane::ActivatePrevItem",
"cmd-+": "zed::IncreaseBufferFontSize"
}
},
{
@@ -25,9 +26,6 @@
"alt-shift-cmd-down": "editor::FindAllReferences",
"ctrl-.": "editor::GoToHunk",
"ctrl-,": "editor::GoToPrevHunk",
"cmd-k cmd-u": "editor::ConvertToUpperCase",
"cmd-k cmd-l": "editor::ConvertToLowerCase",
"shift-alt-m": "markdown::OpenPreviewToTheSide",
"ctrl-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-delete": "editor::DeleteToNextWordEnd"
}

View File

@@ -22,14 +22,34 @@
"alt-shift-delete": "editor::DeleteToNextWordEnd",
"ctrl-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-delete": "editor::DeleteToNextSubwordEnd",
"alt-left": ["editor::MoveToPreviousWordStart", { "stop_at_soft_wraps": true }],
"alt-right": ["editor::MoveToNextWordEnd", { "stop_at_soft_wraps": true }],
"alt-left": [
"editor::MoveToPreviousWordStart",
{
"stop_at_soft_wraps": true
}
],
"alt-right": [
"editor::MoveToNextWordEnd",
{
"stop_at_soft_wraps": true
}
],
"ctrl-left": "editor::MoveToPreviousSubwordStart",
"ctrl-right": "editor::MoveToNextSubwordEnd",
"cmd-shift-left": "editor::SelectToBeginningOfLine",
"cmd-shift-right": "editor::SelectToEndOfLine",
"alt-shift-left": ["editor::SelectToPreviousWordStart", { "stop_at_soft_wraps": true }],
"alt-shift-right": ["editor::SelectToNextWordEnd", { "stop_at_soft_wraps": true }],
"alt-shift-left": [
"editor::SelectToPreviousWordStart",
{
"stop_at_soft_wraps": true
}
],
"alt-shift-right": [
"editor::SelectToNextWordEnd",
{
"stop_at_soft_wraps": true
}
],
"ctrl-shift-left": "editor::SelectToPreviousSubwordStart",
"ctrl-shift-right": "editor::SelectToNextSubwordEnd",
"ctrl-w": "editor::SelectNext",

View File

@@ -8,8 +8,22 @@
{
"context": "Editor && VimControl && !VimWaiting && !menu",
"bindings": {
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }],
"i": [
"vim::PushOperator",
{
"Object": {
"around": false
}
}
],
"a": [
"vim::PushOperator",
{
"Object": {
"around": true
}
}
],
":": "command_palette::Toggle",
"h": "vim::Left",
"left": "vim::Left",
@@ -43,25 +57,92 @@
// "b": "vim::PreviousSubwordStart",
// "e": "vim::NextSubwordEnd",
// "g e": "vim::PreviousSubwordEnd",
"shift-w": ["vim::NextWordStart", { "ignorePunctuation": true }],
"shift-e": ["vim::NextWordEnd", { "ignorePunctuation": true }],
"shift-b": ["vim::PreviousWordStart", { "ignorePunctuation": true }],
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
"shift-w": [
"vim::NextWordStart",
{
"ignorePunctuation": true
}
],
"shift-e": [
"vim::NextWordEnd",
{
"ignorePunctuation": true
}
],
"shift-b": [
"vim::PreviousWordStart",
{
"ignorePunctuation": true
}
],
"g shift-e": [
"vim::PreviousWordEnd",
{
"ignorePunctuation": true
}
],
"/": "vim::Search",
"g /": "pane::DeploySearch",
"?": ["vim::Search", { "backwards": true }],
"?": [
"vim::Search",
{
"backwards": true
}
],
"*": "vim::MoveToNext",
"#": "vim::MoveToPrev",
"n": "vim::MoveToNextMatch",
"shift-n": "vim::MoveToPrevMatch",
"%": "vim::Matching",
"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 } }],
"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
}
}
],
"m": ["vim::PushOperator", "Mark"],
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
"'": [
"vim::PushOperator",
{
"Jump": {
"line": true
}
}
],
"`": [
"vim::PushOperator",
{
"Jump": {
"line": false
}
}
],
";": "vim::RepeatFind",
",": "vim::RepeatFindReversed",
"ctrl-o": "pane::GoBack",
@@ -98,25 +179,90 @@
"g shift-n": "vim::SelectPreviousMatch",
"g l": "vim::SelectNext",
"g shift-l": "vim::SelectPrevious",
"g >": ["editor::SelectNext", { "replace_newest": true }],
"g <": ["editor::SelectPrevious", { "replace_newest": true }],
"g >": [
"editor::SelectNext",
{
"replace_newest": true
}
],
"g <": [
"editor::SelectPrevious",
{
"replace_newest": true
}
],
"g a": "editor::SelectAllMatches",
"g s": "outline::Toggle",
"g shift-s": "project_symbols::Toggle",
"g .": "editor::ToggleCodeActions", // zed specific
"g shift-a": "editor::FindAllReferences", // zed specific
"g space": "editor::OpenExcerpts", // zed specific
"g *": ["vim::MoveToNext", { "partialWord": true }],
"g #": ["vim::MoveToPrev", { "partialWord": true }],
"g j": ["vim::Down", { "displayLines": true }],
"g down": ["vim::Down", { "displayLines": true }],
"g k": ["vim::Up", { "displayLines": true }],
"g up": ["vim::Up", { "displayLines": true }],
"g $": ["vim::EndOfLine", { "displayLines": true }],
"g end": ["vim::EndOfLine", { "displayLines": true }],
"g 0": ["vim::StartOfLine", { "displayLines": true }],
"g home": ["vim::StartOfLine", { "displayLines": true }],
"g ^": ["vim::FirstNonWhitespace", { "displayLines": true }],
"g *": [
"vim::MoveToNext",
{
"partialWord": true
}
],
"g #": [
"vim::MoveToPrev",
{
"partialWord": true
}
],
"g j": [
"vim::Down",
{
"displayLines": true
}
],
"g down": [
"vim::Down",
{
"displayLines": true
}
],
"g k": [
"vim::Up",
{
"displayLines": true
}
],
"g up": [
"vim::Up",
{
"displayLines": true
}
],
"g $": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g end": [
"vim::EndOfLine",
{
"displayLines": true
}
],
"g 0": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g home": [
"vim::StartOfLine",
{
"displayLines": true
}
],
"g ^": [
"vim::FirstNonWhitespace",
{
"displayLines": true
}
],
"g v": "vim::RestoreVisualSelection",
"g ]": "editor::GoToDiagnostic",
"g [": "editor::GoToPrevDiagnostic",
@@ -134,8 +280,18 @@
"z c": "editor::Fold",
"z o": "editor::UnfoldLines",
"z f": "editor::FoldSelectedRanges",
"shift-z shift-q": ["pane::CloseActiveItem", { "saveIntent": "skip" }],
"shift-z shift-z": ["pane::CloseActiveItem", { "saveIntent": "saveAll" }],
"shift-z shift-q": [
"pane::CloseActiveItem",
{
"saveIntent": "skip"
}
],
"shift-z shift-z": [
"pane::CloseActiveItem",
{
"saveIntent": "saveAll"
}
],
// Count support
"1": ["vim::Number", 1],
"2": ["vim::Number", 2],
@@ -147,7 +303,6 @@
"8": ["vim::Number", 8],
"9": ["vim::Number", 9],
// window related commands (ctrl-w X)
"ctrl-w": null,
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
@@ -231,9 +386,14 @@
"ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "before": true }],
"u": "vim::Undo",
"ctrl-r": "vim::Redo",
"shift-p": [
"vim::Paste",
{
"before": true
}
],
"u": "editor::Undo",
"ctrl-r": "editor::Redo",
"r": ["vim::PushOperator", "Replace"],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
@@ -282,7 +442,12 @@
{
"context": "Editor && vim_mode == normal && vim_operator == c",
"bindings": {
"s": ["vim::PushOperator", { "ChangeSurrounds": {} }]
"s": [
"vim::PushOperator",
{
"ChangeSurrounds": {}
}
]
}
},
{
@@ -327,7 +492,12 @@
{
"context": "Editor && vim_mode == normal && vim_operator == y",
"bindings": {
"s": ["vim::PushOperator", { "AddSurrounds": {} }]
"s": [
"vim::PushOperator",
{
"AddSurrounds": {}
}
]
}
},
{
@@ -352,7 +522,12 @@
"context": "Editor && VimObject",
"bindings": {
"w": "vim::Word",
"shift-w": ["vim::Word", { "ignorePunctuation": true }],
"shift-w": [
"vim::Word",
{
"ignorePunctuation": true
}
],
"t": "vim::Tag",
"s": "vim::Sentence",
"p": "vim::Paragraph",
@@ -387,18 +562,43 @@
"y": "vim::VisualYank",
"shift-y": "vim::VisualYank",
"p": "vim::Paste",
"shift-p": ["vim::Paste", { "preserveClipboard": true }],
"shift-p": [
"vim::Paste",
{
"preserveClipboard": true
}
],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"shift-r": "vim::SubstituteLine",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
"*": ["vim::MoveToNext", { "partialWord": true }],
"#": ["vim::MoveToPrev", { "partialWord": true }],
"*": [
"vim::MoveToNext",
{
"partialWord": true
}
],
"#": [
"vim::MoveToPrev",
{
"partialWord": true
}
],
"ctrl-a": "vim::Increment",
"ctrl-x": "vim::Decrement",
"g ctrl-a": ["vim::Increment", { "step": true }],
"g ctrl-x": ["vim::Decrement", { "step": true }],
"g ctrl-a": [
"vim::Increment",
{
"step": true
}
],
"g ctrl-x": [
"vim::Decrement",
{
"step": true
}
],
"shift-i": "vim::InsertBefore",
"shift-a": "vim::InsertAfter",
"shift-j": "vim::JoinLines",
@@ -408,8 +608,22 @@
"ctrl-[": ["vim::SwitchMode", "Normal"],
">": "vim::Indent",
"<": "vim::Outdent",
"i": ["vim::PushOperator", { "Object": { "around": false } }],
"a": ["vim::PushOperator", { "Object": { "around": true } }]
"i": [
"vim::PushOperator",
{
"Object": {
"around": false
}
}
],
"a": [
"vim::PushOperator",
{
"Object": {
"around": true
}
}
]
}
},
{
@@ -430,7 +644,6 @@
"escape": "vim::NormalBefore",
"ctrl-c": "vim::NormalBefore",
"ctrl-[": "vim::NormalBefore",
"ctrl-x": null,
"ctrl-x ctrl-o": "editor::ShowCompletions",
"ctrl-x ctrl-a": "assistant::InlineAssist", // zed specific
"ctrl-x ctrl-c": "editor::ShowInlineCompletion", // zed specific
@@ -504,7 +717,7 @@
"t": "project_panel::OpenPermanent",
"v": "project_panel::OpenPermanent",
"p": "project_panel::Open",
"x": "project_panel::RevealInFileManager",
"x": "project_panel::RevealInFinder",
"shift-g": "menu::SelectLast",
"g g": "menu::SelectFirst",
"-": "project_panel::SelectParent"

View File

@@ -690,7 +690,6 @@
// }
//
"file_types": {
"JSON": ["flake.lock"],
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json"]
},
// The extensions that Zed should automatically install on startup.
@@ -776,8 +775,7 @@
"PHP": {
"prettier": {
"allowed": true,
"plugins": ["@prettier/plugin-php"],
"parser": "php"
"plugins": ["@prettier/plugin-php"]
}
},
"Ruby": {

View File

@@ -15,25 +15,25 @@
"elevated_surface.background": "#2f343eff",
"surface.background": "#2f343eff",
"background": "#3b414dff",
"element.background": "#2e343eff",
"element.background": "#2f343eff",
"element.hover": "#363c46ff",
"element.active": "#454a56ff",
"element.selected": "#454a56ff",
"element.disabled": "#2e343eff",
"element.disabled": "#2f343eff",
"drop_target.background": "#83899480",
"ghost_element.background": "#00000000",
"ghost_element.hover": "#363c46ff",
"ghost_element.active": "#454a56ff",
"ghost_element.selected": "#454a56ff",
"ghost_element.disabled": "#2e343eff",
"ghost_element.disabled": "#2f343eff",
"text": "#c8ccd4ff",
"text.muted": "#838994ff",
"text.placeholder": "#696B77ff",
"text.disabled": "#696B77ff",
"text.placeholder": "#555a63ff",
"text.disabled": "#555a63ff",
"text.accent": "#74ade8ff",
"icon": "#c8ccd4ff",
"icon.muted": "#838994ff",
"icon.disabled": "#696B77ff",
"icon.disabled": "#555a63ff",
"icon.placeholder": "#838994ff",
"icon.accent": "#74ade8ff",
"status_bar.background": "#3b414dff",
@@ -59,7 +59,7 @@
"editor.highlighted_line.background": "#2f343eff",
"editor.line_number": "#c8ccd459",
"editor.active_line_number": "#c8ccd4ff",
"editor.invisible": "#696B77ff",
"editor.invisible": "#555a63ff",
"editor.wrap_guide": "#c8ccd40d",
"editor.active_wrap_guide": "#c8ccd41a",
"editor.document_highlight.read_background": "#74ade81a",
@@ -94,46 +94,46 @@
"terminal.ansi.dim_white": "#575d65ff",
"link_text.hover": "#74ade8ff",
"conflict": "#dec184ff",
"conflict.background": "#dec1841a",
"conflict.background": "#41321dff",
"conflict.border": "#5d4c2fff",
"created": "#a1c181ff",
"created.background": "#a1c1811a",
"created.background": "#222e1dff",
"created.border": "#38482fff",
"deleted": "#d07277ff",
"deleted.background": "#d072771a",
"deleted.background": "#301b1bff",
"deleted.border": "#4c2b2cff",
"error": "#d07277ff",
"error.background": "#d072771a",
"error.background": "#301b1bff",
"error.border": "#4c2b2cff",
"hidden": "#696B77ff",
"hidden.background": "#696B771a",
"hidden": "#555a63ff",
"hidden.background": "#3b414dff",
"hidden.border": "#414754ff",
"hint": "#5a6f89ff",
"hint.background": "#5a6f891a",
"hint.background": "#18243dff",
"hint.border": "#293b5bff",
"ignored": "#696B77ff",
"ignored.background": "#696B771a",
"ignored": "#555a63ff",
"ignored.background": "#3b414dff",
"ignored.border": "#464b57ff",
"info": "#74ade8ff",
"info.background": "#74ade81a",
"info.background": "#18243dff",
"info.border": "#293b5bff",
"modified": "#dec184ff",
"modified.background": "#dec1841a",
"modified.background": "#41321dff",
"modified.border": "#5d4c2fff",
"predictive": "#5a6a87ff",
"predictive.background": "#5a6a871a",
"predictive.background": "#222e1dff",
"predictive.border": "#38482fff",
"renamed": "#74ade8ff",
"renamed.background": "#74ade81a",
"renamed.background": "#18243dff",
"renamed.border": "#293b5bff",
"success": "#a1c181ff",
"success.background": "#a1c1811a",
"success.background": "#222e1dff",
"success.border": "#38482fff",
"unreachable": "#838994ff",
"unreachable.background": "#8389941a",
"unreachable.background": "#3b414dff",
"unreachable.border": "#464b57ff",
"warning": "#dec184ff",
"warning.background": "#dec1841a",
"warning.background": "#41321dff",
"warning.border": "#5d4c2fff",
"players": [
{

View File

@@ -163,7 +163,7 @@ impl LanguageModelRequestMessage {
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[derive(Debug, Default, Serialize)]
pub struct LanguageModelRequest {
pub model: LanguageModel,
pub messages: Vec<LanguageModelRequestMessage>,

View File

@@ -616,9 +616,8 @@ impl AssistantPanel {
.filter(|editor| editor.read(cx).context.read(cx).path.as_ref() == Some(&path))
});
if let Some(existing_context) = existing_context {
return cx.spawn(|this, mut cx| async move {
this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
});
self.show_context(existing_context, cx);
return Task::ready(Ok(()));
}
let saved_context = self.context_store.read(cx).load(path.clone(), cx);
@@ -1410,7 +1409,7 @@ impl Context {
}
let request = self.to_completion_request(cx);
let response = CompletionProvider::global(cx).complete(request, cx);
let stream = CompletionProvider::global(cx).complete(request);
let assistant_message = self
.insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
.unwrap();
@@ -1423,12 +1422,11 @@ impl Context {
let task = cx.spawn({
|this, mut cx| async move {
let response = response.await;
let assistant_message_id = assistant_message.id;
let mut response_latency = None;
let stream_completion = async {
let request_start = Instant::now();
let mut messages = response.inner.await?;
let mut messages = stream.await?;
while let Some(message) = messages.next().await {
if response_latency.is_none() {
@@ -1720,11 +1718,10 @@ impl Context {
temperature: 1.0,
};
let response = CompletionProvider::global(cx).complete(request, cx);
let stream = CompletionProvider::global(cx).complete(request);
self.pending_summary = cx.spawn(|this, mut cx| {
async move {
let response = response.await;
let mut messages = response.inner.await?;
let mut messages = stream.await?;
while let Some(message) = messages.next().await {
let text = message?;
@@ -3304,13 +3301,14 @@ impl ContextEditorToolbarItem {
let command_name = command_name.clone();
move |_cx| {
h_flex()
.gap_4()
.w_full()
.justify_between()
.child(Label::new(menu_text.clone()))
.child(
Label::new(format!("/{command_name}"))
.color(Color::Muted),
div().ml_4().child(
Label::new(format!("/{command_name}"))
.color(Color::Muted),
),
)
.into_any()
}
@@ -3644,7 +3642,7 @@ mod tests {
#[gpui::test]
fn test_inserting_and_removing_messages(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
FakeCompletionProvider::setup_test(cx);
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
cx.set_global(settings_store);
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
@@ -3776,7 +3774,7 @@ mod tests {
fn test_message_splitting(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
FakeCompletionProvider::setup_test(cx);
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
@@ -3869,7 +3867,7 @@ mod tests {
#[gpui::test]
fn test_messages_for_offsets(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx);
FakeCompletionProvider::setup_test(cx);
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
cx.set_global(settings_store);
init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
@@ -3954,8 +3952,7 @@ mod tests {
async fn test_slash_commands(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(|cx| FakeCompletionProvider::setup_test(cx));
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
cx.update(Project::init_settings);
cx.update(init);
let fs = FakeFs::new(cx.background_executor.clone());
@@ -4150,7 +4147,7 @@ mod tests {
async fn test_serialization(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store);
cx.update(FakeCompletionProvider::setup_test);
cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
cx.update(init);
let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let context =

View File

@@ -1,6 +1,5 @@
use std::fmt;
use crate::{preprocess_anthropic_request, LanguageModel, LanguageModelRequest};
pub use anthropic::Model as AnthropicModel;
use gpui::Pixels;
pub use ollama::Model as OllamaModel;
@@ -16,6 +15,8 @@ use serde::{
use settings::{Settings, SettingsSources};
use strum::{EnumIter, IntoEnumIterator};
use crate::{preprocess_anthropic_request, LanguageModel, LanguageModelRequest};
#[derive(Clone, Debug, Default, PartialEq, EnumIter)]
pub enum CloudModel {
Gpt3Point5Turbo,

View File

@@ -11,8 +11,6 @@ pub use cloud::*;
pub use fake::*;
pub use ollama::*;
pub use open_ai::*;
use parking_lot::RwLock;
use smol::lock::{Semaphore, SemaphoreGuardArc};
use crate::{
assistant_settings::{AssistantProvider, AssistantSettings},
@@ -23,8 +21,8 @@ use client::Client;
use futures::{future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, AppContext, BorrowAppContext, Task, WindowContext};
use settings::{Settings, SettingsStore};
use std::sync::Arc;
use std::time::Duration;
use std::{any::Any, sync::Arc};
/// Choose which model to use for openai provider.
/// If the model is not available, try to use the first available model, or fallback to the original model.
@@ -41,117 +39,176 @@ fn choose_openai_model(
}
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
let provider = create_provider_from_settings(client.clone(), 0, cx);
cx.set_global(CompletionProvider::new(provider, Some(client)));
let mut settings_version = 0;
let provider = match &AssistantSettings::get_global(cx).provider {
AssistantProvider::ZedDotDev { model } => CompletionProvider::Cloud(
CloudCompletionProvider::new(model.clone(), client.clone(), settings_version, cx),
),
AssistantProvider::OpenAi {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
)),
AssistantProvider::Anthropic {
model,
api_url,
low_speed_timeout_in_seconds,
} => CompletionProvider::Anthropic(AnthropicCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
)),
AssistantProvider::Ollama {
model,
api_url,
low_speed_timeout_in_seconds,
} => CompletionProvider::Ollama(OllamaCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
cx,
)),
};
cx.set_global(provider);
cx.observe_global::<SettingsStore>(move |cx| {
settings_version += 1;
cx.update_global::<CompletionProvider, _>(|provider, cx| {
provider.update_settings(settings_version, cx);
match (&mut *provider, &AssistantSettings::get_global(cx).provider) {
(
CompletionProvider::OpenAi(provider),
AssistantProvider::OpenAi {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
provider.update(
choose_openai_model(model, available_models),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
);
}
(
CompletionProvider::Anthropic(provider),
AssistantProvider::Anthropic {
model,
api_url,
low_speed_timeout_in_seconds,
},
) => {
provider.update(
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
);
}
(
CompletionProvider::Ollama(provider),
AssistantProvider::Ollama {
model,
api_url,
low_speed_timeout_in_seconds,
},
) => {
provider.update(
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
cx,
);
}
(CompletionProvider::Cloud(provider), AssistantProvider::ZedDotDev { model }) => {
provider.update(model.clone(), settings_version);
}
(_, AssistantProvider::ZedDotDev { model }) => {
*provider = CompletionProvider::Cloud(CloudCompletionProvider::new(
model.clone(),
client.clone(),
settings_version,
cx,
));
}
(
_,
AssistantProvider::OpenAi {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
},
) => {
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
choose_openai_model(model, available_models),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
));
}
(
_,
AssistantProvider::Anthropic {
model,
api_url,
low_speed_timeout_in_seconds,
},
) => {
*provider = CompletionProvider::Anthropic(AnthropicCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
));
}
(
_,
AssistantProvider::Ollama {
model,
api_url,
low_speed_timeout_in_seconds,
},
) => {
*provider = CompletionProvider::Ollama(OllamaCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
cx,
));
}
}
})
})
.detach();
}
pub struct CompletionResponse {
pub inner: BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>,
_lock: SemaphoreGuardArc,
}
pub trait LanguageModelCompletionProvider: Send + Sync {
fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel>;
fn settings_version(&self) -> usize;
fn is_authenticated(&self) -> bool;
fn authenticate(&self, cx: &AppContext) -> Task<Result<()>>;
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView;
fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>>;
fn model(&self) -> LanguageModel;
fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
) -> BoxFuture<'static, Result<usize>>;
fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
const MAX_CONCURRENT_COMPLETION_REQUESTS: usize = 4;
pub struct CompletionProvider {
provider: Arc<RwLock<dyn LanguageModelCompletionProvider>>,
client: Option<Arc<Client>>,
request_limiter: Arc<Semaphore>,
}
impl CompletionProvider {
pub fn new(
provider: Arc<RwLock<dyn LanguageModelCompletionProvider>>,
client: Option<Arc<Client>>,
) -> Self {
Self {
provider,
client,
request_limiter: Arc::new(Semaphore::new(MAX_CONCURRENT_COMPLETION_REQUESTS)),
}
}
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
self.provider.read().available_models(cx)
}
pub fn settings_version(&self) -> usize {
self.provider.read().settings_version()
}
pub fn is_authenticated(&self) -> bool {
self.provider.read().is_authenticated()
}
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
self.provider.read().authenticate(cx)
}
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
self.provider.read().authentication_prompt(cx)
}
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
self.provider.read().reset_credentials(cx)
}
pub fn model(&self) -> LanguageModel {
self.provider.read().model()
}
pub fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
self.provider.read().count_tokens(request, cx)
}
pub fn complete(
&self,
request: LanguageModelRequest,
cx: &AppContext,
) -> Task<CompletionResponse> {
let rate_limiter = self.request_limiter.clone();
let provider = self.provider.clone();
cx.background_executor().spawn(async move {
let lock = rate_limiter.acquire_arc().await;
let response = provider.read().complete(request);
CompletionResponse {
inner: response,
_lock: lock,
}
})
}
pub enum CompletionProvider {
OpenAi(OpenAiCompletionProvider),
Anthropic(AnthropicCompletionProvider),
Cloud(CloudCompletionProvider),
#[cfg(test)]
Fake(FakeCompletionProvider),
Ollama(OllamaCompletionProvider),
}
impl gpui::Global for CompletionProvider {}
@@ -161,213 +218,121 @@ impl CompletionProvider {
cx.global::<Self>()
}
pub fn update_current_as<R, T: LanguageModelCompletionProvider + 'static>(
&mut self,
update: impl FnOnce(&mut T) -> R,
) -> Option<R> {
let mut provider = self.provider.write();
if let Some(provider) = provider.as_any_mut().downcast_mut::<T>() {
Some(update(provider))
} else {
None
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
match self {
CompletionProvider::OpenAi(provider) => provider
.available_models(cx)
.map(LanguageModel::OpenAi)
.collect(),
CompletionProvider::Anthropic(provider) => provider
.available_models()
.map(LanguageModel::Anthropic)
.collect(),
CompletionProvider::Cloud(provider) => provider
.available_models()
.map(LanguageModel::Cloud)
.collect(),
CompletionProvider::Ollama(provider) => provider
.available_models()
.map(|model| LanguageModel::Ollama(model.clone()))
.collect(),
#[cfg(test)]
CompletionProvider::Fake(_) => unimplemented!(),
}
}
pub fn update_settings(&mut self, version: usize, cx: &mut AppContext) {
let updated = match &AssistantSettings::get_global(cx).provider {
AssistantProvider::ZedDotDev { model } => self
.update_current_as::<_, CloudCompletionProvider>(|provider| {
provider.update(model.clone(), version);
}),
AssistantProvider::OpenAi {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => self.update_current_as::<_, OpenAiCompletionProvider>(|provider| {
provider.update(
choose_openai_model(&model, &available_models),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
version,
);
}),
AssistantProvider::Anthropic {
model,
api_url,
low_speed_timeout_in_seconds,
} => self.update_current_as::<_, AnthropicCompletionProvider>(|provider| {
provider.update(
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
version,
);
}),
AssistantProvider::Ollama {
model,
api_url,
low_speed_timeout_in_seconds,
} => self.update_current_as::<_, OllamaCompletionProvider>(|provider| {
provider.update(
model.clone(),
api_url.clone(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
version,
cx,
);
}),
};
pub fn settings_version(&self) -> usize {
match self {
CompletionProvider::OpenAi(provider) => provider.settings_version(),
CompletionProvider::Anthropic(provider) => provider.settings_version(),
CompletionProvider::Cloud(provider) => provider.settings_version(),
CompletionProvider::Ollama(provider) => provider.settings_version(),
#[cfg(test)]
CompletionProvider::Fake(_) => unimplemented!(),
}
}
// Previously configured provider was changed to another one
if updated.is_none() {
if let Some(client) = self.client.clone() {
self.provider = create_provider_from_settings(client, version, cx);
} else {
log::warn!("completion provider cannot be created because client is not set");
}
pub fn is_authenticated(&self) -> bool {
match self {
CompletionProvider::OpenAi(provider) => provider.is_authenticated(),
CompletionProvider::Anthropic(provider) => provider.is_authenticated(),
CompletionProvider::Cloud(provider) => provider.is_authenticated(),
CompletionProvider::Ollama(provider) => provider.is_authenticated(),
#[cfg(test)]
CompletionProvider::Fake(_) => true,
}
}
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
match self {
CompletionProvider::OpenAi(provider) => provider.authenticate(cx),
CompletionProvider::Anthropic(provider) => provider.authenticate(cx),
CompletionProvider::Cloud(provider) => provider.authenticate(cx),
CompletionProvider::Ollama(provider) => provider.authenticate(cx),
#[cfg(test)]
CompletionProvider::Fake(_) => Task::ready(Ok(())),
}
}
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
match self {
CompletionProvider::OpenAi(provider) => provider.authentication_prompt(cx),
CompletionProvider::Anthropic(provider) => provider.authentication_prompt(cx),
CompletionProvider::Cloud(provider) => provider.authentication_prompt(cx),
CompletionProvider::Ollama(provider) => provider.authentication_prompt(cx),
#[cfg(test)]
CompletionProvider::Fake(_) => unimplemented!(),
}
}
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
match self {
CompletionProvider::OpenAi(provider) => provider.reset_credentials(cx),
CompletionProvider::Anthropic(provider) => provider.reset_credentials(cx),
CompletionProvider::Cloud(_) => Task::ready(Ok(())),
CompletionProvider::Ollama(provider) => provider.reset_credentials(cx),
#[cfg(test)]
CompletionProvider::Fake(_) => Task::ready(Ok(())),
}
}
pub fn model(&self) -> LanguageModel {
match self {
CompletionProvider::OpenAi(provider) => LanguageModel::OpenAi(provider.model()),
CompletionProvider::Anthropic(provider) => LanguageModel::Anthropic(provider.model()),
CompletionProvider::Cloud(provider) => LanguageModel::Cloud(provider.model()),
CompletionProvider::Ollama(provider) => LanguageModel::Ollama(provider.model()),
#[cfg(test)]
CompletionProvider::Fake(_) => LanguageModel::default(),
}
}
pub fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
match self {
CompletionProvider::OpenAi(provider) => provider.count_tokens(request, cx),
CompletionProvider::Anthropic(provider) => provider.count_tokens(request, cx),
CompletionProvider::Cloud(provider) => provider.count_tokens(request, cx),
CompletionProvider::Ollama(provider) => provider.count_tokens(request, cx),
#[cfg(test)]
CompletionProvider::Fake(_) => futures::FutureExt::boxed(futures::future::ready(Ok(0))),
}
}
pub fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
match self {
CompletionProvider::OpenAi(provider) => provider.complete(request),
CompletionProvider::Anthropic(provider) => provider.complete(request),
CompletionProvider::Cloud(provider) => provider.complete(request),
CompletionProvider::Ollama(provider) => provider.complete(request),
#[cfg(test)]
CompletionProvider::Fake(provider) => provider.complete(),
}
}
}
fn create_provider_from_settings(
client: Arc<Client>,
settings_version: usize,
cx: &mut AppContext,
) -> Arc<RwLock<dyn LanguageModelCompletionProvider>> {
match &AssistantSettings::get_global(cx).provider {
AssistantProvider::ZedDotDev { model } => Arc::new(RwLock::new(
CloudCompletionProvider::new(model.clone(), client.clone(), settings_version, cx),
)),
AssistantProvider::OpenAi {
model,
api_url,
low_speed_timeout_in_seconds,
available_models,
} => Arc::new(RwLock::new(OpenAiCompletionProvider::new(
choose_openai_model(&model, &available_models),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
))),
AssistantProvider::Anthropic {
model,
api_url,
low_speed_timeout_in_seconds,
} => Arc::new(RwLock::new(AnthropicCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
))),
AssistantProvider::Ollama {
model,
api_url,
low_speed_timeout_in_seconds,
} => Arc::new(RwLock::new(OllamaCompletionProvider::new(
model.clone(),
api_url.clone(),
client.http_client(),
low_speed_timeout_in_seconds.map(Duration::from_secs),
settings_version,
cx,
))),
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use gpui::AppContext;
use parking_lot::RwLock;
use settings::SettingsStore;
use smol::stream::StreamExt;
use crate::{
completion_provider::MAX_CONCURRENT_COMPLETION_REQUESTS, CompletionProvider,
FakeCompletionProvider, LanguageModelRequest,
};
#[gpui::test]
fn test_rate_limiting(cx: &mut AppContext) {
SettingsStore::test(cx);
let fake_provider = FakeCompletionProvider::setup_test(cx);
let provider = CompletionProvider::new(Arc::new(RwLock::new(fake_provider.clone())), None);
// Enqueue some requests
for i in 0..MAX_CONCURRENT_COMPLETION_REQUESTS * 2 {
let response = provider.complete(
LanguageModelRequest {
temperature: i as f32 / 10.0,
..Default::default()
},
cx,
);
cx.background_executor()
.spawn(async move {
let response = response.await;
let mut stream = response.inner.await.unwrap();
while let Some(message) = stream.next().await {
message.unwrap();
}
})
.detach();
}
cx.background_executor().run_until_parked();
assert_eq!(
fake_provider.completion_count(),
MAX_CONCURRENT_COMPLETION_REQUESTS
);
// Get the first completion request that is in flight and mark it as completed.
let completion = fake_provider
.running_completions()
.into_iter()
.next()
.unwrap();
fake_provider.finish_completion(&completion);
// Ensure that the number of in-flight completion requests is reduced.
assert_eq!(
fake_provider.completion_count(),
MAX_CONCURRENT_COMPLETION_REQUESTS - 1
);
cx.background_executor().run_until_parked();
// Ensure that another completion request was allowed to acquire the lock.
assert_eq!(
fake_provider.completion_count(),
MAX_CONCURRENT_COMPLETION_REQUESTS
);
// Mark all completion requests as finished that are in flight.
for request in fake_provider.running_completions() {
fake_provider.finish_completion(&request);
}
assert_eq!(fake_provider.completion_count(), 0);
// Wait until the background tasks acquire the lock again.
cx.background_executor().run_until_parked();
assert_eq!(
fake_provider.completion_count(),
MAX_CONCURRENT_COMPLETION_REQUESTS - 1
);
// Finish all remaining completion requests.
for request in fake_provider.running_completions() {
fake_provider.finish_completion(&request);
}
cx.background_executor().run_until_parked();
assert_eq!(fake_provider.completion_count(), 0);
}
}

View File

@@ -2,7 +2,7 @@ use crate::{
assistant_settings::AnthropicModel, CompletionProvider, LanguageModel, LanguageModelRequest,
Role,
};
use crate::{count_open_ai_tokens, LanguageModelCompletionProvider, LanguageModelRequestMessage};
use crate::{count_open_ai_tokens, LanguageModelRequestMessage};
use anthropic::{stream_completion, Request, RequestMessage};
use anyhow::{anyhow, Result};
use editor::{Editor, EditorElement, EditorStyle};
@@ -26,22 +26,50 @@ pub struct AnthropicCompletionProvider {
settings_version: usize,
}
impl LanguageModelCompletionProvider for AnthropicCompletionProvider {
fn available_models(&self, _cx: &AppContext) -> Vec<LanguageModel> {
AnthropicModel::iter()
.map(LanguageModel::Anthropic)
.collect()
impl AnthropicCompletionProvider {
pub fn new(
model: AnthropicModel,
api_url: String,
http_client: Arc<dyn HttpClient>,
low_speed_timeout: Option<Duration>,
settings_version: usize,
) -> Self {
Self {
api_key: None,
api_url,
model,
http_client,
low_speed_timeout,
settings_version,
}
}
fn settings_version(&self) -> usize {
pub fn update(
&mut self,
model: AnthropicModel,
api_url: String,
low_speed_timeout: Option<Duration>,
settings_version: usize,
) {
self.model = model;
self.api_url = api_url;
self.low_speed_timeout = low_speed_timeout;
self.settings_version = settings_version;
}
pub fn available_models(&self) -> impl Iterator<Item = AnthropicModel> {
AnthropicModel::iter()
}
pub fn settings_version(&self) -> usize {
self.settings_version
}
fn is_authenticated(&self) -> bool {
pub fn is_authenticated(&self) -> bool {
self.api_key.is_some()
}
fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@@ -57,36 +85,36 @@ impl LanguageModelCompletionProvider for AnthropicCompletionProvider {
String::from_utf8(api_key)?
};
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, AnthropicCompletionProvider>(|provider| {
if let CompletionProvider::Anthropic(provider) = provider {
provider.api_key = Some(api_key);
});
}
})
})
}
}
fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
let delete_credentials = cx.delete_credentials(&self.api_url);
cx.spawn(|mut cx| async move {
delete_credentials.await.log_err();
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, AnthropicCompletionProvider>(|provider| {
if let CompletionProvider::Anthropic(provider) = provider {
provider.api_key = None;
});
}
})
})
}
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|cx| AuthenticationPrompt::new(self.api_url.clone(), cx))
.into()
}
fn model(&self) -> LanguageModel {
LanguageModel::Anthropic(self.model.clone())
pub fn model(&self) -> AnthropicModel {
self.model.clone()
}
fn count_tokens(
pub fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
@@ -94,7 +122,7 @@ impl LanguageModelCompletionProvider for AnthropicCompletionProvider {
count_open_ai_tokens(request, cx.background_executor())
}
fn complete(
pub fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
@@ -139,48 +167,12 @@ impl LanguageModelCompletionProvider for AnthropicCompletionProvider {
.boxed()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl AnthropicCompletionProvider {
pub fn new(
model: AnthropicModel,
api_url: String,
http_client: Arc<dyn HttpClient>,
low_speed_timeout: Option<Duration>,
settings_version: usize,
) -> Self {
Self {
api_key: None,
api_url,
model,
http_client,
low_speed_timeout,
settings_version,
}
}
pub fn update(
&mut self,
model: AnthropicModel,
api_url: String,
low_speed_timeout: Option<Duration>,
settings_version: usize,
) {
self.model = model;
self.api_url = api_url;
self.low_speed_timeout = low_speed_timeout;
self.settings_version = settings_version;
}
fn to_anthropic_request(&self, mut request: LanguageModelRequest) -> Request {
preprocess_anthropic_request(&mut request);
let model = match request.model {
LanguageModel::Anthropic(model) => model,
_ => self.model.clone(),
_ => self.model(),
};
let mut system_message = String::new();
@@ -286,9 +278,9 @@ impl AuthenticationPrompt {
cx.spawn(|_, mut cx| async move {
write_credentials.await?;
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, AnthropicCompletionProvider>(|provider| {
if let CompletionProvider::Anthropic(provider) = provider {
provider.api_key = Some(api_key);
});
}
})
})
.detach_and_log_err(cx);

View File

@@ -1,6 +1,6 @@
use crate::{
assistant_settings::CloudModel, count_open_ai_tokens, CompletionProvider, LanguageModel,
LanguageModelCompletionProvider, LanguageModelRequest,
LanguageModelRequest,
};
use anyhow::{anyhow, Result};
use client::{proto, Client};
@@ -30,9 +30,11 @@ impl CloudCompletionProvider {
let maintain_client_status = cx.spawn(|mut cx| async move {
while let Some(status) = status_rx.next().await {
let _ = cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, Self>(|provider| {
if let CompletionProvider::Cloud(provider) = provider {
provider.status = status;
});
} else {
unreachable!()
}
});
}
});
@@ -49,53 +51,44 @@ impl CloudCompletionProvider {
self.model = model;
self.settings_version = settings_version;
}
}
impl LanguageModelCompletionProvider for CloudCompletionProvider {
fn available_models(&self, _cx: &AppContext) -> Vec<LanguageModel> {
pub fn available_models(&self) -> impl Iterator<Item = CloudModel> {
let mut custom_model = if let CloudModel::Custom(custom_model) = self.model.clone() {
Some(custom_model)
} else {
None
};
CloudModel::iter()
.filter_map(move |model| {
if let CloudModel::Custom(_) = model {
Some(CloudModel::Custom(custom_model.take()?))
} else {
Some(model)
}
})
.map(LanguageModel::Cloud)
.collect()
CloudModel::iter().filter_map(move |model| {
if let CloudModel::Custom(_) = model {
Some(CloudModel::Custom(custom_model.take()?))
} else {
Some(model)
}
})
}
fn settings_version(&self) -> usize {
pub fn settings_version(&self) -> usize {
self.settings_version
}
fn is_authenticated(&self) -> bool {
pub fn model(&self) -> CloudModel {
self.model.clone()
}
pub fn is_authenticated(&self) -> bool {
self.status.is_connected()
}
fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
let client = self.client.clone();
cx.spawn(move |cx| async move { client.authenticate_and_connect(true, &cx).await })
}
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|_cx| AuthenticationPrompt).into()
}
fn reset_credentials(&self, _cx: &AppContext) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn model(&self) -> LanguageModel {
LanguageModel::Cloud(self.model.clone())
}
fn count_tokens(
pub fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
@@ -135,7 +128,7 @@ impl LanguageModelCompletionProvider for CloudCompletionProvider {
}
}
fn complete(
pub fn complete(
&self,
mut request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
@@ -168,10 +161,6 @@ impl LanguageModelCompletionProvider for CloudCompletionProvider {
})
.boxed()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
struct AuthenticationPrompt;

View File

@@ -1,107 +1,29 @@
use anyhow::Result;
use collections::HashMap;
use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
use gpui::{AnyView, AppContext, Task};
use std::sync::Arc;
use ui::WindowContext;
use crate::{LanguageModel, LanguageModelCompletionProvider, LanguageModelRequest};
#[derive(Clone, Default)]
pub struct FakeCompletionProvider {
current_completion_txs: Arc<parking_lot::Mutex<HashMap<String, mpsc::UnboundedSender<String>>>>,
current_completion_tx: Arc<parking_lot::Mutex<Option<mpsc::UnboundedSender<String>>>>,
}
impl FakeCompletionProvider {
#[cfg(test)]
pub fn setup_test(cx: &mut AppContext) -> Self {
use crate::CompletionProvider;
use parking_lot::RwLock;
let this = Self::default();
let provider = CompletionProvider::new(Arc::new(RwLock::new(this.clone())), None);
cx.set_global(provider);
this
pub fn complete(&self) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
let (tx, rx) = mpsc::unbounded();
*self.current_completion_tx.lock() = Some(tx);
async move { Ok(rx.map(Ok).boxed()) }.boxed()
}
pub fn running_completions(&self) -> Vec<LanguageModelRequest> {
self.current_completion_txs
pub fn send_completion(&self, chunk: String) {
self.current_completion_tx
.lock()
.keys()
.map(|k| serde_json::from_str(k).unwrap())
.collect()
}
pub fn completion_count(&self) -> usize {
self.current_completion_txs.lock().len()
}
pub fn send_completion(&self, request: &LanguageModelRequest, chunk: String) {
let json = serde_json::to_string(request).unwrap();
self.current_completion_txs
.lock()
.get(&json)
.as_ref()
.unwrap()
.unbounded_send(chunk)
.unwrap();
}
pub fn finish_completion(&self, request: &LanguageModelRequest) {
self.current_completion_txs
.lock()
.remove(&serde_json::to_string(request).unwrap());
}
}
impl LanguageModelCompletionProvider for FakeCompletionProvider {
fn available_models(&self, _cx: &AppContext) -> Vec<LanguageModel> {
vec![LanguageModel::default()]
}
fn settings_version(&self) -> usize {
0
}
fn is_authenticated(&self) -> bool {
true
}
fn authenticate(&self, _cx: &AppContext) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn authentication_prompt(&self, _cx: &mut WindowContext) -> AnyView {
unimplemented!()
}
fn reset_credentials(&self, _cx: &AppContext) -> Task<Result<()>> {
Task::ready(Ok(()))
}
fn model(&self) -> LanguageModel {
LanguageModel::default()
}
fn count_tokens(
&self,
_request: LanguageModelRequest,
_cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
futures::future::ready(Ok(0)).boxed()
}
fn complete(
&self,
_request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
let (tx, rx) = mpsc::unbounded();
self.current_completion_txs
.lock()
.insert(serde_json::to_string(&_request).unwrap(), tx);
async move { Ok(rx.map(Ok).boxed()) }.boxed()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
pub fn finish_completion(&self) {
self.current_completion_tx.lock().take();
}
}

View File

@@ -1,4 +1,3 @@
use crate::LanguageModelCompletionProvider;
use crate::{
assistant_settings::OllamaModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -27,108 +26,6 @@ pub struct OllamaCompletionProvider {
available_models: Vec<OllamaModel>,
}
impl LanguageModelCompletionProvider for OllamaCompletionProvider {
fn available_models(&self, _cx: &AppContext) -> Vec<LanguageModel> {
self.available_models
.iter()
.map(|m| LanguageModel::Ollama(m.clone()))
.collect()
}
fn settings_version(&self) -> usize {
self.settings_version
}
fn is_authenticated(&self) -> bool {
!self.available_models.is_empty()
}
fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
self.fetch_models(cx)
}
}
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
let fetch_models = Box::new(move |cx: &mut WindowContext| {
cx.update_global::<CompletionProvider, _>(|provider, cx| {
provider
.update_current_as::<_, OllamaCompletionProvider>(|provider| {
provider.fetch_models(cx)
})
.unwrap_or_else(|| Task::ready(Ok(())))
})
});
cx.new_view(|cx| DownloadOllamaMessage::new(fetch_models, cx))
.into()
}
fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
self.fetch_models(cx)
}
fn model(&self) -> LanguageModel {
LanguageModel::Ollama(self.model.clone())
}
fn count_tokens(
&self,
request: LanguageModelRequest,
_cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
// There is no endpoint for this _yet_ in Ollama
// see: https://github.com/ollama/ollama/issues/1716 and https://github.com/ollama/ollama/issues/3582
let token_count = request
.messages
.iter()
.map(|msg| msg.content.chars().count())
.sum::<usize>()
/ 4;
async move { Ok(token_count) }.boxed()
}
fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
let request = self.to_ollama_request(request);
let http_client = self.http_client.clone();
let api_url = self.api_url.clone();
let low_speed_timeout = self.low_speed_timeout;
async move {
let request =
stream_chat_completion(http_client.as_ref(), &api_url, request, low_speed_timeout);
let response = request.await?;
let stream = response
.filter_map(|response| async move {
match response {
Ok(delta) => {
let content = match delta.message {
ChatMessage::User { content } => content,
ChatMessage::Assistant { content } => content,
ChatMessage::System { content } => content,
};
Some(Ok(content))
}
Err(error) => Some(Err(error)),
}
})
.boxed();
Ok(stream)
}
.boxed()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
impl OllamaCompletionProvider {
pub fn new(
model: OllamaModel,
@@ -190,12 +87,36 @@ impl OllamaCompletionProvider {
self.settings_version = settings_version;
}
pub fn available_models(&self) -> impl Iterator<Item = &OllamaModel> {
self.available_models.iter()
}
pub fn select_first_available_model(&mut self) {
if let Some(model) = self.available_models.first() {
self.model = model.clone();
}
}
pub fn settings_version(&self) -> usize {
self.settings_version
}
pub fn is_authenticated(&self) -> bool {
!self.available_models.is_empty()
}
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
self.fetch_models(cx)
}
}
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
self.fetch_models(cx)
}
pub fn fetch_models(&self, cx: &AppContext) -> Task<Result<()>> {
let http_client = self.http_client.clone();
let api_url = self.api_url.clone();
@@ -216,21 +137,90 @@ impl OllamaCompletionProvider {
models.sort_by(|a, b| a.name.cmp(&b.name));
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, OllamaCompletionProvider>(|provider| {
if let CompletionProvider::Ollama(provider) = provider {
provider.available_models = models;
if !provider.available_models.is_empty() && provider.model.name.is_empty() {
provider.select_first_available_model()
}
});
}
})
})
}
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
let fetch_models = Box::new(move |cx: &mut WindowContext| {
cx.update_global::<CompletionProvider, _>(|provider, cx| {
if let CompletionProvider::Ollama(provider) = provider {
provider.fetch_models(cx)
} else {
Task::ready(Ok(()))
}
})
});
cx.new_view(|cx| DownloadOllamaMessage::new(fetch_models, cx))
.into()
}
pub fn model(&self) -> OllamaModel {
self.model.clone()
}
pub fn count_tokens(
&self,
request: LanguageModelRequest,
_cx: &AppContext,
) -> BoxFuture<'static, Result<usize>> {
// There is no endpoint for this _yet_ in Ollama
// see: https://github.com/ollama/ollama/issues/1716 and https://github.com/ollama/ollama/issues/3582
let token_count = request
.messages
.iter()
.map(|msg| msg.content.chars().count())
.sum::<usize>()
/ 4;
async move { Ok(token_count) }.boxed()
}
pub fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
let request = self.to_ollama_request(request);
let http_client = self.http_client.clone();
let api_url = self.api_url.clone();
let low_speed_timeout = self.low_speed_timeout;
async move {
let request =
stream_chat_completion(http_client.as_ref(), &api_url, request, low_speed_timeout);
let response = request.await?;
let stream = response
.filter_map(|response| async move {
match response {
Ok(delta) => {
let content = match delta.message {
ChatMessage::User { content } => content,
ChatMessage::Assistant { content } => content,
ChatMessage::System { content } => content,
};
Some(Ok(content))
}
Err(error) => Some(Err(error)),
}
})
.boxed();
Ok(stream)
}
.boxed()
}
fn to_ollama_request(&self, request: LanguageModelRequest) -> ChatRequest {
let model = match request.model {
LanguageModel::Ollama(model) => model,
_ => self.model.clone(),
_ => self.model(),
};
ChatRequest {

View File

@@ -1,6 +1,5 @@
use crate::assistant_settings::CloudModel;
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
use crate::LanguageModelCompletionProvider;
use crate::{
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
};
@@ -58,75 +57,37 @@ impl OpenAiCompletionProvider {
self.settings_version = settings_version;
}
fn to_open_ai_request(&self, request: LanguageModelRequest) -> Request {
let model = match request.model {
LanguageModel::OpenAi(model) => model,
_ => self.model.clone(),
};
Request {
model,
messages: request
.messages
.into_iter()
.map(|msg| match msg.role {
Role::User => RequestMessage::User {
content: msg.content,
},
Role::Assistant => RequestMessage::Assistant {
content: Some(msg.content),
tool_calls: Vec::new(),
},
Role::System => RequestMessage::System {
content: msg.content,
},
})
.collect(),
stream: true,
stop: request.stop,
temperature: request.temperature,
tools: Vec::new(),
tool_choice: None,
}
}
}
impl LanguageModelCompletionProvider for OpenAiCompletionProvider {
fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
pub fn available_models(&self, cx: &AppContext) -> impl Iterator<Item = OpenAiModel> {
if let AssistantProvider::OpenAi {
available_models, ..
} = &AssistantSettings::get_global(cx).provider
{
if !available_models.is_empty() {
return available_models
.iter()
.cloned()
.map(LanguageModel::OpenAi)
.collect();
// available_models is set, just return it
return available_models.clone().into_iter();
}
}
let available_models = if matches!(self.model, OpenAiModel::Custom { .. }) {
// available_models is not set but the default model is set to custom, only show custom
vec![self.model.clone()]
} else {
// default case, use all models except custom
OpenAiModel::iter()
.filter(|model| !matches!(model, OpenAiModel::Custom { .. }))
.collect()
};
available_models
.into_iter()
.map(LanguageModel::OpenAi)
.collect()
available_models.into_iter()
}
fn settings_version(&self) -> usize {
pub fn settings_version(&self) -> usize {
self.settings_version
}
fn is_authenticated(&self) -> bool {
pub fn is_authenticated(&self) -> bool {
self.api_key.is_some()
}
fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
pub fn authenticate(&self, cx: &AppContext) -> Task<Result<()>> {
if self.is_authenticated() {
Task::ready(Ok(()))
} else {
@@ -142,36 +103,36 @@ impl LanguageModelCompletionProvider for OpenAiCompletionProvider {
String::from_utf8(api_key)?
};
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, Self>(|provider| {
if let CompletionProvider::OpenAi(provider) = provider {
provider.api_key = Some(api_key);
});
}
})
})
}
}
fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
pub fn reset_credentials(&self, cx: &AppContext) -> Task<Result<()>> {
let delete_credentials = cx.delete_credentials(&self.api_url);
cx.spawn(|mut cx| async move {
delete_credentials.await.log_err();
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, Self>(|provider| {
if let CompletionProvider::OpenAi(provider) = provider {
provider.api_key = None;
});
}
})
})
}
fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
pub fn authentication_prompt(&self, cx: &mut WindowContext) -> AnyView {
cx.new_view(|cx| AuthenticationPrompt::new(self.api_url.clone(), cx))
.into()
}
fn model(&self) -> LanguageModel {
LanguageModel::OpenAi(self.model.clone())
pub fn model(&self) -> OpenAiModel {
self.model.clone()
}
fn count_tokens(
pub fn count_tokens(
&self,
request: LanguageModelRequest,
cx: &AppContext,
@@ -179,7 +140,7 @@ impl LanguageModelCompletionProvider for OpenAiCompletionProvider {
count_open_ai_tokens(request, cx.background_executor())
}
fn complete(
pub fn complete(
&self,
request: LanguageModelRequest,
) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
@@ -212,8 +173,36 @@ impl LanguageModelCompletionProvider for OpenAiCompletionProvider {
.boxed()
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
fn to_open_ai_request(&self, request: LanguageModelRequest) -> Request {
let model = match request.model {
LanguageModel::OpenAi(model) => model,
_ => self.model(),
};
Request {
model,
messages: request
.messages
.into_iter()
.map(|msg| match msg.role {
Role::User => RequestMessage::User {
content: msg.content,
},
Role::Assistant => RequestMessage::Assistant {
content: Some(msg.content),
tool_calls: Vec::new(),
},
Role::System => RequestMessage::System {
content: msg.content,
},
})
.collect(),
stream: true,
stop: request.stop,
temperature: request.temperature,
tools: Vec::new(),
tool_choice: None,
}
}
}
@@ -295,9 +284,9 @@ impl AuthenticationPrompt {
cx.spawn(|_, mut cx| async move {
write_credentials.await?;
cx.update_global::<CompletionProvider, _>(|provider, _cx| {
provider.update_current_as::<_, OpenAiCompletionProvider>(|provider| {
if let CompletionProvider::OpenAi(provider) = provider {
provider.api_key = Some(api_key);
});
}
})
})
.detach_and_log_err(cx);

View File

@@ -1986,14 +1986,13 @@ impl Codegen {
.unwrap_or_else(|| snapshot.indent_size_for_line(MultiBufferRow(selection_start.row)));
let model_telemetry_id = prompt.model.telemetry_id();
let response = CompletionProvider::global(cx).complete(prompt, cx);
let response = CompletionProvider::global(cx).complete(prompt);
let telemetry = self.telemetry.clone();
self.edit_position = range.start;
self.diff = Diff::default();
self.status = CodegenStatus::Pending;
self.generation = cx.spawn(|this, mut cx| {
async move {
let response = response.await;
let generate = async {
let mut edit_start = range.start.to_offset(&snapshot);
@@ -2003,7 +2002,7 @@ impl Codegen {
let mut response_latency = None;
let request_start = Instant::now();
let diff = async {
let chunks = StripInvalidSpans::new(response.inner.await?);
let chunks = StripInvalidSpans::new(response.await?);
futures::pin_mut!(chunks);
let mut diff = StreamingDiff::new(selected_text.to_string());
@@ -2474,8 +2473,9 @@ mod tests {
#[gpui::test(iterations = 10)]
async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) {
let provider = FakeCompletionProvider::default();
cx.set_global(cx.update(SettingsStore::test));
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
cx.set_global(CompletionProvider::Fake(provider.clone()));
cx.update(language_settings::init);
let text = indoc! {"
@@ -2495,11 +2495,8 @@ mod tests {
});
let codegen = cx.new_model(|cx| Codegen::new(buffer.clone(), range, None, cx));
codegen.update(cx, |codegen, cx| {
codegen.start(LanguageModelRequest::default(), cx)
});
cx.background_executor.run_until_parked();
let request = LanguageModelRequest::default();
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
let mut new_text = concat!(
" let mut x = 0;\n",
@@ -2511,11 +2508,11 @@ mod tests {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
provider.send_completion(chunk.into());
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion(&LanguageModelRequest::default());
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(
@@ -2536,7 +2533,8 @@ mod tests {
cx: &mut TestAppContext,
mut rng: StdRng,
) {
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
let provider = FakeCompletionProvider::default();
cx.set_global(CompletionProvider::Fake(provider.clone()));
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
@@ -2557,8 +2555,6 @@ mod tests {
let request = LanguageModelRequest::default();
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
cx.background_executor.run_until_parked();
let mut new_text = concat!(
"t mut x = 0;\n",
"while x < 10 {\n",
@@ -2569,11 +2565,11 @@ mod tests {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
provider.send_completion(chunk.into());
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion(&LanguageModelRequest::default());
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(
@@ -2594,7 +2590,8 @@ mod tests {
cx: &mut TestAppContext,
mut rng: StdRng,
) {
let provider = cx.update(|cx| FakeCompletionProvider::setup_test(cx));
let provider = FakeCompletionProvider::default();
cx.set_global(CompletionProvider::Fake(provider.clone()));
cx.set_global(cx.update(SettingsStore::test));
cx.update(language_settings::init);
@@ -2615,8 +2612,6 @@ mod tests {
let request = LanguageModelRequest::default();
codegen.update(cx, |codegen, cx| codegen.start(request, cx));
cx.background_executor.run_until_parked();
let mut new_text = concat!(
"let mut x = 0;\n",
"while x < 10 {\n",
@@ -2627,11 +2622,11 @@ mod tests {
let max_len = cmp::min(new_text.len(), 10);
let len = rng.gen_range(1..=max_len);
let (chunk, suffix) = new_text.split_at(len);
provider.send_completion(&LanguageModelRequest::default(), chunk.into());
provider.send_completion(chunk.into());
new_text = suffix;
cx.background_executor.run_until_parked();
}
provider.finish_completion(&LanguageModelRequest::default());
provider.finish_completion();
cx.background_executor.run_until_parked();
assert_eq!(

View File

@@ -49,7 +49,6 @@ impl RenderOnce for ModelSelector {
})
.trigger(
ButtonLike::new("active-model")
.style(ButtonStyle::Subtle)
.child(
h_flex()
.w_full()
@@ -68,11 +67,14 @@ impl RenderOnce for ModelSelector {
),
)
.child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
div().child(
Icon::new(IconName::ChevronDown)
.color(Color::Muted)
.size(IconSize::XSmall),
),
),
)
.style(ButtonStyle::Subtle)
.tooltip(move |cx| {
Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
}),

View File

@@ -170,7 +170,7 @@ impl SlashCommandCompletionProvider {
.await?
.into_iter()
.map(|command_argument| {
let confirm = if command_argument.run_command {
let confirm =
editor
.clone()
.zip(workspace.clone())
@@ -178,7 +178,7 @@ impl SlashCommandCompletionProvider {
Arc::new({
let command_range = command_range.clone();
let command_name = command_name.clone();
let command_argument = command_argument.new_text.clone();
let command_argument = command_argument.clone();
move |cx: &mut WindowContext| {
editor
.update(cx, |editor, cx| {
@@ -194,24 +194,15 @@ impl SlashCommandCompletionProvider {
.ok();
}
}) as Arc<_>
})
} else {
None
};
let mut new_text = command_argument.new_text.clone();
if !command_argument.run_command {
new_text.push(' ');
}
});
project::Completion {
old_range: argument_range.clone(),
label: CodeLabel::plain(command_argument.label, None),
new_text,
label: CodeLabel::plain(command_argument.clone(), None),
new_text: command_argument.clone(),
documentation: None,
server_id: LanguageServerId(0),
lsp_completion: Default::default(),
show_new_completions_on_confirm: !command_argument.run_command,
show_new_completions_on_confirm: false,
confirm,
}
})

View File

@@ -4,7 +4,6 @@ use super::{
SlashCommand, SlashCommandOutput,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::ArgumentCompletion;
use editor::Editor;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
@@ -34,7 +33,7 @@ impl SlashCommand for ActiveSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}

View File

@@ -1,7 +1,7 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::{
@@ -36,7 +36,7 @@ impl SlashCommand for DefaultSlashCommand {
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}

View File

@@ -1,6 +1,6 @@
use super::{create_label_for_command, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::{PathMatch, StringMatchCandidate};
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{
@@ -108,7 +108,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -143,14 +143,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
.map(|candidate| candidate.string),
);
Ok(matches
.into_iter()
.map(|completion| ArgumentCompletion {
label: completion.clone(),
new_text: completion,
run_command: true,
})
.collect())
Ok(matches)
})
}

View File

@@ -3,9 +3,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::{anyhow, bail, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use gpui::{AppContext, Model, Task, WeakView};
use indexed_docs::{
IndexedDocsRegistry, IndexedDocsStore, LocalProvider, PackageName, ProviderId, RustdocIndexer,
@@ -94,7 +92,7 @@ impl SlashCommand for DocsSlashCommand {
_cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
self.ensure_rustdoc_provider_is_registered(workspace, cx);
let indexed_docs_registry = IndexedDocsRegistry::global(cx);
@@ -104,17 +102,15 @@ impl SlashCommand for DocsSlashCommand {
.ok_or_else(|| anyhow!("no docs provider specified"))
.and_then(|provider| IndexedDocsStore::try_global(provider, cx));
cx.background_executor().spawn(async move {
fn build_completions(
provider: ProviderId,
items: Vec<String>,
) -> Vec<ArgumentCompletion> {
/// HACK: Prefixes the completions with the provider ID so that it doesn't get deleted
/// when a completion is accepted.
///
/// We will likely want to extend `complete_argument` with support for replacing just
/// a particular range of the argument when a completion is accepted.
fn prefix_with_provider(provider: ProviderId, items: Vec<String>) -> Vec<String> {
items
.into_iter()
.map(|item| ArgumentCompletion {
label: item.clone(),
new_text: format!("{provider} {item}"),
run_command: true,
})
.map(|item| format!("{provider} {item}"))
.collect()
}
@@ -123,11 +119,7 @@ impl SlashCommand for DocsSlashCommand {
let providers = indexed_docs_registry.list_providers();
Ok(providers
.into_iter()
.map(|provider| ArgumentCompletion {
label: provider.to_string(),
new_text: provider.to_string(),
run_command: false,
})
.map(|provider| provider.to_string())
.collect())
}
DocsSlashCommandArgs::SearchPackageDocs {
@@ -144,7 +136,7 @@ impl SlashCommand for DocsSlashCommand {
}
let items = store.search(package).await;
Ok(build_completions(provider, items))
Ok(prefix_with_provider(provider, items))
}
DocsSlashCommandArgs::SearchItemDocs {
provider,
@@ -153,7 +145,7 @@ impl SlashCommand for DocsSlashCommand {
} => {
let store = store?;
let items = store.search(item_path).await;
Ok(build_completions(provider, items))
Ok(prefix_with_provider(provider, items))
}
}
})

View File

@@ -4,9 +4,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::{anyhow, bail, Context, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use futures::AsyncReadExt;
use gpui::{AppContext, Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
@@ -121,7 +119,7 @@ impl SlashCommand for FetchSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
}

View File

@@ -1,6 +1,6 @@
use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, LineEnding, LspAdapterDelegate};
@@ -105,7 +105,7 @@ impl SlashCommand for FileSlashCommand {
cancellation_flag: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
let Some(workspace) = workspace.and_then(|workspace| workspace.upgrade()) else {
return Task::ready(Err(anyhow!("workspace was dropped")));
};
@@ -116,17 +116,11 @@ impl SlashCommand for FileSlashCommand {
.await
.into_iter()
.map(|path_match| {
let text = format!(
format!(
"{}{}",
path_match.path_prefix,
path_match.path.to_string_lossy()
);
ArgumentCompletion {
label: text.clone(),
new_text: text,
run_command: true,
}
)
})
.collect())
})

View File

@@ -2,9 +2,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use chrono::Local;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
@@ -36,7 +34,7 @@ impl SlashCommand for NowSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
}

View File

@@ -1,6 +1,6 @@
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
use language::LspAdapterDelegate;
@@ -107,7 +107,7 @@ impl SlashCommand for ProjectSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}

View File

@@ -1,7 +1,7 @@
use super::{SlashCommand, SlashCommandOutput};
use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::sync::{atomic::AtomicBool, Arc};
@@ -33,20 +33,13 @@ impl SlashCommand for PromptSlashCommand {
_cancellation_flag: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
let store = PromptStore::global(cx);
cx.background_executor().spawn(async move {
let prompts = store.await?.search(query).await;
Ok(prompts
.into_iter()
.filter_map(|prompt| {
let prompt_title = prompt.title?.to_string();
Some(ArgumentCompletion {
label: prompt_title.clone(),
new_text: prompt_title,
run_command: true,
})
})
.filter_map(|prompt| Some(prompt.title?.to_string()))
.collect())
})
}

View File

@@ -4,7 +4,7 @@ use super::{
SlashCommand, SlashCommandOutput,
};
use anyhow::Result;
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use assistant_slash_command::SlashCommandOutputSection;
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LineEnding, LspAdapterDelegate};
use semantic_index::SemanticIndex;
@@ -46,7 +46,7 @@ impl SlashCommand for SearchSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(Vec::new()))
}

View File

@@ -4,7 +4,6 @@ use super::{
SlashCommand, SlashCommandOutput,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::ArgumentCompletion;
use collections::HashMap;
use editor::Editor;
use gpui::{AppContext, Entity, Task, WeakView};
@@ -38,7 +37,7 @@ impl SlashCommand for TabsSlashCommand {
_cancel: Arc<std::sync::atomic::AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
Task::ready(Err(anyhow!("this command does not require argument")))
}

View File

@@ -2,9 +2,7 @@ use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use anyhow::Result;
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use gpui::{AppContext, Task, WeakView};
use language::{CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
@@ -44,12 +42,8 @@ impl SlashCommand for TermSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
Task::ready(Ok(vec![ArgumentCompletion {
label: LINE_COUNT_ARG.to_string(),
new_text: LINE_COUNT_ARG.to_string(),
run_command: true,
}]))
) -> Task<Result<Vec<String>>> {
Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()]))
}
fn run(

View File

@@ -1026,10 +1026,9 @@ impl Codegen {
let telemetry = self.telemetry.clone();
let model_telemetry_id = prompt.model.telemetry_id();
let response = CompletionProvider::global(cx).complete(prompt, cx);
let response = CompletionProvider::global(cx).complete(prompt);
self.generation = cx.spawn(|this, mut cx| async move {
let response = response.await;
let generate = async {
let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1);
@@ -1037,7 +1036,7 @@ impl Codegen {
let mut response_latency = None;
let request_start = Instant::now();
let task = async {
let mut response = response.inner.await?;
let mut response = response.await?;
while let Some(chunk) = response.next().await {
if response_latency.is_none() {
response_latency = Some(request_start.elapsed());

View File

@@ -15,16 +15,6 @@ pub fn init(cx: &mut AppContext) {
SlashCommandRegistry::default_global(cx);
}
#[derive(Debug)]
pub struct ArgumentCompletion {
/// The label to display for this completion.
pub label: String,
/// The new text that should be inserted into the command when this completion is accepted.
pub new_text: String,
/// Whether the command should be run when accepting this completion.
pub run_command: bool,
}
pub trait SlashCommand: 'static + Send + Sync {
fn name(&self) -> String;
fn label(&self, _cx: &AppContext) -> CodeLabel {
@@ -38,7 +28,7 @@ pub trait SlashCommand: 'static + Send + Sync {
cancel: Arc<AtomicBool>,
workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>>;
) -> Task<Result<Vec<String>>>;
fn requires_argument(&self) -> bool;
fn run(
self: Arc<Self>,

View File

@@ -227,7 +227,7 @@ impl Telemetry {
let state = state.clone();
async move {
if let Some(tempfile) =
NamedTempFile::new_in(paths::logs_dir().as_path()).log_err()
NamedTempFile::new_in(paths::config_dir().as_path()).log_err()
{
state.lock().log_file = Some(tempfile);
}

View File

@@ -355,10 +355,11 @@ impl ChatPanel {
.child(Icon::new(IconName::ReplyArrowRight).color(Color::Muted))
.child(Avatar::new(user_being_replied_to.avatar_uri.clone()).size(rems(0.7)))
.child(
Label::new(format!("@{}", user_being_replied_to.github_login))
.size(LabelSize::XSmall)
.weight(FontWeight::SEMIBOLD)
.color(Color::Muted),
div().font_weight(FontWeight::SEMIBOLD).child(
Label::new(format!("@{}", user_being_replied_to.github_login))
.size(LabelSize::XSmall)
.color(Color::Muted),
),
)
.child(
div().overflow_y_hidden().child(
@@ -489,16 +490,22 @@ impl ChatPanel {
|this| {
this.child(
h_flex()
.gap_2()
.text_ui_sm(cx)
.child(
Avatar::new(message.sender.avatar_uri.clone())
.size(rems(1.)),
div().absolute().child(
Avatar::new(message.sender.avatar_uri.clone())
.size(rems(1.)),
),
)
.child(
Label::new(message.sender.github_login.clone())
.size(LabelSize::Small)
.weight(FontWeight::BOLD),
div()
.pl(cx.rem_size() + px(6.0))
.pr(px(8.0))
.font_weight(FontWeight::BOLD)
.child(
Label::new(message.sender.github_login.clone())
.size(LabelSize::Small),
),
)
.child(
Label::new(time_format::format_localized_timestamp(
@@ -1037,12 +1044,13 @@ impl Render for ChatPanel {
.id(("reply-preview", reply_to_message_id))
.child(Label::new("Replying to ").size(LabelSize::Small))
.child(
Label::new(format!(
"@{}",
user_being_replied_to.github_login.clone()
))
.size(LabelSize::Small)
.weight(FontWeight::BOLD),
div().font_weight(FontWeight::BOLD).child(
Label::new(format!(
"@{}",
user_being_replied_to.github_login.clone()
))
.size(LabelSize::Small),
),
)
.when_some(channel_id, |this, channel_id| {
this.cursor_pointer().on_click(cx.listener(

View File

@@ -2547,8 +2547,9 @@ impl CollabPanel {
.take(FACEPILE_LIMIT)
.chain(if extra_count > 0 {
Some(
Label::new(format!("+{extra_count}"))
div()
.ml_2()
.child(Label::new(format!("+{extra_count}")))
.into_any_element(),
)
} else {

View File

@@ -258,7 +258,7 @@ gpui::actions!(
RedoSelection,
Rename,
RestartLanguageServer,
RevealInFileManager,
RevealInFinder,
ReverseLines,
RevertSelectedHunks,
ScrollCursorBottom,

View File

@@ -89,16 +89,13 @@ use language::{
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
Point, Selection, SelectionGoal, TransactionId,
};
use language::{point_to_lsp, BufferRow, Runnable, RunnableRange};
use language::{BufferRow, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges;
use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
pub use lsp::CompletionContext;
use lsp::{
CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity, InsertTextFormat,
LanguageServerId,
};
use lsp::{CompletionTriggerKind, DiagnosticSeverity, LanguageServerId};
use mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails;
pub use multi_buffer::{
@@ -1129,10 +1126,11 @@ impl CompletionsMenu {
None
} else {
Some(
Label::new(text.clone())
.ml_4()
.size(LabelSize::Small)
.color(Color::Muted),
h_flex().ml_4().child(
Label::new(text.clone())
.size(LabelSize::Small)
.color(Color::Muted),
),
)
}
} else {
@@ -1155,7 +1153,7 @@ impl CompletionsMenu {
}
}))
.child(h_flex().overflow_hidden().child(completion_label))
.end_slot::<Label>(documentation_label),
.end_slot::<Div>(documentation_label),
)
})
.collect()
@@ -1929,11 +1927,6 @@ impl Editor {
EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full",
};
if EditorSettings::get_global(cx).jupyter.enabled {
key_context.add("jupyter");
}
key_context.set("mode", mode);
if self.pending_rename.is_some() {
key_context.add("renaming");
@@ -5159,6 +5152,7 @@ impl Editor {
})
.collect::<Vec<_>>()
});
if let Some(tabstop) = tabstops.first() {
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(tabstop.ranges.iter().cloned());
@@ -10332,7 +10326,7 @@ impl Editor {
cx.notify();
}
pub fn reveal_in_finder(&mut self, _: &RevealInFileManager, cx: &mut ViewContext<Self>) {
pub fn reveal_in_finder(&mut self, _: &RevealInFinder, cx: &mut ViewContext<Self>) {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
cx.reveal_path(&file.abs_path(cx));
@@ -11758,97 +11752,6 @@ pub trait CompletionProvider {
) -> bool;
}
fn snippet_completions(
project: &Project,
buffer: &Model<Buffer>,
buffer_position: text::Anchor,
cx: &mut AppContext,
) -> Vec<Completion> {
let language = buffer.read(cx).language_at(buffer_position);
let language_name = language.as_ref().map(|language| language.lsp_id());
let snippet_store = project.snippets().read(cx);
let snippets = snippet_store.snippets_for(language_name, cx);
if snippets.is_empty() {
return vec![];
}
let snapshot = buffer.read(cx).text_snapshot();
let chunks = snapshot.reversed_chunks_in_range(text::Anchor::MIN..buffer_position);
let mut lines = chunks.lines();
let Some(line_at) = lines.next().filter(|line| !line.is_empty()) else {
return vec![];
};
let scope = language.map(|language| language.default_scope());
let mut last_word = line_at
.chars()
.rev()
.take_while(|c| char_kind(&scope, *c) == CharKind::Word)
.collect::<String>();
last_word = last_word.chars().rev().collect();
let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
let to_lsp = |point: &text::Anchor| {
let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
point_to_lsp(end)
};
let lsp_end = to_lsp(&buffer_position);
snippets
.into_iter()
.filter_map(|snippet| {
let matching_prefix = snippet
.prefix
.iter()
.find(|prefix| prefix.starts_with(&last_word))?;
let start = as_offset - last_word.len();
let start = snapshot.anchor_before(start);
let range = start..buffer_position;
let lsp_start = to_lsp(&start);
let lsp_range = lsp::Range {
start: lsp_start,
end: lsp_end,
};
Some(Completion {
old_range: range,
new_text: snippet.body.clone(),
label: CodeLabel {
text: matching_prefix.clone(),
runs: vec![],
filter_range: 0..matching_prefix.len(),
},
server_id: LanguageServerId(usize::MAX),
documentation: snippet
.description
.clone()
.map(|description| Documentation::SingleLine(description)),
lsp_completion: lsp::CompletionItem {
label: snippet.prefix.first().unwrap().clone(),
kind: Some(CompletionItemKind::SNIPPET),
label_details: snippet.description.as_ref().map(|description| {
lsp::CompletionItemLabelDetails {
detail: Some(description.clone()),
description: None,
}
}),
insert_text_format: Some(InsertTextFormat::SNIPPET),
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
lsp::InsertReplaceEdit {
new_text: snippet.body.clone(),
insert: lsp_range,
replace: lsp_range,
},
)),
filter_text: Some(snippet.body.clone()),
sort_text: Some(char::MAX.to_string()),
..Default::default()
},
confirm: None,
show_new_completions_on_confirm: false,
})
})
.collect()
}
impl CompletionProvider for Model<Project> {
fn completions(
&self,
@@ -11858,14 +11761,7 @@ impl CompletionProvider for Model<Project> {
cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<Completion>>> {
self.update(cx, |project, cx| {
let snippets = snippet_completions(project, buffer, buffer_position, cx);
let project_completions = project.completions(&buffer, buffer_position, options, cx);
cx.background_executor().spawn(async move {
let mut completions = project_completions.await?;
//let snippets = snippets.into_iter().;
completions.extend(snippets);
Ok(completions)
})
project.completions(&buffer, buffer_position, options, cx)
})
}
@@ -12823,7 +12719,7 @@ pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> +
})
}
pub trait RangeToAnchorExt {
trait RangeToAnchorExt {
fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
}

View File

@@ -25,8 +25,6 @@ pub struct EditorSettings {
pub expand_excerpt_lines: u32,
#[serde(default)]
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
#[serde(default)]
pub jupyter: Jupyter,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -66,15 +64,6 @@ pub enum DoubleClickInMultibuffer {
Open,
}
#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct Jupyter {
/// Whether the Jupyter feature is enabled.
///
/// Default: `false`
pub enabled: bool,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Toolbar {
pub breadcrumbs: bool,
@@ -228,9 +217,6 @@ pub struct EditorSettingsContent {
///
/// Default: select
pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
/// Jupyter REPL settings.
pub jupyter: Option<Jupyter>,
}
// Toolbar related settings

View File

@@ -3660,11 +3660,6 @@ impl EditorElement {
if scroll_position != current_scroll_position {
editor.scroll(scroll_position, axis, cx);
cx.stop_propagation();
} else if y < 0. {
// Due to clamping, we may fail to detect cases of overscroll to the top;
// We want the scroll manager to get an update in such cases and detect the change of direction
// on the next frame.
cx.notify();
}
});
}

View File

@@ -19,7 +19,7 @@ use project::{HoverBlock, HoverBlockKind, InlayHintLabelPart};
use settings::Settings;
use smol::stream::StreamExt;
use std::{ops::Range, sync::Arc, time::Duration};
use ui::{prelude::*, window_is_transparent, Tooltip};
use ui::{prelude::*, Tooltip};
use util::TryFutureExt;
use workspace::Workspace;
@@ -587,12 +587,15 @@ impl DiagnosticPopover {
div()
.id("diagnostic")
.block()
.elevation_2_borderless(cx)
// Don't draw the background color if the theme
// allows transparent surfaces.
.when(window_is_transparent(cx), |this| {
this.bg(gpui::transparent_black())
})
.elevation_2(cx)
.overflow_y_scroll()
.px_2()
.py_1()
.bg(diagnostic_colors.background)
.text_color(style.text.color)
.border_1()
.border_color(diagnostic_colors.border)
.rounded_md()
.max_w(max_size.width)
.max_h(max_size.height)
.cursor(CursorStyle::PointingHand)
@@ -604,19 +607,7 @@ impl DiagnosticPopover {
// because that would move the cursor.
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(cx.listener(|editor, _, cx| editor.go_to_diagnostic(&Default::default(), cx)))
.child(
div()
.id("diagnostic-inner")
.overflow_y_scroll()
.px_2()
.py_1()
.bg(diagnostic_colors.background)
.text_color(style.text.color)
.border_1()
.border_color(diagnostic_colors.border)
.rounded_lg()
.child(SharedString::from(text)),
)
.child(SharedString::from(text))
.into_any_element()
}

View File

@@ -1,12 +1,8 @@
use std::ops::Range;
use crate::{
selections_collection::SelectionsCollection, Copy, CopyPermalinkToLine, Cut, DisplayPoint,
DisplaySnapshot, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToImplementation,
GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, ToDisplayPoint,
ToggleCodeActions,
Copy, CopyPermalinkToLine, Cut, DisplayPoint, Editor, EditorMode, FindAllReferences,
GoToDefinition, GoToImplementation, GoToTypeDefinition, Paste, Rename, RevealInFinder,
SelectMode, ToggleCodeActions,
};
use gpui::prelude::FluentBuilder;
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
use workspace::OpenInTerminal;
@@ -41,23 +37,6 @@ impl MouseContextMenu {
}
}
fn display_ranges<'a>(
display_map: &'a DisplaySnapshot,
selections: &'a SelectionsCollection,
) -> impl Iterator<Item = Range<DisplayPoint>> + 'a {
let pending = selections
.pending
.as_ref()
.map(|pending| &pending.selection);
selections.disjoint.iter().chain(pending).map(move |s| {
if s.reversed {
s.end.to_display_point(&display_map)..s.start.to_display_point(&display_map)
} else {
s.start.to_display_point(&display_map)..s.end.to_display_point(&display_map)
}
})
}
pub fn deploy_context_menu(
editor: &mut Editor,
position: Point<Pixels>,
@@ -86,14 +65,11 @@ pub fn deploy_context_menu(
return;
}
let display_map = editor.selections.display_map(cx);
if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) {
// Move the cursor to the clicked location so that dispatched actions make sense
editor.change_selections(None, cx, |s| {
s.clear_disjoint();
s.set_pending_display_range(point..point, SelectMode::Character);
});
}
// Move the cursor to the clicked location so that dispatched actions make sense
editor.change_selections(None, cx, |s| {
s.clear_disjoint();
s.set_pending_display_range(point..point, SelectMode::Character);
});
let focus = cx.focused();
ui::ContextMenu::build(cx, |menu, _cx| {
@@ -114,12 +90,7 @@ pub fn deploy_context_menu(
.action("Copy", Box::new(Copy))
.action("Paste", Box::new(Paste))
.separator()
.when(cfg!(target_os = "macos"), |builder| {
builder.action("Reveal in Finder", Box::new(RevealInFileManager))
})
.when(cfg!(not(target_os = "macos")), |builder| {
builder.action("Reveal in File Manager", Box::new(RevealInFileManager))
})
.action("Reveal in Finder", Box::new(RevealInFinder))
.action("Open in Terminal", Box::new(OpenInTerminal))
.action("Copy Permalink", Box::new(CopyPermalinkToLine));
match focus {

View File

@@ -42,7 +42,6 @@ semantic_version.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
snippet_provider.workspace = true
theme.workspace = true
toml.workspace = true
ui.workspace = true

View File

@@ -526,11 +526,6 @@ fn populate_defaults(manifest: &mut ExtensionManifest, extension_path: &Path) ->
}
}
let snippets_json_path = extension_path.join("snippets.json");
if snippets_json_path.exists() {
manifest.snippets = Some(snippets_json_path);
}
// For legacy extensions on the v0 schema (aka, using `extension.json`), we want to populate the grammars in
// the manifest using the contents of the `grammars` directory.
if manifest.schema_version.is_v0() {

View File

@@ -78,8 +78,6 @@ pub struct ExtensionManifest {
pub slash_commands: BTreeMap<Arc<str>, SlashCommandManifestEntry>,
#[serde(default)]
pub indexed_docs_providers: BTreeMap<Arc<str>, IndexedDocsProviderEntry>,
#[serde(default)]
pub snippets: Option<PathBuf>,
}
#[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
@@ -208,6 +206,5 @@ fn manifest_from_old_manifest(
language_servers: Default::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}
}

View File

@@ -1,9 +1,7 @@
use std::sync::{atomic::AtomicBool, Arc};
use anyhow::{anyhow, Result};
use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use futures::FutureExt;
use gpui::{AppContext, Task, WeakView, WindowContext};
use language::LspAdapterDelegate;
@@ -43,7 +41,7 @@ impl SlashCommand for ExtensionSlashCommand {
_cancel: Arc<AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
) -> Task<Result<Vec<String>>> {
cx.background_executor().spawn(async move {
self.extension
.call({
@@ -59,16 +57,7 @@ impl SlashCommand for ExtensionSlashCommand {
.await?
.map_err(|e| anyhow!("{}", e))?;
anyhow::Ok(
completions
.into_iter()
.map(|completion| ArgumentCompletion {
label: completion.label,
new_text: completion.new_text,
run_command: completion.run_command,
})
.collect(),
)
anyhow::Ok(completions)
}
.boxed()
}

View File

@@ -44,7 +44,6 @@ use release_channel::ReleaseChannel;
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use settings::Settings;
use snippet_provider::SnippetRegistry;
use std::ops::RangeInclusive;
use std::str::FromStr;
use std::{
@@ -116,7 +115,6 @@ pub struct ExtensionStore {
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
modified_extensions: HashSet<Arc<str>>,
wasm_host: Arc<WasmHost>,
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
@@ -195,7 +193,6 @@ pub fn init(
theme_registry,
SlashCommandRegistry::global(cx),
IndexedDocsRegistry::global(cx),
SnippetRegistry::global(cx),
cx,
)
});
@@ -230,7 +227,6 @@ impl ExtensionStore {
theme_registry: Arc<ThemeRegistry>,
slash_command_registry: Arc<SlashCommandRegistry>,
indexed_docs_registry: Arc<IndexedDocsRegistry>,
snippet_registry: Arc<SnippetRegistry>,
cx: &mut ModelContext<Self>,
) -> Self {
let work_dir = extensions_dir.join("work");
@@ -263,7 +259,6 @@ impl ExtensionStore {
theme_registry,
slash_command_registry,
indexed_docs_registry,
snippet_registry,
reload_tx,
tasks: Vec::new(),
};
@@ -1050,7 +1045,6 @@ impl ExtensionStore {
.collect::<Vec<_>>();
let mut grammars_to_add = Vec::new();
let mut themes_to_add = Vec::new();
let mut snippets_to_add = Vec::new();
for extension_id in &extensions_to_load {
let Some(extension) = new_index.extensions.get(extension_id) else {
continue;
@@ -1068,11 +1062,6 @@ impl ExtensionStore {
path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
path
}));
snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
let mut path = self.installed_dir.clone();
path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
path
}));
}
self.language_registry
@@ -1108,7 +1097,6 @@ impl ExtensionStore {
let wasm_host = self.wasm_host.clone();
let root_dir = self.installed_dir.clone();
let theme_registry = self.theme_registry.clone();
let snippet_registry = self.snippet_registry.clone();
let extension_entries = extensions_to_load
.iter()
.filter_map(|name| new_index.extensions.get(name).cloned())
@@ -1129,15 +1117,6 @@ impl ExtensionStore {
.await
.log_err();
}
for snippets_path in &snippets_to_add {
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
{
snippet_registry
.register_snippets(snippets_path, &snippets_contents)
.log_err();
}
}
}
})
.await;

View File

@@ -19,7 +19,6 @@ use parking_lot::Mutex;
use project::{Project, DEFAULT_COMPLETION_CONTEXT};
use serde_json::json;
use settings::{Settings as _, SettingsStore};
use snippet_provider::SnippetRegistry;
use std::{
ffi::OsString,
path::{Path, PathBuf},
@@ -161,7 +160,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -187,7 +185,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -261,7 +258,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let snippet_registry = Arc::new(SnippetRegistry::new());
let node_runtime = FakeNodeRuntime::new();
let store = cx.new_model(|cx| {
@@ -276,7 +272,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry.clone(),
indexed_docs_registry.clone(),
snippet_registry.clone(),
cx,
)
});
@@ -350,7 +345,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
language_servers: BTreeMap::default(),
slash_commands: BTreeMap::default(),
indexed_docs_providers: BTreeMap::default(),
snippets: None,
}),
dev: false,
},
@@ -402,7 +396,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
cx,
)
});
@@ -484,7 +477,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
let slash_command_registry = SlashCommandRegistry::new();
let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
let snippet_registry = Arc::new(SnippetRegistry::new());
let node_runtime = FakeNodeRuntime::new();
let mut status_updates = language_registry.language_server_binary_statuses();
@@ -576,7 +568,6 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
theme_registry.clone(),
slash_command_registry,
indexed_docs_registry,
snippet_registry,
cx,
)
});

View File

@@ -20,7 +20,7 @@ use wasmtime::{
pub use latest::CodeLabelSpanLiteral;
pub use latest::{
zed::extension::lsp::{Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind},
zed::extension::slash_command::{SlashCommandArgumentCompletion, SlashCommandOutput},
zed::extension::slash_command::SlashCommandOutput,
CodeLabel, CodeLabelSpan, Command, Range, SlashCommand,
};
pub use since_v0_0_4::LanguageServerConfig;
@@ -263,7 +263,7 @@ impl Extension {
store: &mut Store<WasmState>,
command: &SlashCommand,
query: &str,
) -> Result<Result<Vec<SlashCommandArgumentCompletion>, String>> {
) -> Result<Result<Vec<String>, String>> {
match self {
Extension::V007(ext) => {
ext.call_complete_slash_command_argument(store, command, query)

View File

@@ -421,10 +421,27 @@ impl ExtensionImports for WasmState {
.await?;
}
DownloadedFileType::Zip => {
let file_name = destination_path
.file_name()
.ok_or_else(|| anyhow!("invalid download path"))?
.to_string_lossy();
let zip_filename = format!("{file_name}.zip");
let mut zip_path = destination_path.clone();
zip_path.set_file_name(zip_filename);
futures::pin_mut!(body);
node_runtime::extract_zip(&destination_path, body)
.await
.with_context(|| format!("failed to unzip {} archive", path.display()))?;
self.host.fs.create_file_with(&zip_path, body).await?;
let unzip_status = std::process::Command::new("unzip")
.current_dir(&extension_work_dir)
.arg("-d")
.arg(&destination_path)
.arg(&zip_path)
.output()?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip {} archive", path.display()))?;
}
}
}

View File

@@ -25,9 +25,7 @@ pub use wit::{
npm_package_latest_version,
},
zed::extension::platform::{current_platform, Architecture, Os},
zed::extension::slash_command::{
SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
},
zed::extension::slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection},
CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
KeyValueStore, LanguageServerInstallationStatus, Range, Worktree,
};
@@ -116,7 +114,7 @@ pub trait Extension: Send + Sync {
&self,
_command: SlashCommand,
_query: String,
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
) -> Result<Vec<String>, String> {
Ok(Vec::new())
}
@@ -249,7 +247,7 @@ impl wit::Guest for Component {
fn complete_slash_command_argument(
command: SlashCommand,
query: String,
) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
) -> Result<Vec<String>, String> {
extension().complete_slash_command_argument(command, query)
}

View File

@@ -8,7 +8,7 @@ world extension {
use common.{range};
use lsp.{completion, symbol};
use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
use slash-command.{slash-command, slash-command-output};
/// Initializes the extension.
export init-extension: func();
@@ -130,7 +130,7 @@ world extension {
export labels-for-symbols: func(language-server-id: string, symbols: list<symbol>) -> result<list<option<code-label>>, string>;
/// Returns the completions that should be shown when completing the provided slash command with the given query.
export complete-slash-command-argument: func(command: slash-command, query: string) -> result<list<slash-command-argument-completion>, string>;
export complete-slash-command-argument: func(command: slash-command, query: string) -> result<list<string>, string>;
/// Returns the output from running the provided slash command.
export run-slash-command: func(command: slash-command, argument: option<string>, worktree: borrow<worktree>) -> result<slash-command-output, string>;

View File

@@ -28,14 +28,4 @@ interface slash-command {
/// The label to display in the placeholder for this section.
label: string,
}
/// A completion for a slash command argument.
record slash-command-argument-completion {
/// The label to display for this completion.
label: string,
/// The new text that should be inserted into the command when this completion is accepted.
new-text: string,
/// Whether the command should be run when accepting this completion.
run-command: bool,
}
}

View File

@@ -124,6 +124,7 @@ wayland-protocols = { version = "0.31.2", features = [
] }
wayland-protocols-plasma = { version = "0.2.0", features = ["client"] }
oo7 = "0.3.0"
open = "5.1.2"
filedescriptor = "0.8.2"
x11rb = { version = "0.13.0", features = [
"allow-unsafe-code",
@@ -141,6 +142,7 @@ xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca
] }
font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] }
x11-clipboard = "0.9.2"
mio = { version = "1.0.0", features = ["os-poll", "os-ext"] }
[target.'cfg(windows)'.dependencies]
windows.workspace = true

View File

@@ -26,8 +26,6 @@ struct TextInput {
selection_reversed: bool,
marked_range: Option<Range<usize>>,
last_layout: Option<ShapedLine>,
last_bounds: Option<Bounds<Pixels>>,
is_selecting: bool,
}
impl TextInput {
@@ -82,21 +80,6 @@ impl TextInput {
self.replace_text_in_range(None, "", cx)
}
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
self.is_selecting = true;
self.move_to(self.index_for_mouse_position(event.position), cx)
}
fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {
self.is_selecting = false;
}
fn on_mouse_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) {
if self.is_selecting {
self.select_to(self.index_for_mouse_position(event.position), cx);
}
}
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
cx.show_character_palette();
}
@@ -114,20 +97,6 @@ impl TextInput {
}
}
fn index_for_mouse_position(&self, position: Point<Pixels>) -> usize {
let (Some(bounds), Some(line)) = (self.last_bounds.as_ref(), self.last_layout.as_ref())
else {
return 0;
};
if position.y < bounds.top() {
return 0;
}
if position.y > bounds.bottom() {
return self.content.len();
}
line.closest_index_for_x(position.x - bounds.left())
}
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
if self.selection_reversed {
self.selected_range.start = offset
@@ -193,16 +162,6 @@ impl TextInput {
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.content.len())
}
fn reset(&mut self) {
self.content = "".into();
self.selected_range = 0..0;
self.selection_reversed = false;
self.marked_range = None;
self.last_layout = None;
self.last_bounds = None;
self.is_selecting = false;
}
}
impl ViewInputHandler for TextInput {
@@ -325,7 +284,6 @@ impl Element for TextElement {
None
}
#[profiling::function]
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
@@ -337,7 +295,6 @@ impl Element for TextElement {
(cx.request_layout(style, []), ())
}
#[profiling::function]
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
@@ -428,7 +385,6 @@ impl Element for TextElement {
}
}
#[profiling::function]
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
@@ -443,31 +399,26 @@ impl Element for TextElement {
ElementInputHandler::new(bounds, self.input.clone()),
);
if let Some(selection) = prepaint.selection.take() {
profiling::scope!("paint_quad selection");
cx.paint_quad(selection)
}
let line = prepaint.line.take().unwrap();
line.paint(bounds.origin, cx.line_height(), cx).unwrap();
if let Some(cursor) = prepaint.cursor.take() {
profiling::scope!("paint_quad cursor");
cx.paint_quad(cursor);
}
self.input.update(cx, |input, _cx| {
input.last_layout = Some(line);
input.last_bounds = Some(bounds);
});
}
}
impl Render for TextInput {
#[profiling::function]
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.key_context("TextInput")
.track_focus(&self.focus_handle)
.cursor(CursorStyle::IBeam)
.on_action(cx.listener(Self::backspace))
.on_action(cx.listener(Self::delete))
.on_action(cx.listener(Self::left))
@@ -478,10 +429,6 @@ impl Render for TextInput {
.on_action(cx.listener(Self::home))
.on_action(cx.listener(Self::end))
.on_action(cx.listener(Self::show_character_palette))
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up))
.on_mouse_move(cx.listener(Self::on_mouse_move))
.bg(rgb(0xeeeeee))
.size_full()
.line_height(px(30.))
@@ -499,74 +446,6 @@ impl Render for TextInput {
}
}
impl FocusableView for TextInput {
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
struct InputExample {
text_input: View<TextInput>,
recent_keystrokes: Vec<Keystroke>,
}
impl InputExample {
fn on_reset_click(&mut self, _: &MouseUpEvent, cx: &mut ViewContext<Self>) {
self.recent_keystrokes.clear();
self.text_input
.update(cx, |text_input, _cx| text_input.reset());
cx.notify();
}
}
impl Render for InputExample {
#[profiling::function]
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let num_keystrokes = self.recent_keystrokes.len();
div()
.bg(rgb(0xaaaaaa))
.flex()
.flex_col()
.size_full()
.child(
div()
.bg(white())
.border_b_1()
.border_color(black())
.flex()
.flex_row()
.justify_between()
.child(format!("Keystrokes: {}", num_keystrokes))
.child(
div()
.border_1()
.border_color(black())
.px_2()
.bg(yellow())
.child("Reset")
.hover(|style| {
style
.bg(yellow().blend(opaque_grey(0.5, 0.5)))
.cursor_pointer()
})
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_reset_click)),
),
)
.child(self.text_input.clone())
.children(self.recent_keystrokes.iter().rev().map(|ks| {
format!(
"{:} {}",
ks,
if let Some(ime_key) = ks.ime_key.as_ref() {
format!("-> {}", ime_key)
} else {
"".to_owned()
}
)
}))
}
}
fn main() {
App::new().run(|cx: &mut AppContext| {
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
@@ -589,36 +468,21 @@ fn main() {
..Default::default()
},
|cx| {
let text_input = cx.new_view(|cx| TextInput {
cx.new_view(|cx| TextInput {
focus_handle: cx.focus_handle(),
content: "".into(),
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
last_layout: None,
last_bounds: None,
is_selecting: false,
});
cx.new_view(|_| InputExample {
text_input,
recent_keystrokes: vec![],
})
},
)
.unwrap();
cx.observe_keystrokes(move |ev, cx| {
window
.update(cx, |view, cx| {
view.recent_keystrokes.push(ev.keystroke.clone());
cx.notify();
})
.unwrap();
})
.detach();
window
.update(cx, |view, cx| {
cx.focus_view(&view.text_input);
cx.activate(true);
view.focus_handle.focus(cx);
cx.activate(true)
})
.unwrap();
});

View File

@@ -387,7 +387,6 @@ impl<E: Element> Drawable<E> {
}
}
#[profiling::function]
pub(crate) fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
@@ -504,7 +503,6 @@ impl AnyElement {
}
/// Performs layout for this element within the given available space and returns its size.
#[profiling::function]
pub fn layout_as_root(
&mut self,
available_space: Size<AvailableSpace>,
@@ -519,7 +517,6 @@ impl AnyElement {
}
/// Performs layout on this element in the available space, then prepaints it at the given absolute origin.
#[profiling::function]
pub fn prepaint_as_root(
&mut self,
origin: Point<Pixels>,
@@ -527,10 +524,7 @@ impl AnyElement {
cx: &mut WindowContext,
) {
self.layout_as_root(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| {
profiling::scope!("with_absolute_element_offset prepaint");
self.0.prepaint(cx)
});
cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
}
}

View File

@@ -2320,18 +2320,6 @@ impl Pixels {
Self(self.0.abs())
}
/// Returns the sign of the `Pixels` value.
///
/// # Returns
///
/// Returns:
/// * `1.0` if the value is positive
/// * `-1.0` if the value is negative
/// * `0.0` if the value is zero
pub fn signum(&self) -> f32 {
self.0.signum()
}
/// Returns the f64 value of `Pixels`.
///
/// # Returns

View File

@@ -291,41 +291,14 @@ impl ScrollDelta {
}
/// Combines two scroll deltas into one.
/// If the signs of the deltas are the same (both positive or both negative),
/// the deltas are added together. If the signs are opposite, the second delta
/// (other) is used, effectively overriding the first delta.
pub fn coalesce(self, other: ScrollDelta) -> ScrollDelta {
match (self, other) {
(ScrollDelta::Pixels(a), ScrollDelta::Pixels(b)) => {
let x = if a.x.signum() * b.x.signum() >= 0. {
a.x + b.x
} else {
b.x
};
let y = if a.y.signum() * b.y.signum() >= 0. {
a.y + b.y
} else {
b.y
};
ScrollDelta::Pixels(point(x, y))
(ScrollDelta::Pixels(px_a), ScrollDelta::Pixels(px_b)) => {
ScrollDelta::Pixels(px_a + px_b)
}
(ScrollDelta::Lines(a), ScrollDelta::Lines(b)) => {
let x = if a.x.signum() * b.x.signum() >= 0. {
a.x + b.x
} else {
b.x
};
let y = if a.y.signum() * b.y.signum() >= 0. {
a.y + b.y
} else {
b.y
};
ScrollDelta::Lines(point(x, y))
(ScrollDelta::Lines(lines_a), ScrollDelta::Lines(lines_b)) => {
ScrollDelta::Lines(lines_a + lines_b)
}
_ => other,

View File

@@ -281,16 +281,6 @@ pub struct Tiling {
}
impl Tiling {
/// Initializes a [`Tiling`] type with all sides tiled
pub fn tiled() -> Self {
Self {
top: true,
left: true,
right: true,
bottom: true,
}
}
/// Whether any edge is tiled
pub fn is_tiled(&self) -> bool {
self.top || self.left || self.right || self.bottom

View File

@@ -524,7 +524,6 @@ impl BladeRenderer {
self.gpu.destroy_command_encoder(&mut self.command_encoder);
}
#[profiling::function]
pub fn draw(&mut self, scene: &Scene) {
self.command_encoder.start();
self.atlas.before_frame(&mut self.command_encoder);

View File

@@ -5,12 +5,10 @@ use calloop::{
timer::TimeoutAction,
EventLoop,
};
use mio::Waker;
use parking::{Parker, Unparker};
use parking_lot::Mutex;
use std::{
thread,
time::{Duration, Instant},
};
use std::{sync::Arc, thread, time::Duration};
use util::ResultExt;
struct TimerAfter {
@@ -21,6 +19,7 @@ struct TimerAfter {
pub(crate) struct LinuxDispatcher {
parker: Mutex<Parker>,
main_sender: Sender<Runnable>,
main_waker: Option<Arc<Waker>>,
timer_sender: Sender<TimerAfter>,
background_sender: flume::Sender<Runnable>,
_background_threads: Vec<thread::JoinHandle<()>>,
@@ -28,28 +27,18 @@ pub(crate) struct LinuxDispatcher {
}
impl LinuxDispatcher {
pub fn new(main_sender: Sender<Runnable>) -> Self {
pub fn new(main_sender: Sender<Runnable>, main_waker: Option<Arc<Waker>>) -> Self {
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
let thread_count = std::thread::available_parallelism()
.map(|i| i.get())
.unwrap_or(1);
let mut background_threads = (0..thread_count)
.map(|i| {
.map(|_| {
let receiver = background_receiver.clone();
std::thread::spawn(move || {
let thread_name = format!("background-{}", i);
profiling::register_thread!(&thread_name);
for runnable in receiver {
let start = Instant::now();
runnable.run();
log::trace!(
"background thread {}: ran runnable. took: {:?}",
i,
start.elapsed()
);
}
})
})
@@ -57,8 +46,6 @@ impl LinuxDispatcher {
let (timer_sender, timer_channel) = calloop::channel::channel::<TimerAfter>();
let timer_thread = std::thread::spawn(|| {
profiling::register_thread!("timer-thread");
let mut event_loop: EventLoop<()> =
EventLoop::try_new().expect("Failed to initialize timer loop!");
@@ -92,6 +79,7 @@ impl LinuxDispatcher {
Self {
parker: Mutex::new(Parker::new()),
main_sender,
main_waker,
timer_sender,
background_sender,
_background_threads: background_threads,
@@ -111,6 +99,9 @@ impl PlatformDispatcher for LinuxDispatcher {
fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender.send(runnable).ok();
if let Some(main_waker) = self.main_waker.as_ref() {
main_waker.wake().ok();
}
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {

View File

@@ -22,7 +22,7 @@ impl HeadlessClient {
pub(crate) fn new() -> Self {
let event_loop = EventLoop::try_new().unwrap();
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
let handle = event_loop.handle();
@@ -81,8 +81,6 @@ impl LinuxClient for HeadlessClient {
fn open_uri(&self, _uri: &str) {}
fn reveal_path(&self, _path: std::path::PathBuf) {}
fn write_to_primary(&self, _item: crate::ClipboardItem) {}
fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}

View File

@@ -7,7 +7,7 @@ use std::ffi::OsString;
use std::fs::File;
use std::io::Read;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, FromRawFd};
use std::os::fd::{AsRawFd, FromRawFd};
use std::panic::Location;
use std::rc::Weak;
use std::{
@@ -20,14 +20,13 @@ use std::{
use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest};
use ashpd::{url, ActivationToken};
use async_task::Runnable;
use calloop::channel::Channel;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use filedescriptor::FileDescriptor;
use flume::{Receiver, Sender};
use futures::channel::oneshot;
use mio::Waker;
use parking_lot::Mutex;
use time::UtcOffset;
use util::ResultExt;
@@ -68,7 +67,6 @@ pub trait LinuxClient {
) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle);
fn open_uri(&self, uri: &str);
fn reveal_path(&self, path: PathBuf);
fn write_to_primary(&self, item: ClipboardItem);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_primary(&self) -> Option<ClipboardItem>;
@@ -87,6 +85,16 @@ pub(crate) struct PlatformHandlers {
pub(crate) validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
}
pub trait QuitSignal {
fn quit(&mut self);
}
impl QuitSignal for LoopSignal {
fn quit(&mut self) {
self.stop();
}
}
pub(crate) struct LinuxCommon {
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
@@ -94,17 +102,20 @@ pub(crate) struct LinuxCommon {
pub(crate) appearance: WindowAppearance,
pub(crate) auto_hide_scrollbars: bool,
pub(crate) callbacks: PlatformHandlers,
pub(crate) signal: LoopSignal,
pub(crate) quit_signal: Box<dyn QuitSignal>,
pub(crate) menus: Vec<OwnedMenu>,
}
impl LinuxCommon {
pub fn new(signal: LoopSignal) -> (Self, Channel<Runnable>) {
pub fn new(
quit_signal: Box<dyn QuitSignal>,
main_waker: Option<Arc<Waker>>,
) -> (Self, Channel<Runnable>) {
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
let text_system = Arc::new(CosmicTextSystem::new());
let callbacks = PlatformHandlers::default();
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone()));
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender.clone(), main_waker));
let background_executor = BackgroundExecutor::new(dispatcher.clone());
@@ -115,7 +126,7 @@ impl LinuxCommon {
appearance: WindowAppearance::Light,
auto_hide_scrollbars: false,
callbacks,
signal,
quit_signal,
menus: Vec::new(),
};
@@ -149,7 +160,7 @@ impl<P: LinuxClient + 'static> Platform for P {
}
fn quit(&self) {
self.with_common(|common| common.signal.stop());
self.with_common(|common| common.quit_signal.quit());
}
fn compositor_name(&self) -> &'static str {
@@ -303,27 +314,20 @@ impl<P: LinuxClient + 'static> Platform for P {
let directory = directory.to_owned();
self.foreground_executor()
.spawn(async move {
let request = SaveFileRequest::default()
let result = SaveFileRequest::default()
.modal(true)
.title("Select new path")
.accept_label("Accept")
.current_folder(directory);
let result = if let Ok(request) = request {
request
.send()
.await
.ok()
.and_then(|request| request.response().ok())
.and_then(|response| {
response
.uris()
.first()
.and_then(|uri| uri.to_file_path().ok())
})
} else {
None
};
.send()
.await
.ok()
.and_then(|request| request.response().ok())
.and_then(|response| {
response
.uris()
.first()
.and_then(|uri| uri.to_file_path().ok())
});
done_tx.send(result);
})
@@ -333,7 +337,13 @@ impl<P: LinuxClient + 'static> Platform for P {
}
fn reveal_path(&self, path: &Path) {
self.reveal_path(path.to_owned());
if path.is_dir() {
open::that_detached(path);
return;
}
// If `path` is a file, the system may try to open it in a text editor
let dir = path.parent().unwrap_or(Path::new(""));
open::that_detached(dir);
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
@@ -494,40 +504,18 @@ impl<P: LinuxClient + 'static> Platform for P {
fn add_recent_document(&self, _path: &Path) {}
}
pub(super) fn open_uri_internal(
executor: BackgroundExecutor,
uri: &str,
activation_token: Option<String>,
) {
if let Some(uri) = url::Url::parse(uri).log_err() {
executor
.spawn(async move {
OpenUriRequest::default()
.activation_token(activation_token.map(ActivationToken::from))
.send_uri(&uri)
.await
.log_err();
})
.detach();
pub(super) fn open_uri_internal(uri: &str, activation_token: Option<&str>) {
let mut last_err = None;
for mut command in open::commands(uri) {
if let Some(token) = activation_token {
command.env("XDG_ACTIVATION_TOKEN", token);
}
match command.spawn() {
Ok(_) => return,
Err(err) => last_err = Some(err),
}
}
}
pub(super) fn reveal_path_internal(
executor: BackgroundExecutor,
path: PathBuf,
activation_token: Option<String>,
) {
executor
.spawn(async move {
if let Some(dir) = File::open(path).log_err() {
OpenDirectoryRequest::default()
.activation_token(activation_token.map(ActivationToken::from))
.send(&dir.as_fd())
.await
.log_err();
}
})
.detach();
log::error!("failed to open uri: {uri:?}, last error: {last_err:?}");
}
pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bool {
@@ -640,8 +628,6 @@ impl Keystroke {
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
Keysym::ISO_Left_Tab => "tab".to_owned(),
Keysym::KP_Prior => "pageup".to_owned(),
Keysym::KP_Next => "pagedown".to_owned(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
@@ -679,14 +665,7 @@ impl Keystroke {
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
_ => {
let name = xkb::keysym_get_name(key_sym).to_lowercase();
if key_sym.is_keypad_key() {
name.replace("kp_", "")
} else {
name
}
}
_ => xkb::keysym_get_name(key_sym).to_lowercase(),
};
if modifiers.shift {

View File

@@ -61,6 +61,7 @@ use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blu
use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1;
use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS};
use super::super::{open_uri_internal, read_fd, DOUBLE_CLICK_INTERVAL};
use super::display::WaylandDisplay;
use super::window::{ImeInput, WaylandWindowStatePtr};
use crate::platform::linux::wayland::clipboard::{
@@ -71,14 +72,11 @@ use crate::platform::linux::wayland::serial::{SerialKind, SerialTracker};
use crate::platform::linux::wayland::window::WaylandWindow;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::LinuxClient;
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, read_fd,
reveal_path_internal,
};
use crate::platform::linux::{get_xkb_compose_state, is_within_click_distance};
use crate::platform::PlatformWindow;
use crate::{
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
DOUBLE_CLICK_INTERVAL, SCROLL_LINES,
SCROLL_LINES,
};
use crate::{
AnyWindowHandle, CursorStyle, DisplayId, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers,
@@ -222,7 +220,7 @@ pub(crate) struct WaylandClientState {
data_offers: Vec<DataOffer<WlDataOffer>>,
primary_data_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
cursor: Cursor,
pending_activation: Option<PendingActivation>,
pending_open_uri: Option<String>,
event_loop: Option<EventLoop<'static, WaylandClientStatePtr>>,
common: LinuxCommon,
}
@@ -246,15 +244,6 @@ pub(crate) struct KeyRepeat {
current_keycode: Option<xkb::Keycode>,
}
pub(crate) enum PendingActivation {
/// URI to open in the web browser.
Uri(String),
/// Path to open in the file explorer.
Path(PathBuf),
/// A window from ourselves to raise.
Window(ObjectId),
}
/// This struct is required to conform to Rust's orphan rules, so we can dispatch on the state but hand the
/// window to GPUI.
#[derive(Clone)]
@@ -271,11 +260,6 @@ impl WaylandClientStatePtr {
self.0.upgrade().unwrap().borrow().serial_tracker.get(kind)
}
pub fn set_pending_activation(&self, window: ObjectId) {
self.0.upgrade().unwrap().borrow_mut().pending_activation =
Some(PendingActivation::Window(window));
}
pub fn enable_ime(&self) {
let client = self.get_client();
let mut state = client.borrow_mut();
@@ -326,7 +310,7 @@ impl WaylandClientStatePtr {
}
}
if state.windows.is_empty() {
state.common.signal.stop();
state.common.quit_signal.quit();
}
}
}
@@ -422,7 +406,7 @@ impl WaylandClient {
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let (common, main_receiver) = LinuxCommon::new(Box::new(event_loop.get_signal()), None);
let handle = event_loop.handle();
handle
@@ -459,7 +443,7 @@ impl WaylandClient {
let mut cursor = Cursor::new(&conn, &globals, 24);
handle
.insert_source(XDPEventSource::new(&common.background_executor), {
.insert_source(XDPEventSource::new(&common.background_executor, None), {
move |event, _, client| match event {
XDPEvent::WindowAppearance(appearance) => {
if let Some(client) = client.0.upgrade() {
@@ -546,7 +530,7 @@ impl WaylandClient {
data_offers: Vec::new(),
primary_data_offer: None,
cursor,
pending_activation: None,
pending_open_uri: None,
event_loop: Some(event_loop),
}));
@@ -645,33 +629,14 @@ impl LinuxClient for WaylandClient {
state.globals.activation.clone(),
state.mouse_focused_window.clone(),
) {
state.pending_activation = Some(PendingActivation::Uri(uri.to_string()));
state.pending_open_uri = Some(uri.to_owned());
let token = activation.get_activation_token(&state.globals.qh, ());
let serial = state.serial_tracker.get(SerialKind::MousePress);
token.set_serial(serial, &state.wl_seat);
token.set_surface(&window.surface());
token.commit();
} else {
let executor = state.common.background_executor.clone();
open_uri_internal(executor, uri, None);
}
}
fn reveal_path(&self, path: PathBuf) {
let mut state = self.0.borrow_mut();
if let (Some(activation), Some(window)) = (
state.globals.activation.clone(),
state.mouse_focused_window.clone(),
) {
state.pending_activation = Some(PendingActivation::Path(path));
let token = activation.get_activation_token(&state.globals.qh, ());
let serial = state.serial_tracker.get(SerialKind::MousePress);
token.set_serial(serial, &state.wl_seat);
token.set_surface(&window.surface());
token.commit();
} else {
let executor = state.common.background_executor.clone();
reveal_path_internal(executor, path, None);
open_uri_internal(uri, None);
}
}
@@ -844,7 +809,7 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
match event {
wl_callback::Event::Done { .. } => {
window.frame();
window.frame(true);
}
_ => {}
}
@@ -989,25 +954,13 @@ impl Dispatch<xdg_activation_token_v1::XdgActivationTokenV1, ()> for WaylandClie
) {
let client = this.get_client();
let mut state = client.borrow_mut();
if let xdg_activation_token_v1::Event::Done { token } = event {
let executor = state.common.background_executor.clone();
match state.pending_activation.take() {
Some(PendingActivation::Uri(uri)) => open_uri_internal(executor, &uri, Some(token)),
Some(PendingActivation::Path(path)) => {
reveal_path_internal(executor, path, Some(token))
}
Some(PendingActivation::Window(window)) => {
let Some(window) = get_window(&mut state, &window) else {
return;
};
let activation = state.globals.activation.as_ref().unwrap();
activation.activate(token, &window.surface());
}
None => log::error!("activation token received with no pending activation"),
if let Some(uri) = state.pending_open_uri.take() {
open_uri_internal(&uri, Some(&token));
} else {
log::error!("called while pending_open_uri is None");
}
}
token.destroy();
}
}
@@ -1241,7 +1194,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
&& state.repeat.current_keycode.is_some()
&& state.keyboard_focused_window.is_some();
if !is_repeating || rate == 0 {
if !is_repeating {
return TimeoutAction::Drop;
}
@@ -1555,11 +1508,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
if state.axis_source == AxisSource::Wheel {
return;
}
let axis = if state.modifiers.shift {
wl_pointer::Axis::HorizontalScroll
} else {
axis
};
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
@@ -1585,11 +1533,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
discrete,
} => {
state.scroll_event_received = true;
let axis = if state.modifiers.shift {
wl_pointer::Axis::HorizontalScroll
} else {
axis
};
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,
@@ -1612,11 +1555,6 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
value120,
} => {
state.scroll_event_received = true;
let axis = if state.modifiers.shift {
wl_pointer::Axis::HorizontalScroll
} else {
axis
};
let axis_modifier = match axis {
wl_pointer::Axis::VerticalScroll => state.vertical_modifier,
wl_pointer::Axis::HorizontalScroll => state.horizontal_modifier,

View File

@@ -76,7 +76,6 @@ pub struct WaylandWindowState {
acknowledged_first_configure: bool,
pub surface: wl_surface::WlSurface,
decoration: Option<zxdg_toplevel_decoration_v1::ZxdgToplevelDecorationV1>,
app_id: Option<String>,
appearance: WindowAppearance,
blur: Option<org_kde_kwin_blur::OrgKdeKwinBlur>,
toplevel: xdg_toplevel::XdgToplevel,
@@ -101,6 +100,7 @@ pub struct WaylandWindowState {
in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls,
inset: Option<Pixels>,
requested_inset: Option<Pixels>,
}
#[derive(Clone)]
@@ -159,7 +159,6 @@ impl WaylandWindowState {
acknowledged_first_configure: false,
surface,
decoration,
app_id: None,
blur: None,
toplevel,
viewport,
@@ -190,6 +189,7 @@ impl WaylandWindowState {
window_menu: true,
},
inset: None,
requested_inset: None,
})
}
@@ -316,11 +316,12 @@ impl WaylandWindowStatePtr {
Rc::ptr_eq(&self.state, &other.state)
}
pub fn frame(&self) {
let mut state = self.state.borrow_mut();
state.surface.frame(&state.globals.qh, state.surface.id());
drop(state);
pub fn frame(&self, request_frame_callback: bool) {
if request_frame_callback {
let mut state = self.state.borrow_mut();
state.surface.frame(&state.globals.qh, state.surface.id());
drop(state);
}
let mut cb = self.callbacks.borrow_mut();
if let Some(fun) = cb.request_frame.as_mut() {
fun();
@@ -350,12 +351,13 @@ impl WaylandWindowStatePtr {
state.fullscreen = configure.fullscreen;
state.maximized = configure.maximized;
state.tiling = configure.tiling;
if got_unmaximized {
configure.size = Some(state.window_bounds.size);
} else if !configure.maximized {
configure.size =
compute_outer_size(state.inset, configure.size, state.tiling);
}
if !configure.fullscreen && !configure.maximized {
configure.size = if got_unmaximized {
Some(state.window_bounds.size)
} else {
compute_outer_size(state.inset, configure.size, state.tiling)
};
if let Some(size) = configure.size {
state.window_bounds = Bounds {
origin: Point::default(),
@@ -371,27 +373,12 @@ impl WaylandWindowStatePtr {
}
let mut state = self.state.borrow_mut();
state.xdg_surface.ack_configure(serial);
let window_geometry = inset_by_tiling(
state.bounds.map_origin(|_| px(0.0)),
state.inset.unwrap_or(px(0.0)),
state.tiling,
)
.map(|v| v.0 as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
state.xdg_surface.set_window_geometry(
window_geometry.origin.x,
window_geometry.origin.y,
window_geometry.size.width,
window_geometry.size.height,
);
let request_frame_callback = !state.acknowledged_first_configure;
state.acknowledged_first_configure = true;
if request_frame_callback {
state.acknowledged_first_configure = true;
drop(state);
self.frame();
self.frame(true);
}
}
_ => {}
@@ -483,10 +470,6 @@ impl WaylandWindowStatePtr {
}
}
if fullscreen || maximized {
tiling = Tiling::tiled();
}
let mut state = self.state.borrow_mut();
state.in_progress_configure = Some(InProgressConfigure {
size,
@@ -825,20 +808,7 @@ impl PlatformWindow for WaylandWindow {
}
fn activate(&self) {
// Try to request an activation token. Even though the activation is likely going to be rejected,
// KWin and Mutter can use the app_id to visually indicate we're requesting attention.
let state = self.borrow();
if let (Some(activation), Some(app_id)) = (&state.globals.activation, state.app_id.clone())
{
state.client.set_pending_activation(state.surface.id());
let token = activation.get_activation_token(&state.globals.qh, ());
// The serial isn't exactly important here, since the activation is probably going to be rejected anyway.
let serial = state.client.get_serial(SerialKind::MousePress);
token.set_app_id(app_id);
token.set_serial(serial, &state.globals.seat);
token.set_surface(&state.surface);
token.commit();
}
log::info!("Wayland does not support this API");
}
fn is_active(&self) -> bool {
@@ -850,9 +820,7 @@ impl PlatformWindow for WaylandWindow {
}
fn set_app_id(&mut self, app_id: &str) {
let mut state = self.borrow_mut();
state.toplevel.set_app_id(app_id.to_owned());
state.app_id = Some(app_id.to_owned());
self.borrow().toplevel.set_app_id(app_id.to_owned());
}
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
@@ -925,7 +893,26 @@ impl PlatformWindow for WaylandWindow {
}
fn completed_frame(&self) {
let state = self.borrow();
let mut state = self.borrow_mut();
if let Some(area) = state.requested_inset {
state.inset = Some(area);
}
let window_geometry = inset_by_tiling(
state.bounds.map_origin(|_| px(0.0)),
state.inset.unwrap_or(px(0.0)),
state.tiling,
)
.map(|v| v.0 as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
state.xdg_surface.set_window_geometry(
window_geometry.origin.x,
window_geometry.origin.y,
window_geometry.size.width,
window_geometry.size.height,
);
state.surface.commit();
}
@@ -986,7 +973,7 @@ impl PlatformWindow for WaylandWindow {
fn set_client_inset(&self, inset: Pixels) {
let mut state = self.borrow_mut();
if Some(inset) != state.inset {
state.inset = Some(inset);
state.requested_inset = Some(inset);
update_window(state);
}
}

View File

@@ -1,23 +1,26 @@
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::path::PathBuf;
use std::os::fd::AsRawFd;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::{Duration, Instant};
use calloop::generic::{FdWrapper, Generic};
use calloop::{EventLoop, LoopHandle, RegistrationToken};
use anyhow::Context;
use async_task::Runnable;
use calloop::channel::Channel;
use collections::HashMap;
use util::ResultExt;
use futures::channel::oneshot;
use mio::{Interest, Token, Waker};
use util::ResultExt;
use x11rb::connection::{Connection, RequestConnection};
use x11rb::cursor;
use x11rb::errors::ConnectionError;
use x11rb::protocol::randr::ConnectionExt as _;
use x11rb::protocol::xinput::ConnectionExt;
use x11rb::protocol::xkb::ConnectionExt as _;
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _, KeyPressEvent};
use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt as _};
use x11rb::protocol::{randr, render, xinput, xkb, xproto, Event};
use x11rb::resource_manager::Database;
use x11rb::xcb_ffi::XCBConnection;
@@ -30,24 +33,24 @@ use crate::platform::linux::LinuxClient;
use crate::platform::{LinuxCommon, PlatformWindow};
use crate::{
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, ClipboardItem, CursorStyle,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, Platform, PlatformDisplay,
PlatformInput, Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
DisplayId, Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput,
Point, QuitSignal, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
use super::{X11Display, X11WindowStatePtr, XcbAtoms};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES};
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
use crate::platform::linux::{
get_xkb_compose_state, is_within_click_distance, open_uri_internal, reveal_path_internal,
use super::{
super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
X11Display, X11WindowStatePtr, XcbAtoms,
};
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
use super::{XimCallbackEvent, XimHandler};
use crate::platform::linux::is_within_click_distance;
use crate::platform::linux::platform::DOUBLE_CLICK_INTERVAL;
use crate::platform::linux::xdg_desktop_portal::{Event as XDPEvent, XDPEventSource};
pub(super) const XINPUT_MASTER_DEVICE: u16 = 1;
pub(crate) struct WindowRef {
window: X11WindowStatePtr,
refresh_event_token: RegistrationToken,
}
impl WindowRef {
@@ -95,15 +98,18 @@ impl From<xim::ClientError> for EventHandlerError {
}
pub struct X11ClientState {
pub(crate) loop_handle: LoopHandle<'static, X11Client>,
pub(crate) event_loop: Option<calloop::EventLoop<'static, X11Client>>,
/// poll is in an Option so we can take it out in `run()` without
/// mutating self.
poll: Option<mio::Poll>,
quit_signal_rx: oneshot::Receiver<()>,
runnables: Channel<Runnable>,
xdp_event_source: XDPEventSource,
pub(crate) last_click: Instant,
pub(crate) last_location: Point<Pixels>,
pub(crate) current_count: usize,
pub(crate) scale_factor: f32,
pub(crate) xcb_connection: Rc<XCBConnection>,
client_side_decorations_supported: bool,
pub(crate) x_root_index: usize,
@@ -119,7 +125,6 @@ pub struct X11ClientState {
pub(crate) compose_state: Option<xkbc::compose::State>,
pub(crate) pre_edit_text: Option<String>,
pub(crate) composing: bool,
pub(crate) pre_ime_key_down: Option<Keystroke>,
pub(crate) cursor_handle: cursor::Handle,
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
@@ -141,14 +146,46 @@ impl X11ClientStatePtr {
let client = X11Client(self.0.upgrade().expect("client already dropped"));
let mut state = client.0.borrow_mut();
if let Some(window_ref) = state.windows.remove(&x_window) {
state.loop_handle.remove(window_ref.refresh_event_token);
if state.windows.remove(&x_window).is_none() {
log::warn!(
"failed to remove X window {} from client state, does not exist",
x_window
);
}
state.cursor_styles.remove(&x_window);
if state.windows.is_empty() {
state.common.signal.stop();
state.common.quit_signal.quit();
}
}
}
struct ChannelQuitSignal {
tx: Option<oneshot::Sender<()>>,
waker: Option<Arc<Waker>>,
}
impl ChannelQuitSignal {
fn new(waker: Option<Arc<Waker>>) -> (Self, oneshot::Receiver<()>) {
let (tx, rx) = oneshot::channel::<()>();
let quit_signal = ChannelQuitSignal {
tx: Some(tx),
waker,
};
(quit_signal, rx)
}
}
impl QuitSignal for ChannelQuitSignal {
fn quit(&mut self) {
if let Some(tx) = self.tx.take() {
tx.send(()).log_err();
if let Some(waker) = self.waker.as_ref() {
waker.wake().ok();
}
}
}
}
@@ -158,27 +195,12 @@ pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
impl X11Client {
pub(crate) fn new() -> Self {
let event_loop = EventLoop::try_new().unwrap();
let mut poll = mio::Poll::new().unwrap();
let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
let waker = Arc::new(Waker::new(poll.registry(), WAKER_TOKEN).unwrap());
let handle = event_loop.handle();
handle
.insert_source(main_receiver, {
let handle = handle.clone();
move |event, _, _: &mut X11Client| {
if let calloop::channel::Event::Msg(runnable) = event {
// Insert the runnables as idle callbacks, so we make sure that user-input and X11
// events have higher priority and runnables are only worked off after the event
// callbacks.
handle.insert_idle(|_| {
runnable.run();
});
}
}
})
.unwrap();
let (quit_signal, quit_signal_rx) = ChannelQuitSignal::new(Some(waker.clone()));
let (common, runnables) = LinuxCommon::new(Box::new(quit_signal), Some(waker.clone()));
let (xcb_connection, x_root_index) = XCBConnection::connect(None).unwrap();
xcb_connection
@@ -289,47 +311,18 @@ impl X11Client {
None
};
// Safety: Safe if xcb::Connection always returns a valid fd
let fd = unsafe { FdWrapper::new(Rc::clone(&xcb_connection)) };
handle
.insert_source(
Generic::new_with_error::<EventHandlerError>(
fd,
calloop::Interest::READ,
calloop::Mode::Level,
),
{
let xcb_connection = xcb_connection.clone();
move |_readiness, _, client| {
client.process_x11_events(&xcb_connection)?;
Ok(calloop::PostAction::Continue)
}
},
)
.expect("Failed to initialize x11 event source");
handle
.insert_source(XDPEventSource::new(&common.background_executor), {
move |event, _, client| match event {
XDPEvent::WindowAppearance(appearance) => {
client.with_common(|common| common.appearance = appearance);
for (_, window) in &mut client.0.borrow_mut().windows {
window.window.set_appearance(appearance);
}
}
XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
// noop, X11 manages this for us.
}
}
})
.unwrap();
let xdp_event_source =
XDPEventSource::new(&common.background_executor, Some(waker.clone()));
X11Client(Rc::new(RefCell::new(X11ClientState {
modifiers: Modifiers::default(),
event_loop: Some(event_loop),
loop_handle: handle,
poll: Some(poll),
runnables,
xdp_event_source,
quit_signal_rx,
common,
modifiers: Modifiers::default(),
last_click: Instant::now(),
last_location: Point::new(px(0.0), px(0.0)),
current_count: 0,
@@ -348,7 +341,6 @@ impl X11Client {
compose_state,
pre_edit_text: None,
pre_ime_key_down: None,
composing: false,
cursor_handle,
@@ -364,125 +356,6 @@ impl X11Client {
})))
}
pub fn process_x11_events(
&self,
xcb_connection: &XCBConnection,
) -> Result<(), EventHandlerError> {
loop {
let mut events = Vec::new();
let mut windows_to_refresh = HashSet::new();
let mut last_key_release = None;
let mut last_key_press: Option<KeyPressEvent> = None;
loop {
match xcb_connection.poll_for_event() {
Ok(Some(event)) => {
match event {
Event::Expose(expose_event) => {
windows_to_refresh.insert(expose_event.window);
}
Event::KeyRelease(_) => {
last_key_release = Some(event);
}
Event::KeyPress(key_press) => {
if let Some(last_press) = last_key_press.as_ref() {
if last_press.detail == key_press.detail {
continue;
}
}
if let Some(Event::KeyRelease(key_release)) =
last_key_release.take()
{
// We ignore that last KeyRelease if it's too close to this KeyPress,
// suggesting that it's auto-generated by X11 as a key-repeat event.
if key_release.detail != key_press.detail
|| key_press.time.saturating_sub(key_release.time) > 20
{
events.push(Event::KeyRelease(key_release));
}
}
events.push(Event::KeyPress(key_press));
last_key_press = Some(key_press);
}
_ => {
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
events.push(event);
}
}
}
Ok(None) => {
// Add any remaining stored KeyRelease event
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
break;
}
Err(e) => {
log::warn!("error polling for X11 events: {e:?}");
break;
}
}
}
if events.is_empty() && windows_to_refresh.is_empty() {
break;
}
for window in windows_to_refresh.into_iter() {
if let Some(window) = self.get_window(window) {
window.refresh();
}
}
for event in events.into_iter() {
let mut state = self.0.borrow_mut();
if state.ximc.is_none() || state.xim_handler.is_none() {
drop(state);
self.handle_event(event);
continue;
}
let mut ximc = state.ximc.take().unwrap();
let mut xim_handler = state.xim_handler.take().unwrap();
let xim_connected = xim_handler.connected;
drop(state);
let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
Ok(handled) => handled,
Err(err) => {
log::error!("XIMClientError: {}", err);
false
}
};
let xim_callback_event = xim_handler.last_callback_event.take();
let mut state = self.0.borrow_mut();
state.ximc = Some(ximc);
state.xim_handler = Some(xim_handler);
drop(state);
if let Some(event) = xim_callback_event {
self.handle_xim_callback_event(event);
}
if xim_filtered {
continue;
}
if xim_connected {
self.xim_handle_event(event);
} else {
self.handle_event(event);
}
}
}
Ok(())
}
pub fn enable_ime(&self) {
let mut state = self.0.borrow_mut();
if state.ximc.is_none() {
@@ -545,6 +418,110 @@ impl X11Client {
.map(|window_reference| window_reference.window.clone())
}
fn read_x11_events(&self) -> (HashSet<u32>, Vec<Event>) {
let mut events = Vec::new();
let mut windows_to_refresh = HashSet::new();
let mut state = self.0.borrow_mut();
let mut last_key_release: Option<Event> = None;
loop {
match state.xcb_connection.poll_for_event() {
Ok(Some(event)) => {
if let Event::Expose(expose_event) = event {
windows_to_refresh.insert(expose_event.window);
} else {
match event {
Event::KeyRelease(_) => {
last_key_release = Some(event);
}
Event::KeyPress(key_press) => {
if let Some(Event::KeyRelease(key_release)) =
last_key_release.take()
{
// We ignore that last KeyRelease if it's too close to this KeyPress,
// suggesting that it's auto-generated by X11 as a key-repeat event.
if key_release.detail != key_press.detail
|| key_press.time.wrapping_sub(key_release.time) > 20
{
events.push(Event::KeyRelease(key_release));
}
}
events.push(Event::KeyPress(key_press));
}
_ => {
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
events.push(event);
}
}
}
}
Ok(None) => {
// Add any remaining stored KeyRelease event
if let Some(release_event) = last_key_release.take() {
events.push(release_event);
}
break;
}
Err(e) => {
log::warn!("error polling for X11 events: {e:?}");
break;
}
}
}
(windows_to_refresh, events)
}
fn process_x11_events(&self, events: Vec<Event>) {
for event in events.into_iter() {
let mut state = self.0.borrow_mut();
if state.ximc.is_none() || state.xim_handler.is_none() {
drop(state);
self.handle_event(event);
continue;
}
let mut ximc = state.ximc.take().unwrap();
let mut xim_handler = state.xim_handler.take().unwrap();
let xim_connected = xim_handler.connected;
drop(state);
// let xim_filtered = false;
let xim_filtered = match ximc.filter_event(&event, &mut xim_handler) {
Ok(handled) => handled,
Err(err) => {
log::error!("XIMClientError: {}", err);
false
}
};
let xim_callback_event = xim_handler.last_callback_event.take();
let mut state = self.0.borrow_mut();
state.ximc = Some(ximc);
state.xim_handler = Some(xim_handler);
if let Some(event) = xim_callback_event {
drop(state);
self.handle_xim_callback_event(event);
} else {
drop(state);
}
if xim_filtered {
continue;
}
if xim_connected {
self.xim_handle_event(event);
} else {
self.handle_event(event);
}
}
}
fn handle_event(&self, event: Event) -> Option<()> {
match event {
Event::ClientMessage(event) => {
@@ -584,6 +561,10 @@ impl X11Client {
let window = self.get_window(event.window)?;
window.property_notify(event);
}
Event::Expose(event) => {
let window = self.get_window(event.window)?;
window.refresh();
}
Event::FocusIn(event) => {
let window = self.get_window(event.event)?;
window.set_focused(true);
@@ -611,8 +592,8 @@ impl X11Client {
event.base_mods.into(),
event.latched_mods.into(),
event.locked_mods.into(),
event.base_group as u32,
event.latched_group as u32,
0,
0,
event.locked_group.into(),
);
@@ -636,7 +617,6 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
state.pre_ime_key_down.take();
let keystroke = {
let code = event.detail.into();
@@ -851,15 +831,10 @@ impl X11Client {
if let Some(old_scroll) = old_scroll {
let delta_scroll = old_scroll - new_scroll;
let (x, y) = if !modifiers.shift {
(0.0, delta_scroll)
} else {
(delta_scroll, 0.0)
};
window.handle_input(PlatformInput::ScrollWheel(
crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(x, y)),
delta: ScrollDelta::Lines(Point::new(0.0, delta_scroll)),
modifiers,
touch_phase: TouchPhase::default(),
},
@@ -916,11 +891,6 @@ impl X11Client {
match event {
Event::KeyPress(event) | Event::KeyRelease(event) => {
let mut state = self.0.borrow_mut();
state.pre_ime_key_down = Some(Keystroke::from_xkb(
&state.xkb,
state.modifiers,
event.detail.into(),
));
let mut ximc = state.ximc.take().unwrap();
let mut xim_handler = state.xim_handler.take().unwrap();
drop(state);
@@ -947,16 +917,6 @@ impl X11Client {
fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> {
let window = self.get_window(window).unwrap();
let mut state = self.0.borrow_mut();
if !state.composing {
if let Some(keystroke) = state.pre_ime_key_down.take() {
drop(state);
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
}));
return Some(());
}
}
state.composing = false;
drop(state);
@@ -1006,11 +966,13 @@ impl X11Client {
}
}
const XCB_CONNECTION_TOKEN: Token = Token(0);
const WAKER_TOKEN: Token = Token(1);
impl LinuxClient for X11Client {
fn compositor_name(&self) -> &'static str {
"X11"
}
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
f(&mut self.0.borrow_mut().common)
}
@@ -1077,61 +1039,8 @@ impl LinuxClient for X11Client {
state.common.appearance,
)?;
let screen_resources = state
.xcb_connection
.randr_get_screen_resources(x_window)
.unwrap()
.reply()
.expect("Could not find available screens");
let mode = screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = state
.xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
.ok()?
.reply()
.ok()?;
screen_resources
.modes
.iter()
.find(|m| m.id == crtc_info.mode)
})
.expect("Unable to find screen refresh rate");
let refresh_event_token = state
.loop_handle
.insert_source(calloop::timer::Timer::immediate(), {
let refresh_duration = mode_refresh_rate(mode);
move |mut instant, (), client| {
let xcb_connection = {
let state = client.0.borrow_mut();
let xcb_connection = state.xcb_connection.clone();
if let Some(window) = state.windows.get(&x_window) {
let window = window.window.clone();
drop(state);
window.refresh();
}
xcb_connection
};
client.process_x11_events(&xcb_connection).log_err();
// Take into account that some frames have been skipped
let now = Instant::now();
while instant < now {
instant += refresh_duration;
}
calloop::timer::TimeoutAction::ToInstant(instant)
}
})
.expect("Failed to initialize refresh timer");
let window_ref = WindowRef {
window: window.0.clone(),
refresh_event_token,
};
state.windows.insert(x_window, window_ref);
@@ -1173,17 +1082,11 @@ impl LinuxClient for X11Client {
..Default::default()
},
)
.expect("failed to change window cursor")
.check()
.unwrap();
.expect("failed to change window cursor");
}
fn open_uri(&self, uri: &str) {
open_uri_internal(self.background_executor(), uri, None);
}
fn reveal_path(&self, path: PathBuf) {
reveal_path_internal(self.background_executor(), path, None);
open_uri_internal(uri, None);
}
fn write_to_primary(&self, item: crate::ClipboardItem) {
@@ -1260,14 +1163,123 @@ impl LinuxClient for X11Client {
}
fn run(&self) {
let mut event_loop = self
let mut poll = self
.0
.borrow_mut()
.event_loop
.poll
.take()
.expect("App is already running");
.context("no poll set on X11Client. calling run more than once is not possible")
.unwrap();
event_loop.run(None, &mut self.clone(), |_| {}).log_err();
let xcb_fd = self.0.borrow().xcb_connection.as_raw_fd();
let mut xcb_source = mio::unix::SourceFd(&xcb_fd);
poll.registry()
.register(&mut xcb_source, XCB_CONNECTION_TOKEN, Interest::READABLE)
.unwrap();
let mut events = mio::Events::with_capacity(1024);
let mut next_refresh_needed = Instant::now();
'run_loop: loop {
let poll_timeout = next_refresh_needed - Instant::now();
// We rounding the poll_timeout down so `mio` doesn't round it up to the next higher milliseconds
let poll_timeout = Duration::from_millis(poll_timeout.as_millis() as u64);
if poll_timeout >= Duration::from_millis(1) {
let _ = poll.poll(&mut events, Some(poll_timeout));
};
let mut state = self.0.borrow_mut();
// Check if we need to quit
if let Ok(Some(())) = state.quit_signal_rx.try_recv() {
return;
}
// Redraw windows
let now = Instant::now();
if now > next_refresh_needed {
// This will be pulled down to 16ms (or less) if a window is open
let mut frame_length = Duration::from_millis(100);
let mut windows = vec![];
for (_, window_ref) in state.windows.iter() {
if !window_ref.window.state.borrow().destroyed {
frame_length = frame_length.min(window_ref.window.refresh_rate());
windows.push(window_ref.window.clone());
}
}
drop(state);
for window in windows {
window.refresh();
}
state = self.0.borrow_mut();
// In the case that we're looping a bit too fast, slow down
next_refresh_needed = now.max(next_refresh_needed) + frame_length;
}
// X11 events
drop(state);
loop {
let (x_windows, events) = self.read_x11_events();
for x_window in x_windows {
if let Some(window) = self.get_window(x_window) {
window.refresh();
}
}
if events.len() == 0 {
break;
}
self.process_x11_events(events);
// When X11 is sending us events faster than we can handle we'll
// let the frame rate drop to 10fps to try and avoid getting too behind.
if Instant::now() > next_refresh_needed + Duration::from_millis(80) {
continue 'run_loop;
}
}
state = self.0.borrow_mut();
// Runnables
while let Ok(runnable) = state.runnables.try_recv() {
drop(state);
runnable.run();
state = self.0.borrow_mut();
if Instant::now() + Duration::from_millis(1) >= next_refresh_needed {
continue 'run_loop;
}
}
// XDG events
if let Ok(event) = state.xdp_event_source.try_recv() {
match event {
XDPEvent::WindowAppearance(appearance) => {
let mut windows = state
.windows
.values()
.map(|window| window.window.clone())
.collect::<Vec<_>>();
drop(state);
self.with_common(|common| common.appearance = appearance);
for mut window in windows {
window.set_appearance(appearance);
}
}
XDPEvent::CursorTheme(_) | XDPEvent::CursorSize(_) => {
// noop, X11 manages this for us.
}
};
};
}
}
fn active_window(&self) -> Option<AnyWindowHandle> {
@@ -1281,19 +1293,6 @@ impl LinuxClient for X11Client {
}
}
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);
Duration::from_micros(micros)
}
fn fp3232_to_f32(value: xinput::Fp3232) -> f32 {
value.integral as f32 + value.frac as f32 / u32::MAX as f32
}

View File

@@ -15,6 +15,7 @@ use util::{maybe, ResultExt};
use x11rb::{
connection::Connection,
protocol::{
randr::{self, ConnectionExt as _},
sync,
xinput::{self, ConnectionExt as _},
xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
@@ -25,7 +26,7 @@ use x11rb::{
use std::{
cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
sync::Arc,
sync::Arc, time::Duration,
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
@@ -54,7 +55,6 @@ x11rb::atom_manager! {
_MOTIF_WM_HINTS,
_GTK_SHOW_WINDOW_MENU,
_GTK_FRAME_EXTENTS,
_GTK_EDGE_CONSTRAINTS,
}
}
@@ -86,49 +86,6 @@ impl ResizeEdge {
}
}
#[derive(Debug)]
struct EdgeConstraints {
top_tiled: bool,
#[allow(dead_code)]
top_resizable: bool,
right_tiled: bool,
#[allow(dead_code)]
right_resizable: bool,
bottom_tiled: bool,
#[allow(dead_code)]
bottom_resizable: bool,
left_tiled: bool,
#[allow(dead_code)]
left_resizable: bool,
}
impl EdgeConstraints {
fn from_atom(atom: u32) -> Self {
EdgeConstraints {
top_tiled: (atom & (1 << 0)) != 0,
top_resizable: (atom & (1 << 1)) != 0,
right_tiled: (atom & (1 << 2)) != 0,
right_resizable: (atom & (1 << 3)) != 0,
bottom_tiled: (atom & (1 << 4)) != 0,
bottom_resizable: (atom & (1 << 5)) != 0,
left_tiled: (atom & (1 << 6)) != 0,
left_resizable: (atom & (1 << 7)) != 0,
}
}
fn to_tiling(&self) -> Tiling {
Tiling {
top: self.top_tiled,
right: self.right_tiled,
bottom: self.bottom_tiled,
left: self.left_tiled,
}
}
}
#[derive(Debug)]
struct Visual {
id: xproto::Visualid,
@@ -220,6 +177,7 @@ pub struct Callbacks {
pub struct X11WindowState {
pub destroyed: bool,
refresh_rate: Duration,
client: X11ClientStatePtr,
executor: ForegroundExecutor,
atoms: XcbAtoms,
@@ -241,14 +199,14 @@ pub struct X11WindowState {
fullscreen: bool,
client_side_decorations_supported: bool,
decorations: WindowDecorations,
edge_constraints: Option<EdgeConstraints>,
pub handle: AnyWindowHandle,
last_insets: [u32; 4],
}
impl X11WindowState {
fn is_transparent(&self) -> bool {
self.background_appearance != WindowBackgroundAppearance::Opaque
self.decorations == WindowDecorations::Client
|| self.background_appearance != WindowBackgroundAppearance::Opaque
}
}
@@ -257,7 +215,7 @@ pub(crate) struct X11WindowStatePtr {
pub state: Rc<RefCell<X11WindowState>>,
pub(crate) callbacks: Rc<RefCell<Callbacks>>,
xcb_connection: Rc<XCBConnection>,
x_window: xproto::Window,
pub x_window: xproto::Window,
}
impl rwh::HasWindowHandle for RawWindow {
@@ -485,14 +443,36 @@ impl X11WindowState {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb_connection, x_window),
// We set it to transparent by default, even if we have client-side
// decorations, since those seem to work on X11 even without `true` here.
// If the window appearance changes, then the renderer will get updated
// too
transparent: false,
// In case we have window decorations to render
transparent: true,
};
xcb_connection.map_window(x_window).unwrap();
let screen_resources = xcb_connection
.randr_get_screen_resources(x_window)
.unwrap()
.reply()
.expect("Could not find available screens");
let mode = screen_resources
.crtcs
.iter()
.find_map(|crtc| {
let crtc_info = xcb_connection
.randr_get_crtc_info(*crtc, x11rb::CURRENT_TIME)
.ok()?
.reply()
.ok()?;
screen_resources
.modes
.iter()
.find(|m| m.id == crtc_info.mode)
})
.expect("Unable to find screen refresh rate");
let refresh_rate = mode_refresh_rate(&mode);
Ok(Self {
client,
executor,
@@ -518,9 +498,9 @@ impl X11WindowState {
client_side_decorations_supported,
decorations: WindowDecorations::Server,
last_insets: [0, 0, 0, 0],
edge_constraints: None,
counter_id: sync_request_counter,
last_sync_counter: None,
refresh_rate,
})
}
@@ -632,8 +612,6 @@ impl X11Window {
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.unwrap()
.check()
.unwrap();
}
@@ -711,30 +689,6 @@ impl X11WindowStatePtr {
let mut state = self.state.borrow_mut();
if event.atom == state.atoms._NET_WM_STATE {
self.set_wm_properties(state);
} else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
self.set_edge_constraints(state);
}
}
fn set_edge_constraints(&self, mut state: std::cell::RefMut<X11WindowState>) {
let reply = self
.xcb_connection
.get_property(
false,
self.x_window,
state.atoms._GTK_EDGE_CONSTRAINTS,
xproto::AtomEnum::CARDINAL,
0,
4,
)
.unwrap()
.reply()
.unwrap();
if reply.value_len != 0 {
let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
let edge_constraints = EdgeConstraints::from_atom(atom);
state.edge_constraints.replace(edge_constraints);
}
}
@@ -930,6 +884,10 @@ impl X11WindowStatePtr {
(fun)()
}
}
pub fn refresh_rate(&self) -> Duration {
self.state.borrow().refresh_rate
}
}
impl PlatformWindow for X11Window {
@@ -1039,7 +997,6 @@ impl PlatformWindow for X11Window {
xproto::Time::CURRENT_TIME,
)
.log_err();
self.0.xcb_connection.flush().unwrap();
}
fn is_active(&self) -> bool {
@@ -1068,7 +1025,6 @@ impl PlatformWindow for X11Window {
title.as_bytes(),
)
.unwrap();
self.0.xcb_connection.flush().unwrap();
}
fn set_app_id(&mut self, app_id: &str) {
@@ -1086,8 +1042,6 @@ impl PlatformWindow for X11Window {
xproto::AtomEnum::STRING,
&data,
)
.unwrap()
.check()
.unwrap();
}
@@ -1123,8 +1077,6 @@ impl PlatformWindow for X11Window {
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.unwrap()
.check()
.unwrap();
}
@@ -1182,7 +1134,6 @@ impl PlatformWindow for X11Window {
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
}
#[profiling::function]
fn draw(&self, scene: &Scene) {
let mut inner = self.0.state.borrow_mut();
inner.renderer.draw(scene);
@@ -1216,8 +1167,6 @@ impl PlatformWindow for X11Window {
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.unwrap()
.check()
.unwrap();
}
@@ -1241,20 +1190,15 @@ impl PlatformWindow for X11Window {
match state.decorations {
WindowDecorations::Server => Decorations::Server,
WindowDecorations::Client => {
let tiling = if state.fullscreen {
Tiling::tiled()
} else if let Some(edge_constraints) = &state.edge_constraints {
edge_constraints.to_tiling()
} else {
// https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
Tiling {
// https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
Decorations::Client {
tiling: Tiling {
top: state.maximized_vertical,
bottom: state.maximized_vertical,
left: state.maximized_horizontal,
right: state.maximized_horizontal,
}
};
Decorations::Client { tiling }
},
}
}
}
}
@@ -1264,28 +1208,17 @@ impl PlatformWindow for X11Window {
let dp = (inset.0 * state.scale_factor) as u32;
let insets = if state.fullscreen {
[0, 0, 0, 0]
} else if let Some(edge_constraints) = &state.edge_constraints {
let left = if edge_constraints.left_tiled { 0 } else { dp };
let top = if edge_constraints.top_tiled { 0 } else { dp };
let right = if edge_constraints.right_tiled { 0 } else { dp };
let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
[left, right, top, bottom]
let (left, right) = if state.maximized_horizontal {
(0, 0)
} else {
let (left, right) = if state.maximized_horizontal {
(0, 0)
} else {
(dp, dp)
};
let (top, bottom) = if state.maximized_vertical {
(0, 0)
} else {
(dp, dp)
};
[left, right, top, bottom]
(dp, dp)
};
let (top, bottom) = if state.maximized_vertical {
(0, 0)
} else {
(dp, dp)
};
let insets = [left, right, top, bottom];
if state.last_insets != insets {
state.last_insets = insets;
@@ -1301,8 +1234,6 @@ impl PlatformWindow for X11Window {
4,
bytemuck::cast_slice::<u32, u8>(&insets),
)
.unwrap()
.check()
.unwrap();
}
}
@@ -1336,8 +1267,6 @@ impl PlatformWindow for X11Window {
5,
bytemuck::cast_slice::<u32, u8>(&hints_data),
)
.unwrap()
.check()
.unwrap();
match decorations {
@@ -1360,3 +1289,16 @@ impl PlatformWindow for X11Window {
}
}
}
// Adapted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate(mode: &randr::ModeInfo) -> Duration {
if mode.dot_clock == 0 || mode.htotal == 0 || mode.vtotal == 0 {
return Duration::from_millis(16);
}
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
let micros = 1_000_000_000 / millihertz;
log::info!("Refreshing at {} micros", micros);
Duration::from_micros(micros)
}

View File

@@ -2,9 +2,13 @@
//!
//! This module uses the [ashpd] crate
use std::sync::Arc;
use anyhow::anyhow;
use ashpd::desktop::settings::{ColorScheme, Settings};
use calloop::channel::Channel;
use calloop::channel::{Channel, Sender};
use calloop::{EventSource, Poll, PostAction, Readiness, Token, TokenFactory};
use mio::Waker;
use smol::stream::StreamExt;
use crate::{BackgroundExecutor, WindowAppearance};
@@ -20,31 +24,45 @@ pub struct XDPEventSource {
}
impl XDPEventSource {
pub fn new(executor: &BackgroundExecutor) -> Self {
pub fn new(executor: &BackgroundExecutor, waker: Option<Arc<Waker>>) -> Self {
let (sender, channel) = calloop::channel::channel();
let background = executor.clone();
executor
.spawn(async move {
fn send_event<T>(
sender: &Sender<T>,
waker: &Option<Arc<Waker>>,
event: T,
) -> Result<(), std::sync::mpsc::SendError<T>> {
sender.send(event)?;
if let Some(waker) = waker {
waker.wake().ok();
};
Ok(())
}
let settings = Settings::new().await?;
if let Ok(initial_appearance) = settings.color_scheme().await {
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
initial_appearance,
)))?;
send_event(
&sender,
&waker,
Event::WindowAppearance(WindowAppearance::from_native(initial_appearance)),
)?;
}
if let Ok(initial_theme) = settings
.read::<String>("org.gnome.desktop.interface", "cursor-theme")
.await
{
sender.send(Event::CursorTheme(initial_theme))?;
send_event(&sender, &waker, Event::CursorTheme(initial_theme))?;
}
if let Ok(initial_size) = settings
.read::<u32>("org.gnome.desktop.interface", "cursor-size")
.await
{
sender.send(Event::CursorSize(initial_size))?;
send_event(&sender, &waker, Event::CursorSize(initial_size))?;
}
if let Ok(mut cursor_theme_changed) = settings
@@ -55,11 +73,12 @@ impl XDPEventSource {
.await
{
let sender = sender.clone();
let waker = waker.clone();
background
.spawn(async move {
while let Some(theme) = cursor_theme_changed.next().await {
let theme = theme?;
sender.send(Event::CursorTheme(theme))?;
send_event(&sender, &waker, Event::CursorTheme(theme))?;
}
anyhow::Ok(())
})
@@ -74,11 +93,12 @@ impl XDPEventSource {
.await
{
let sender = sender.clone();
let waker = waker.clone();
background
.spawn(async move {
while let Some(size) = cursor_size_changed.next().await {
let size = size?;
sender.send(Event::CursorSize(size))?;
send_event(&sender, &waker, Event::CursorSize(size))?;
}
anyhow::Ok(())
})
@@ -87,9 +107,11 @@ impl XDPEventSource {
let mut appearance_changed = settings.receive_color_scheme_changed().await?;
while let Some(scheme) = appearance_changed.next().await {
sender.send(Event::WindowAppearance(WindowAppearance::from_native(
scheme,
)))?;
send_event(
&sender,
&waker,
Event::WindowAppearance(WindowAppearance::from_native(scheme)),
)?;
}
anyhow::Ok(())
@@ -98,6 +120,12 @@ impl XDPEventSource {
Self { channel }
}
pub fn try_recv(&self) -> anyhow::Result<Event> {
self.channel
.try_recv()
.map_err(|error| anyhow!("{}", error))
}
}
impl EventSource for XDPEventSource {

View File

@@ -49,8 +49,7 @@ struct DirectWriteComponent {
struct GlyphRenderContext {
params: IDWriteRenderingParams3,
normal_dc_target: ID2D1DeviceContext4,
emoji_dc_target: ID2D1DeviceContext4,
dc_target: ID2D1DeviceContext4,
}
// All use of the IUnknown methods should be "thread-safe".
@@ -128,16 +127,7 @@ impl GlyphRenderContext {
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
grid_fit_mode,
)?;
let normal_dc_target = {
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
DXGI_FORMAT_A8_UNORM,
D2D1_ALPHA_MODE_STRAIGHT,
))?;
let target = target.cast::<ID2D1DeviceContext4>()?;
target.SetTextRenderingParams(&params);
target
};
let emoji_dc_target = {
let dc_target = {
let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_PREMULTIPLIED,
@@ -147,11 +137,7 @@ impl GlyphRenderContext {
target
};
Ok(Self {
params,
normal_dc_target,
emoji_dc_target,
})
Ok(Self { params, dc_target })
}
}
}
@@ -571,11 +557,7 @@ impl DirectWriteState {
}
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let render_target = if params.is_emoji {
&self.components.render_context.emoji_dc_target
} else {
&self.components.render_context.normal_dc_target
};
let render_target = &self.components.render_context.dc_target;
unsafe {
render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor);

View File

@@ -659,13 +659,6 @@ fn handle_calc_client_size(
requested_client_rect[0].left += frame_x + padding;
requested_client_rect[0].bottom -= frame_y + padding;
if state_ptr.state.borrow().is_maximized() {
requested_client_rect[0].top += frame_y + padding;
} else {
// Magic number that calculates the width of the border
requested_client_rect[0].top += frame_y - 3;
}
Some(0)
}
@@ -828,14 +821,14 @@ fn handle_hit_test_msg(
let dpi = unsafe { GetDpiForWindow(handle) };
let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
let mut cursor_point = POINT {
x: lparam.signed_loword().into(),
y: lparam.signed_hiword().into(),
};
unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
if !state_ptr.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y
{
if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
return Some(HTTOP as _);
}
@@ -1051,8 +1044,6 @@ fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Optio
let mut lock = state_ptr.state.borrow_mut();
// mouse wheel
lock.system_settings.mouse_wheel_settings.update();
// mouse double click
lock.click_state.system_update();
Some(0)
}
@@ -1268,7 +1259,7 @@ fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool {
}
#[inline]
pub(crate) fn current_modifiers() -> Modifiers {
fn current_modifiers() -> Modifiers {
Modifiers {
control: is_virtual_key_pressed(VK_CONTROL),
alt: is_virtual_key_pressed(VK_MENU),

View File

@@ -27,7 +27,10 @@ use windows::{
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
},
UI::ViewManagement::UISettings,
UI::{
Color,
ViewManagement::{UIColorType, UISettings},
},
};
use crate::*;
@@ -675,6 +678,25 @@ fn load_icon() -> Result<HICON> {
Ok(HICON(handle.0))
}
// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
#[inline]
fn system_appearance() -> Result<WindowAppearance> {
let ui_settings = UISettings::new()?;
let foreground_color = ui_settings.GetColorValue(UIColorType::Foreground)?;
// If the foreground is light, then is_color_light will evaluate to true,
// meaning Dark mode is enabled.
if is_color_light(&foreground_color) {
Ok(WindowAppearance::Dark)
} else {
Ok(WindowAppearance::Light)
}
}
#[inline(always)]
fn is_color_light(color: &Color) -> bool {
((5 * color.G as u32) + (2 * color.R as u32) + color.B as u32) > (8 * 128)
}
#[inline]
fn should_auto_hide_scrollbars() -> Result<bool> {
let ui_settings = UISettings::new()?;

View File

@@ -1,13 +1,7 @@
use std::sync::OnceLock;
use ::util::ResultExt;
use windows::{
Win32::{Foundation::*, UI::WindowsAndMessaging::*},
UI::{
Color,
ViewManagement::{UIColorType, UISettings},
},
};
use windows::Win32::{Foundation::*, UI::WindowsAndMessaging::*};
use crate::*;
@@ -124,22 +118,3 @@ pub(crate) fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels>
y: px(y / scale_factor),
}
}
// https://learn.microsoft.com/en-us/windows/apps/desktop/modernize/apply-windows-themes
#[inline]
pub(crate) fn system_appearance() -> Result<WindowAppearance> {
let ui_settings = UISettings::new()?;
let foreground_color = ui_settings.GetColorValue(UIColorType::Foreground)?;
// If the foreground is light, then is_color_light will evaluate to true,
// meaning Dark mode is enabled.
if is_color_light(&foreground_color) {
Ok(WindowAppearance::Dark)
} else {
Ok(WindowAppearance::Light)
}
}
#[inline(always)]
fn is_color_light(color: &Color) -> bool {
((5 * color.G as u32) + (2 * color.R as u32) + color.B as u32) > (8 * 128)
}

View File

@@ -252,7 +252,7 @@ impl WindowsWindow {
.titlebar
.as_ref()
.map(|titlebar| titlebar.appears_transparent)
.unwrap_or(true);
.unwrap_or(false);
let windowname = HSTRING::from(
params
.titlebar
@@ -383,8 +383,9 @@ impl PlatformWindow for WindowsWindow {
self.0.state.borrow().scale_factor
}
// todo(windows)
fn appearance(&self) -> WindowAppearance {
system_appearance().log_err().unwrap_or_default()
WindowAppearance::Dark
}
fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
@@ -404,8 +405,9 @@ impl PlatformWindow for WindowsWindow {
logical_point(point.x as f32, point.y as f32, scale_factor)
}
// todo(windows)
fn modifiers(&self) -> Modifiers {
current_modifiers()
Modifiers::none()
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
@@ -785,25 +787,15 @@ pub(crate) struct ClickState {
button: MouseButton,
last_click: Instant,
last_position: Point<DevicePixels>,
double_click_spatial_tolerance_width: i32,
double_click_spatial_tolerance_height: i32,
double_click_interval: Duration,
pub(crate) current_count: usize,
}
impl ClickState {
pub fn new() -> Self {
let double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
let double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
let double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
ClickState {
button: MouseButton::Left,
last_click: Instant::now(),
last_position: Point::default(),
double_click_spatial_tolerance_width,
double_click_spatial_tolerance_height,
double_click_interval,
current_count: 0,
}
}
@@ -822,19 +814,13 @@ impl ClickState {
self.current_count
}
pub fn system_update(&mut self) {
self.double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
self.double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
self.double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
}
#[inline]
fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
let diff = self.last_position - new_position;
self.last_click.elapsed() < self.double_click_interval
&& diff.x.0.abs() <= self.double_click_spatial_tolerance_width
&& diff.y.0.abs() <= self.double_click_spatial_tolerance_height
self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
&& diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
&& diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
}
}
@@ -942,6 +928,10 @@ fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) {
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
mod windows_renderer {
use std::{num::NonZeroIsize, sync::Arc};

View File

@@ -1,14 +1,11 @@
use crate::TextStyleRefinement;
use crate::{
self as gpui, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, DefiniteLength,
Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla, JustifyContent, Length,
SharedString, StyleRefinement, WhiteSpace,
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
JustifyContent, Length, Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
};
pub use gpui_macros::{
box_shadow_style_methods, cursor_style_methods, margin_style_methods, overflow_style_methods,
padding_style_methods, position_style_methods, visibility_style_methods,
};
use taffy::style::{AlignContent, Display};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec};
use taffy::style::{AlignContent, Display, Overflow};
/// A trait for elements that can be styled.
/// Use this to opt-in to a CSS-like styling API.
@@ -17,13 +14,20 @@ pub trait Styled: Sized {
fn style(&mut self) -> &mut StyleRefinement;
gpui_macros::style_helpers!();
gpui_macros::visibility_style_methods!();
gpui_macros::margin_style_methods!();
gpui_macros::padding_style_methods!();
gpui_macros::position_style_methods!();
gpui_macros::overflow_style_methods!();
gpui_macros::cursor_style_methods!();
gpui_macros::box_shadow_style_methods!();
/// Sets the position of the element to `relative`.
/// [Docs](https://tailwindcss.com/docs/position)
fn relative(mut self) -> Self {
self.style().position = Some(Position::Relative);
self
}
/// Sets the position of the element to `absolute`.
/// [Docs](https://tailwindcss.com/docs/position)
fn absolute(mut self) -> Self {
self.style().position = Some(Position::Absolute);
self
}
/// Sets the display type of the element to `block`.
/// [Docs](https://tailwindcss.com/docs/display)
@@ -39,6 +43,195 @@ pub trait Styled: Sized {
self
}
/// Sets the visibility of the element to `visible`.
/// [Docs](https://tailwindcss.com/docs/visibility)
fn visible(mut self) -> Self {
self.style().visibility = Some(Visibility::Visible);
self
}
/// Sets the visibility of the element to `hidden`.
/// [Docs](https://tailwindcss.com/docs/visibility)
fn invisible(mut self) -> Self {
self.style().visibility = Some(Visibility::Hidden);
self
}
/// Sets the behavior of content that overflows the container to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self.style().overflow.y = Some(Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the X axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_x_hidden(mut self) -> Self {
self.style().overflow.x = Some(Overflow::Hidden);
self
}
/// Sets the behavior of content that overflows the container on the Y axis to be hidden.
/// [Docs](https://tailwindcss.com/docs/overflow#hiding-content-that-overflows)
fn overflow_y_hidden(mut self) -> Self {
self.style().overflow.y = Some(Overflow::Hidden);
self
}
/// Set the cursor style when hovering over this element
fn cursor(mut self, cursor: CursorStyle) -> Self {
self.style().mouse_cursor = Some(cursor);
self
}
/// Sets the cursor style when hovering an element to `default`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_default(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::Arrow);
self
}
/// Sets the cursor style when hovering an element to `pointer`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_pointer(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::PointingHand);
self
}
/// Sets cursor style when hovering over an element to `text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeam);
self
}
/// Sets cursor style when hovering over an element to `move`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_move(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `not-allowed`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_not_allowed(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `context-menu`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_context_menu(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ContextualMenu);
self
}
/// Sets cursor style when hovering over an element to `crosshair`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_crosshair(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::Crosshair);
self
}
/// Sets cursor style when hovering over an element to `vertical-text`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_vertical_text(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::IBeamCursorForVerticalLayout);
self
}
/// Sets cursor style when hovering over an element to `alias`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_alias(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragLink);
self
}
/// Sets cursor style when hovering over an element to `copy`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_copy(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::DragCopy);
self
}
/// Sets cursor style when hovering over an element to `no-drop`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_no_drop(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OperationNotAllowed);
self
}
/// Sets cursor style when hovering over an element to `grab`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grab(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::OpenHand);
self
}
/// Sets cursor style when hovering over an element to `grabbing`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_grabbing(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ClosedHand);
self
}
/// Sets cursor style when hovering over an element to `ew-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_ew_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeftRight);
self
}
/// Sets cursor style when hovering over an element to `ns-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_ns_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUpDown);
self
}
/// Sets cursor style when hovering over an element to `col-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_col_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeColumn);
self
}
/// Sets cursor style when hovering over an element to `row-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_row_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeRow);
self
}
/// Sets cursor style when hovering over an element to `n-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_n_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeUp);
self
}
/// Sets cursor style when hovering over an element to `e-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_e_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeRight);
self
}
/// Sets cursor style when hovering over an element to `s-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_s_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeDown);
self
}
/// Sets cursor style when hovering over an element to `w-resize`.
/// [Docs](https://tailwindcss.com/docs/cursor)
fn cursor_w_resize(mut self) -> Self {
self.style().mouse_cursor = Some(CursorStyle::ResizeLeft);
self
}
/// Sets the whitespace of the element to `normal`.
/// [Docs](https://tailwindcss.com/docs/whitespace#normal)
fn whitespace_normal(mut self) -> Self {
@@ -306,6 +499,104 @@ pub trait Styled: Sized {
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self {
self.style().box_shadow = Some(shadows);
self
}
/// Clears the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_none(mut self) -> Self {
self.style().box_shadow = Some(Default::default());
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_sm(mut self) -> Self {
self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.05),
offset: point(px(0.), px(1.)),
blur_radius: px(2.),
spread_radius: px(0.),
}]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_md(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0.5, 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-1.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(2.)),
blur_radius: px(4.),
spread_radius: px(-2.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_lg(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(10.)),
blur_radius: px(15.),
spread_radius: px(-3.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(4.)),
blur_radius: px(6.),
spread_radius: px(-4.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_xl(mut self) -> Self {
self.style().box_shadow = Some(smallvec![
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(20.)),
blur_radius: px(25.),
spread_radius: px(-5.),
},
BoxShadow {
color: hsla(0., 0., 0., 0.1),
offset: point(px(0.), px(8.)),
blur_radius: px(10.),
spread_radius: px(-6.),
}
]);
self
}
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
fn shadow_2xl(mut self) -> Self {
self.style().box_shadow = Some(smallvec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
spread_radius: px(-12.),
}]);
self
}
/// Get the text style that has been configured on this element.
fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
let style: &mut StyleRefinement = self.style();

View File

@@ -142,7 +142,6 @@ impl TaffyLayoutEngine {
Ok(edges)
}
#[profiling::function]
pub fn compute_layout(
&mut self,
id: LayoutId,
@@ -162,7 +161,6 @@ impl TaffyLayoutEngine {
//
if !self.computed_layouts.insert(id) {
profiling::scope!("compute layout stack extension");
let mut stack = SmallVec::<[LayoutId; 64]>::new();
stack.push(id);
while let Some(id) = stack.pop() {
@@ -183,8 +181,6 @@ impl TaffyLayoutEngine {
id.into(),
available_space.into(),
|known_dimensions, available_space, node_id, _context| {
profiling::scope!("measure function");
let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
return taffy::geometry::Size::default();
};
@@ -194,10 +190,7 @@ impl TaffyLayoutEngine {
height: known_dimensions.height.map(Pixels),
};
{
profiling::scope!("calling measure");
measure(known_dimensions, available_space.into(), cx).into()
}
measure(known_dimensions, available_space.into(), cx).into()
},
)
.expect(EXPECT_MESSAGE);

View File

@@ -453,48 +453,17 @@ impl Frame {
}
}
#[profiling::function]
pub(crate) fn clear(&mut self) {
{
profiling::scope!("element_states clear");
self.element_states.clear();
}
{
profiling::scope!("accessed_element_states clear");
self.accessed_element_states.clear();
}
{
profiling::scope!("mouse_listeners clear");
self.mouse_listeners.clear();
}
{
profiling::scope!("dispatch_tree clear");
self.dispatch_tree.clear();
}
{
profiling::scope!("scene clear");
self.scene.clear();
}
{
profiling::scope!("input handlers clear");
self.input_handlers.clear();
}
{
profiling::scope!("tooltip_requests clear");
self.tooltip_requests.clear();
}
{
profiling::scope!("cursor styles clear");
self.cursor_styles.clear();
}
{
profiling::scope!("hitboxes clear");
self.hitboxes.clear();
}
{
profiling::scope!("deferred draws clear");
self.deferred_draws.clear();
}
self.element_states.clear();
self.accessed_element_states.clear();
self.mouse_listeners.clear();
self.dispatch_tree.clear();
self.scene.clear();
self.input_handlers.clear();
self.tooltip_requests.clear();
self.cursor_styles.clear();
self.hitboxes.clear();
self.deferred_draws.clear();
}
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
@@ -726,7 +695,6 @@ impl Window {
}
}));
platform_window.on_request_frame(Box::new({
profiling::scope!("on_request_frame");
let mut cx = cx.to_async();
let dirty = dirty.clone();
let active = active.clone();
@@ -1461,7 +1429,6 @@ impl<'a> WindowContext<'a> {
.next_frame
.finish(&mut self.window.rendered_frame);
ELEMENT_ARENA.with_borrow_mut(|element_arena| {
profiling::scope!("element area clear");
let percentage = (element_arena.len() as f32 / element_arena.capacity() as f32) * 100.;
if percentage >= 80. {
log::warn!("elevated element arena occupation: {}.", percentage);
@@ -1472,47 +1439,37 @@ impl<'a> WindowContext<'a> {
self.window.draw_phase = DrawPhase::Focus;
let previous_focus_path = self.window.rendered_frame.focus_path();
let previous_window_active = self.window.rendered_frame.window_active;
{
profiling::scope!("swapping frames");
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
}
{
profiling::scope!("clearing next frame");
self.window.next_frame.clear();
}
mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame);
self.window.next_frame.clear();
let current_focus_path = self.window.rendered_frame.focus_path();
let current_window_active = self.window.rendered_frame.window_active;
if previous_focus_path != current_focus_path
|| previous_window_active != current_window_active
{
profiling::scope!("updating focus path");
let current_focus_path = self.window.rendered_frame.focus_path();
let current_window_active = self.window.rendered_frame.window_active;
if previous_focus_path != current_focus_path
|| previous_window_active != current_window_active
{
if !previous_focus_path.is_empty() && current_focus_path.is_empty() {
self.window
.focus_lost_listeners
.clone()
.retain(&(), |listener| listener(self));
}
let event = WindowFocusEvent {
previous_focus_path: if previous_window_active {
previous_focus_path
} else {
Default::default()
},
current_focus_path: if current_window_active {
current_focus_path
} else {
Default::default()
},
};
if !previous_focus_path.is_empty() && current_focus_path.is_empty() {
self.window
.focus_listeners
.focus_lost_listeners
.clone()
.retain(&(), |listener| listener(&event, self));
.retain(&(), |listener| listener(self));
}
let event = WindowFocusEvent {
previous_focus_path: if previous_window_active {
previous_focus_path
} else {
Default::default()
},
current_focus_path: if current_window_active {
current_focus_path
} else {
Default::default()
},
};
self.window
.focus_listeners
.clone()
.retain(&(), |listener| listener(&event, self));
}
self.reset_cursor_style();
@@ -1530,7 +1487,6 @@ impl<'a> WindowContext<'a> {
profiling::finish_frame!();
}
#[profiling::function]
fn draw_roots(&mut self) {
self.window.draw_phase = DrawPhase::Prepaint;
self.window.tooltip_bounds.take();
@@ -1898,7 +1854,6 @@ impl<'a> WindowContext<'a> {
/// Updates the global element offset based on the given offset. This is used to implement
/// drag handles and other manual painting of elements. This method should only be called during
/// the prepaint phase of element drawing.
#[profiling::function]
pub fn with_absolute_element_offset<R>(
&mut self,
offset: Point<Pixels>,
@@ -2764,7 +2719,6 @@ impl<'a> WindowContext<'a> {
/// After calling it, you can request the bounds of the given layout node id or any descendant.
///
/// This method should only be called as part of the prepaint phase of element drawing.
#[profiling::function]
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
debug_assert_eq!(
self.window.draw_phase,
@@ -2773,10 +2727,7 @@ impl<'a> WindowContext<'a> {
);
let mut layout_engine = self.window.layout_engine.take().unwrap();
{
profiling::scope!("layout_engine compute_layout");
layout_engine.compute_layout(layout_id, available_space, self);
}
layout_engine.compute_layout(layout_id, available_space, self);
self.window.layout_engine = Some(layout_engine);
}

View File

@@ -1,7 +1,7 @@
mod derive_into_element;
mod derive_render;
mod register_action;
mod styles;
mod style_helpers;
mod test;
use proc_macro::TokenStream;
@@ -27,53 +27,11 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
derive_render::derive_render(input)
}
/// Used by GPUI to generate the style helpers.
/// Used by gpui to generate the style helpers.
#[proc_macro]
#[doc(hidden)]
pub fn style_helpers(input: TokenStream) -> TokenStream {
styles::style_helpers(input)
}
/// Generates methods for visibility styles.
#[proc_macro]
pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
styles::visibility_style_methods(input)
}
/// Generates methods for margin styles.
#[proc_macro]
pub fn margin_style_methods(input: TokenStream) -> TokenStream {
styles::margin_style_methods(input)
}
/// Generates methods for padding styles.
#[proc_macro]
pub fn padding_style_methods(input: TokenStream) -> TokenStream {
styles::padding_style_methods(input)
}
/// Generates methods for position styles.
#[proc_macro]
pub fn position_style_methods(input: TokenStream) -> TokenStream {
styles::position_style_methods(input)
}
/// Generates methods for overflow styles.
#[proc_macro]
pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
styles::overflow_style_methods(input)
}
/// Generates methods for cursor styles.
#[proc_macro]
pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
styles::cursor_style_methods(input)
}
/// Generates methods for box shadow styles.
#[proc_macro]
pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
styles::box_shadow_style_methods(input)
style_helpers::style_helpers(input)
}
/// #[gpui::test] can be used to annotate test functions that run with GPUI support.

View File

@@ -0,0 +1,569 @@
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
};
struct StyleableMacroInput;
impl Parse for StyleableMacroInput {
fn parse(_input: ParseStream) -> Result<Self> {
Ok(StyleableMacroInput)
}
}
pub fn style_helpers(input: TokenStream) -> TokenStream {
let _ = parse_macro_input!(input as StyleableMacroInput);
let methods = generate_methods();
let output = quote! {
#(#methods)*
};
output.into()
}
fn generate_methods() -> Vec<TokenStream2> {
let mut methods = Vec::new();
for (prefix, auto_allowed, fields, prefix_doc_string) in box_prefixes() {
methods.push(generate_custom_value_setter(
prefix,
if auto_allowed {
quote! { Length }
} else {
quote! { DefiniteLength }
},
&fields,
prefix_doc_string,
));
for (suffix, length_tokens, suffix_doc_string) in box_suffixes() {
if suffix != "auto" || auto_allowed {
methods.push(generate_predefined_setter(
prefix,
suffix,
&fields,
&length_tokens,
false,
&format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
));
}
if suffix != "auto" {
methods.push(generate_predefined_setter(
prefix,
suffix,
&fields,
&length_tokens,
true,
&format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
));
}
}
}
for (prefix, fields, prefix_doc_string) in corner_prefixes() {
methods.push(generate_custom_value_setter(
prefix,
quote! { AbsoluteLength },
&fields,
prefix_doc_string,
));
for (suffix, radius_tokens, suffix_doc_string) in corner_suffixes() {
methods.push(generate_predefined_setter(
prefix,
suffix,
&fields,
&radius_tokens,
false,
&format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
));
}
}
for (prefix, fields, prefix_doc_string) in border_prefixes() {
methods.push(generate_custom_value_setter(
prefix,
quote! { AbsoluteLength },
&fields,
prefix_doc_string,
));
for (suffix, width_tokens, suffix_doc_string) in border_suffixes() {
methods.push(generate_predefined_setter(
prefix,
suffix,
&fields,
&width_tokens,
false,
&format!("{prefix_doc_string}\n\n{suffix_doc_string}"),
));
}
}
methods
}
fn generate_predefined_setter(
name: &'static str,
length: &'static str,
fields: &[TokenStream2],
length_tokens: &TokenStream2,
negate: bool,
doc_string: &str,
) -> TokenStream2 {
let (negation_qualifier, negation_token) = if negate {
("_neg", quote! { - })
} else {
("", quote! {})
};
let method_name = if length.is_empty() {
format_ident!("{name}{negation_qualifier}")
} else {
format_ident!("{name}{negation_qualifier}_{length}")
};
let field_assignments = fields
.iter()
.map(|field_tokens| {
quote! {
style.#field_tokens = Some((#negation_token gpui::#length_tokens).into());
}
})
.collect::<Vec<_>>();
let method = quote! {
#[doc = #doc_string]
fn #method_name(mut self) -> Self {
let style = self.style();
#(#field_assignments)*
self
}
};
method
}
fn generate_custom_value_setter(
prefix: &str,
length_type: TokenStream2,
fields: &[TokenStream2],
doc_string: &str,
) -> TokenStream2 {
let method_name = format_ident!("{}", prefix);
let mut iter = fields.iter();
let last = iter.next_back().unwrap();
let field_assignments = iter
.map(|field_tokens| {
quote! {
style.#field_tokens = Some(length.clone().into());
}
})
.chain(std::iter::once(quote! {
style.#last = Some(length.into());
}))
.collect::<Vec<_>>();
let method = quote! {
#[doc = #doc_string]
fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui::#length_type>) -> Self {
let style = self.style();
#(#field_assignments)*
self
}
};
method
}
/// Returns a vec of (Property name, has 'auto' suffix, tokens for accessing the property, documentation)
fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>, &'static str)> {
vec![
(
"w",
true,
vec![quote! { size.width }],
"Sets the width of the element. [Docs](https://tailwindcss.com/docs/width)",
),
("h", true, vec![quote! { size.height }], "Sets the height of the element. [Docs](https://tailwindcss.com/docs/height)"),
(
"size",
true,
vec![quote! {size.width}, quote! {size.height}],
"Sets the width and height of the element."
),
// TODO: These don't use the same size ramp as the others
// see https://tailwindcss.com/docs/max-width
(
"min_w",
true,
vec![quote! { min_size.width }],
"Sets the minimum width of the element. [Docs](https://tailwindcss.com/docs/min-width)",
),
// TODO: These don't use the same size ramp as the others
// see https://tailwindcss.com/docs/max-width
(
"min_h",
true,
vec![quote! { min_size.height }],
"Sets the minimum height of the element. [Docs](https://tailwindcss.com/docs/min-height)",
),
// TODO: These don't use the same size ramp as the others
// see https://tailwindcss.com/docs/max-width
(
"max_w",
true,
vec![quote! { max_size.width }],
"Sets the maximum width of the element. [Docs](https://tailwindcss.com/docs/max-width)",
),
// TODO: These don't use the same size ramp as the others
// see https://tailwindcss.com/docs/max-width
(
"max_h",
true,
vec![quote! { max_size.height }],
"Sets the maximum height of the element. [Docs](https://tailwindcss.com/docs/max-height)",
),
(
"m",
true,
vec![
quote! { margin.top },
quote! { margin.bottom },
quote! { margin.left },
quote! { margin.right },
],
"Sets the margin of the element. [Docs](https://tailwindcss.com/docs/margin)"
),
("mt", true, vec![quote! { margin.top }], "Sets the top margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"),
(
"mb",
true,
vec![quote! { margin.bottom }],
"Sets the bottom margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"
),
(
"my",
true,
vec![quote! { margin.top }, quote! { margin.bottom }],
"Sets the vertical margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-vertical-margin)"
),
(
"mx",
true,
vec![quote! { margin.left }, quote! { margin.right }],
"Sets the horizontal margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-horizontal-margin)"
),
("ml", true, vec![quote! { margin.left }], "Sets the left margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"),
(
"mr",
true,
vec![quote! { margin.right }],
"Sets the right margin of the element. [Docs](https://tailwindcss.com/docs/margin#add-margin-to-a-single-side)"
),
(
"p",
false,
vec![
quote! { padding.top },
quote! { padding.bottom },
quote! { padding.left },
quote! { padding.right },
],
"Sets the padding of the element. [Docs](https://tailwindcss.com/docs/padding)"
),
(
"pt",
false,
vec![quote! { padding.top }],
"Sets the top padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
),
(
"pb",
false,
vec![quote! { padding.bottom }],
"Sets the bottom padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
),
(
"px",
false,
vec![quote! { padding.left }, quote! { padding.right }],
"Sets the horizontal padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-horizontal-padding)"
),
(
"py",
false,
vec![quote! { padding.top }, quote! { padding.bottom }],
"Sets the vertical padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-vertical-padding)"
),
(
"pl",
false,
vec![quote! { padding.left }],
"Sets the left padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
),
(
"pr",
false,
vec![quote! { padding.right }],
"Sets the right padding of the element. [Docs](https://tailwindcss.com/docs/padding#add-padding-to-a-single-side)"
),
(
"inset",
true,
vec![quote! { inset.top }, quote! { inset.right }, quote! { inset.bottom }, quote! { inset.left }],
"Sets the top, right, bottom, and left values of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"top",
true,
vec![quote! { inset.top }],
"Sets the top value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"bottom",
true,
vec![quote! { inset.bottom }],
"Sets the bottom value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"left",
true,
vec![quote! { inset.left }],
"Sets the left value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"right",
true,
vec![quote! { inset.right }],
"Sets the right value of a positioned element. [Docs](https://tailwindcss.com/docs/top-right-bottom-left)",
),
(
"gap",
false,
vec![quote! { gap.width }, quote! { gap.height }],
"Sets the gap between rows and columns in flex layouts. [Docs](https://tailwindcss.com/docs/gap)"
),
(
"gap_x",
false,
vec![quote! { gap.width }],
"Sets the gap between columns in flex layouts. [Docs](https://tailwindcss.com/docs/gap#changing-row-and-column-gaps-independently)"
),
(
"gap_y",
false,
vec![quote! { gap.height }],
"Sets the gap between rows in flex layouts. [Docs](https://tailwindcss.com/docs/gap#changing-row-and-column-gaps-independently)"
),
]
}
/// Returns a vec of (Suffix size, tokens that correspond to this size, documentation)
fn box_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("0", quote! { px(0.) }, "0px"),
("0p5", quote! { rems(0.125) }, "2px (0.125rem)"),
("1", quote! { rems(0.25) }, "4px (0.25rem)"),
("1p5", quote! { rems(0.375) }, "6px (0.375rem)"),
("2", quote! { rems(0.5) }, "8px (0.5rem)"),
("2p5", quote! { rems(0.625) }, "10px (0.625rem)"),
("3", quote! { rems(0.75) }, "12px (0.75rem)"),
("3p5", quote! { rems(0.875) }, "14px (0.875rem)"),
("4", quote! { rems(1.) }, "16px (1rem)"),
("5", quote! { rems(1.25) }, "20px (1.25rem)"),
("6", quote! { rems(1.5) }, "24px (1.5rem)"),
("7", quote! { rems(1.75) }, "28px (1.75rem)"),
("8", quote! { rems(2.0) }, "32px (2rem)"),
("9", quote! { rems(2.25) }, "36px (2.25rem)"),
("10", quote! { rems(2.5) }, "40px (2.5rem)"),
("11", quote! { rems(2.75) }, "44px (2.75rem)"),
("12", quote! { rems(3.) }, "48px (3rem)"),
("16", quote! { rems(4.) }, "64px (4rem)"),
("20", quote! { rems(5.) }, "80px (5rem)"),
("24", quote! { rems(6.) }, "96px (6rem)"),
("32", quote! { rems(8.) }, "128px (8rem)"),
("40", quote! { rems(10.) }, "160px (10rem)"),
("48", quote! { rems(12.) }, "192px (12rem)"),
("56", quote! { rems(14.) }, "224px (14rem)"),
("64", quote! { rems(16.) }, "256px (16rem)"),
("72", quote! { rems(18.) }, "288px (18rem)"),
("80", quote! { rems(20.) }, "320px (20rem)"),
("96", quote! { rems(24.) }, "384px (24rem)"),
("112", quote! { rems(28.) }, "448px (28rem)"),
("128", quote! { rems(32.) }, "512px (32rem)"),
("auto", quote! { auto() }, "Auto"),
("px", quote! { px(1.) }, "1px"),
("full", quote! { relative(1.) }, "100%"),
("1_2", quote! { relative(0.5) }, "50% (1/2)"),
("1_3", quote! { relative(1./3.) }, "33% (1/3)"),
("2_3", quote! { relative(2./3.) }, "66% (2/3)"),
("1_4", quote! { relative(0.25) }, "25% (1/4)"),
("2_4", quote! { relative(0.5) }, "50% (2/4)"),
("3_4", quote! { relative(0.75) }, "75% (3/4)"),
("1_5", quote! { relative(0.2) }, "20% (1/5)"),
("2_5", quote! { relative(0.4) }, "40% (2/5)"),
("3_5", quote! { relative(0.6) }, "60% (3/5)"),
("4_5", quote! { relative(0.8) }, "80% (4/5)"),
("1_6", quote! { relative(1./6.) }, "16% (1/6)"),
("5_6", quote! { relative(5./6.) }, "80% (5/6)"),
("1_12", quote! { relative(1./12.) }, "8% (1/12)"),
]
}
fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>, &'static str)> {
vec![
(
"rounded",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
quote! { corner_radii.bottom_left },
],
"Sets the border radius of the element. [Docs](https://tailwindcss.com/docs/border-radius)"
),
(
"rounded_t",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.top_right },
],
"Sets the border radius of the top side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
),
(
"rounded_b",
vec![
quote! { corner_radii.bottom_left },
quote! { corner_radii.bottom_right },
],
"Sets the border radius of the bottom side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
),
(
"rounded_r",
vec![
quote! { corner_radii.top_right },
quote! { corner_radii.bottom_right },
],
"Sets the border radius of the right side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
),
(
"rounded_l",
vec![
quote! { corner_radii.top_left },
quote! { corner_radii.bottom_left },
],
"Sets the border radius of the left side of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-sides-separately)"
),
(
"rounded_tl",
vec![quote! { corner_radii.top_left }],
"Sets the border radius of the top left corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
),
(
"rounded_tr",
vec![quote! { corner_radii.top_right }],
"Sets the border radius of the top right corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
),
(
"rounded_bl",
vec![quote! { corner_radii.bottom_left }],
"Sets the border radius of the bottom left corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
),
(
"rounded_br",
vec![quote! { corner_radii.bottom_right }],
"Sets the border radius of the bottom right corner of the element. [Docs](https://tailwindcss.com/docs/border-radius#rounding-corners-separately)"
),
]
}
fn corner_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("none", quote! { px(0.) }, "0px"),
("sm", quote! { rems(0.125) }, "2px (0.125rem)"),
("md", quote! { rems(0.25) }, "4px (0.25rem)"),
("lg", quote! { rems(0.5) }, "8px (0.5rem)"),
("xl", quote! { rems(0.75) }, "12px (0.75rem)"),
("2xl", quote! { rems(1.) }, "16px (1rem)"),
("3xl", quote! { rems(1.5) }, "24px (1.5rem)"),
("full", quote! { px(9999.) }, "9999px"),
]
}
fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>, &'static str)> {
vec![
(
"border",
vec![
quote! { border_widths.top },
quote! { border_widths.right },
quote! { border_widths.bottom },
quote! { border_widths.left },
],
"Sets the border width of the element. [Docs](https://tailwindcss.com/docs/border-width)"
),
(
"border_t",
vec![quote! { border_widths.top }],
"Sets the border width of the top side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
),
(
"border_b",
vec![quote! { border_widths.bottom }],
"Sets the border width of the bottom side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
),
(
"border_r",
vec![quote! { border_widths.right }],
"Sets the border width of the right side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
),
(
"border_l",
vec![quote! { border_widths.left }],
"Sets the border width of the left side of the element. [Docs](https://tailwindcss.com/docs/border-width#individual-sides)"
),
(
"border_x",
vec![
quote! { border_widths.left },
quote! { border_widths.right },
],
"Sets the border width of the vertical sides of the element. [Docs](https://tailwindcss.com/docs/border-width#horizontal-and-vertical-sides)"
),
(
"border_y",
vec![
quote! { border_widths.top },
quote! { border_widths.bottom },
],
"Sets the border width of the horizontal sides of the element. [Docs](https://tailwindcss.com/docs/border-width#horizontal-and-vertical-sides)"
),
]
}
fn border_suffixes() -> Vec<(&'static str, TokenStream2, &'static str)> {
vec![
("0", quote! { px(0.)}, "0px"),
("1", quote! { px(1.) }, "1px"),
("2", quote! { px(2.) }, "2px"),
("3", quote! { px(3.) }, "3px"),
("4", quote! { px(4.) }, "4px"),
("5", quote! { px(5.) }, "5px"),
("6", quote! { px(6.) }, "6px"),
("7", quote! { px(7.) }, "7px"),
("8", quote! { px(8.) }, "8px"),
("9", quote! { px(9.) }, "9px"),
("10", quote! { px(10.) }, "10px"),
("11", quote! { px(11.) }, "11px"),
("12", quote! { px(12.) }, "12px"),
("16", quote! { px(16.) }, "16px"),
("20", quote! { px(20.) }, "20px"),
("24", quote! { px(24.) }, "24px"),
("32", quote! { px(32.) }, "32px"),
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use gpui::{
};
use settings::Settings;
use std::ops::Range;
use theme::{color_alpha, ActiveTheme, ThemeSettings};
use theme::{ActiveTheme, ThemeSettings};
/// An outline of all the symbols contained in a buffer.
#[derive(Debug)]
@@ -146,15 +146,9 @@ impl<T> Outline<T> {
pub fn render_item<T>(
outline_item: &OutlineItem<T>,
match_ranges: impl IntoIterator<Item = Range<usize>>,
custom_highlights: impl IntoIterator<Item = (Range<usize>, HighlightStyle)>,
cx: &AppContext,
) -> StyledText {
let mut highlight_style = HighlightStyle::default();
highlight_style.background_color = Some(color_alpha(cx.theme().colors().text_accent, 0.3));
let custom_highlights = match_ranges
.into_iter()
.map(|range| (range, highlight_style));
let settings = ThemeSettings::get_global(cx);
// TODO: We probably shouldn't need to build a whole new text style here

View File

@@ -317,11 +317,8 @@ impl LspAdapter for NodeVersionAdapter {
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = latest_version.downcast::<GitHubLspBinaryVersion>().unwrap();
let destination_path = container_dir.join(format!(
"package-version-server-{}{}",
version.name,
std::env::consts::EXE_SUFFIX
));
let destination_path =
container_dir.join(format!("package-version-server-{}", version.name));
let destination_container_path =
container_dir.join(format!("package-version-server-{}-tmp", version.name));
if fs::metadata(&destination_path).await.is_err() {
@@ -343,10 +340,7 @@ impl LspAdapter for NodeVersionAdapter {
}
fs::copy(
destination_container_path.join(format!(
"package-version-server{}",
std::env::consts::EXE_SUFFIX
)),
destination_container_path.join("package-version-server"),
&destination_path,
)
.await?;

View File

@@ -9,7 +9,7 @@ brackets = [
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = false, newline = false, not_in = ["string"] },
]
auto_indent_using_last_non_empty_line = false

View File

@@ -18,9 +18,6 @@ use std::{
};
use util::{maybe, ResultExt};
#[cfg(target_os = "windows")]
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server.ps1";
#[cfg(not(target_os = "windows"))]
const SERVER_PATH: &str = "node_modules/.bin/tailwindcss-language-server";
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
@@ -111,39 +108,11 @@ impl LspAdapter for TailwindLspAdapter {
.await?;
}
#[cfg(target_os = "windows")]
{
let mut env_path = vec![self
.node
.binary_path()
.await?
.parent()
.expect("invalid node binary path")
.to_path_buf()];
if let Some(existing_path) = std::env::var_os("PATH") {
let mut paths = std::env::split_paths(&existing_path).collect::<Vec<_>>();
env_path.append(&mut paths);
}
let env_path = std::env::join_paths(env_path)?;
let mut env = HashMap::default();
env.insert("PATH".to_string(), env_path.to_string_lossy().to_string());
Ok(LanguageServerBinary {
path: "powershell.exe".into(),
env: Some(env),
arguments: server_binary_arguments(&server_path),
})
}
#[cfg(not(target_os = "windows"))]
{
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
env: None,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
@@ -189,25 +158,26 @@ impl LspAdapter for TailwindLspAdapter {
.unwrap_or_default()
})?;
let mut configuration = json!({
// We need to set this to null if it's not set, because tailwindcss-languageserver
// will check whether it's an object and if it is (even if it's empty) it will
// ignore the `userLanguages` from the initialization options.
let include_languages = tailwind_user_settings
.get("includeLanguages")
.cloned()
.unwrap_or(Value::Null);
let experimental = tailwind_user_settings
.get("experimental")
.cloned()
.unwrap_or_else(|| json!([]));
Ok(json!({
"tailwindCSS": {
"emmetCompletions": true,
"includeLanguages": include_languages,
"experimental": experimental,
}
});
if let Some(experimental) = tailwind_user_settings.get("experimental").cloned() {
configuration["tailwindCSS"]["experimental"] = experimental;
}
if let Some(class_attributes) = tailwind_user_settings.get("classAttributes").cloned() {
configuration["tailwindCSS"]["classAttributes"] = class_attributes;
}
if let Some(include_languages) = tailwind_user_settings.get("includeLanguages").cloned() {
configuration["tailwindCSS"]["includeLanguages"] = include_languages;
}
Ok(configuration)
}))
}
fn language_ids(&self) -> HashMap<String, String> {

View File

@@ -1,5 +0,0 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)

View File

@@ -0,0 +1 @@
../typescript/brackets.scm

View File

@@ -1,15 +0,0 @@
[
(call_expression)
(assignment_expression)
(member_expression)
(lexical_declaration)
(variable_declaration)
(assignment_expression)
(if_statement)
(for_statement)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View File

@@ -0,0 +1 @@
../typescript/indents.scm

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