Compare commits
4 Commits
animated-g
...
auto-comma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9182bd51f2 | ||
|
|
481a554923 | ||
|
|
c129e3b668 | ||
|
|
12e3c3b502 |
2
.github/workflows/ci.yml
vendored
@@ -254,7 +254,7 @@ jobs:
|
||||
target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
target/release/Zed.dmg
|
||||
body_path: target/release-notes.md
|
||||
body_file: target/release-notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ We plan to set aside time each week to pair program with contributors on promisi
|
||||
|
||||
Zed is made up of several smaller crates - let's go over those you're most likely to interact with:
|
||||
|
||||
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation.**
|
||||
- [`gpui`](/crates/gpui) is a GPU-accelerated UI framework which provides all of the building blocks for Zed. **We recommend familiarizing yourself with the root level GPUI documentation**
|
||||
- [`editor`](/crates/editor) contains the core `Editor` type that drives both the code editor and all various input fields within Zed. It also handles a display layer for LSP features such as Inlay Hints or code completions.
|
||||
- [`project`](/crates/project) manages files and navigation within the filetree. It is also Zed's side of communication with LSP.
|
||||
- [`workspace`](/crates/workspace) handles local state serialization and groups projects together.
|
||||
|
||||
47
Cargo.lock
generated
@@ -2551,13 +2551,18 @@ name = "collab_ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auto_update",
|
||||
"call",
|
||||
"channel",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"db",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"emojis",
|
||||
"extensions_ui",
|
||||
"feedback",
|
||||
"futures 0.3.28",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
@@ -2570,6 +2575,7 @@ dependencies = [
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"release_channel",
|
||||
"rich_text",
|
||||
"rpc",
|
||||
@@ -2581,14 +2587,15 @@ dependencies = [
|
||||
"smallvec",
|
||||
"story",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"time",
|
||||
"time_format",
|
||||
"title_bar",
|
||||
"tree-sitter-markdown",
|
||||
"ui",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4912,7 +4919,6 @@ dependencies = [
|
||||
"taffy",
|
||||
"thiserror",
|
||||
"time",
|
||||
"unicode-segmentation",
|
||||
"usvg",
|
||||
"util",
|
||||
"uuid",
|
||||
@@ -11039,41 +11045,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "title_bar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"auto_update",
|
||||
"call",
|
||||
"client",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"extensions_ui",
|
||||
"feedback",
|
||||
"gpui",
|
||||
"http 0.1.0",
|
||||
"notifications",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"recent_projects",
|
||||
"rpc",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
"story",
|
||||
"theme",
|
||||
"theme_selector",
|
||||
"tree-sitter-markdown",
|
||||
"ui",
|
||||
"util",
|
||||
"vcs_menu",
|
||||
"windows 0.57.0",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.37.0"
|
||||
@@ -13578,7 +13549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.143.0"
|
||||
version = "0.142.0"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
|
||||
30
Cargo.toml
@@ -21,7 +21,6 @@ members = [
|
||||
"crates/command_palette_hooks",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/dev_server_projects",
|
||||
"crates/diagnostics",
|
||||
"crates/editor",
|
||||
"crates/extension",
|
||||
@@ -78,11 +77,14 @@ members = [
|
||||
"crates/refineable",
|
||||
"crates/refineable/derive_refineable",
|
||||
"crates/release_channel",
|
||||
"crates/dev_server_projects",
|
||||
"crates/repl",
|
||||
"crates/rich_text",
|
||||
"crates/rope",
|
||||
"crates/rpc",
|
||||
"crates/rustdoc",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/search",
|
||||
"crates/semantic_index",
|
||||
"crates/semantic_version",
|
||||
@@ -93,20 +95,17 @@ members = [
|
||||
"crates/story",
|
||||
"crates/storybook",
|
||||
"crates/sum_tree",
|
||||
"crates/tab_switcher",
|
||||
"crates/supermaven",
|
||||
"crates/supermaven_api",
|
||||
"crates/tab_switcher",
|
||||
"crates/task",
|
||||
"crates/tasks_ui",
|
||||
"crates/telemetry_events",
|
||||
"crates/terminal",
|
||||
"crates/terminal_view",
|
||||
"crates/text",
|
||||
"crates/theme",
|
||||
"crates/theme_importer",
|
||||
"crates/theme_selector",
|
||||
"crates/telemetry_events",
|
||||
"crates/time_format",
|
||||
"crates/title_bar",
|
||||
"crates/ui",
|
||||
"crates/ui_text_field",
|
||||
"crates/util",
|
||||
@@ -176,7 +175,6 @@ command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
dashmap = "5.5.3"
|
||||
db = { path = "crates/db" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
@@ -197,9 +195,9 @@ gpui_macros = { path = "crates/gpui_macros" }
|
||||
headless = { path = "crates/headless" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http = { path = "crates/http" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
inline_completion_button = { path = "crates/inline_completion_button" }
|
||||
install_cli = { path = "crates/install_cli" }
|
||||
journal = { path = "crates/journal" }
|
||||
language = { path = "crates/language" }
|
||||
language_selector = { path = "crates/language_selector" }
|
||||
@@ -225,17 +223,21 @@ plugin = { path = "crates/plugin" }
|
||||
plugin_macros = { path = "crates/plugin_macros" }
|
||||
prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
proto = { path = "crates/proto" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
proto = { path = "crates/proto" }
|
||||
quick_action_bar = { path = "crates/quick_action_bar" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
release_channel = { path = "crates/release_channel" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
repl = { path = "crates/repl" }
|
||||
rich_text = { path = "crates/rich_text" }
|
||||
rope = { path = "crates/rope" }
|
||||
rpc = { path = "crates/rpc" }
|
||||
rustdoc = { path = "crates/rustdoc" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
search = { path = "crates/search" }
|
||||
semantic_index = { path = "crates/semantic_index" }
|
||||
semantic_version = { path = "crates/semantic_version" }
|
||||
@@ -243,23 +245,20 @@ settings = { path = "crates/settings" }
|
||||
snippet = { path = "crates/snippet" }
|
||||
sqlez = { path = "crates/sqlez" }
|
||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
story = { path = "crates/story" }
|
||||
storybook = { path = "crates/storybook" }
|
||||
sum_tree = { path = "crates/sum_tree" }
|
||||
supermaven = { path = "crates/supermaven" }
|
||||
supermaven_api = { path = "crates/supermaven_api" }
|
||||
tab_switcher = { path = "crates/tab_switcher" }
|
||||
task = { path = "crates/task" }
|
||||
tasks_ui = { path = "crates/tasks_ui" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
terminal = { path = "crates/terminal" }
|
||||
terminal_view = { path = "crates/terminal_view" }
|
||||
text = { path = "crates/text" }
|
||||
theme = { path = "crates/theme" }
|
||||
theme_importer = { path = "crates/theme_importer" }
|
||||
theme_selector = { path = "crates/theme_selector" }
|
||||
telemetry_events = { path = "crates/telemetry_events" }
|
||||
time_format = { path = "crates/time_format" }
|
||||
title_bar = { path = "crates/title_bar" }
|
||||
ui = { path = "crates/ui" }
|
||||
ui_text_field = { path = "crates/ui_text_field" }
|
||||
util = { path = "crates/util" }
|
||||
@@ -267,7 +266,6 @@ vcs_menu = { path = "crates/vcs_menu" }
|
||||
vim = { path = "crates/vim" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
workspace = { path = "crates/workspace" }
|
||||
worktree = { path = "crates/worktree" }
|
||||
zed = { path = "crates/zed" }
|
||||
zed_actions = { path = "crates/zed_actions" }
|
||||
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,92 +0,0 @@
|
||||
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
assets/fonts/zed-mono/zed-mono-extended.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendedbold.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendedbolditalic.ttf
Normal file
BIN
assets/fonts/zed-mono/zed-mono-extendeditalic.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extended.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendedbold.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendedbolditalic.ttf
Normal file
BIN
assets/fonts/zed-sans/zed-sans-extendeditalic.ttf
Normal file
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/></svg>
|
||||
|
Before Width: | Height: | Size: 289 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-copy"><path d="M2 16V4a2 2 0 0 1 2-2h11"/><path d="M5 14H4a2 2 0 1 0 0 4h1"/><path d="M22 18H11a2 2 0 1 0 0 4h11V6H11a2 2 0 0 0-2 2v12"/></svg>
|
||||
|
Before Width: | Height: | Size: 351 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-plus"><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/><path d="M9 10h6"/><path d="M12 7v6"/></svg>
|
||||
|
Before Width: | Height: | Size: 332 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
||||
|
Before Width: | Height: | Size: 276 B |
@@ -94,7 +94,6 @@
|
||||
"lua": "lua",
|
||||
"m4a": "audio",
|
||||
"m4v": "video",
|
||||
"markdown": "document",
|
||||
"md": "document",
|
||||
"mdb": "storage",
|
||||
"mdf": "storage",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-type"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" x2="15" y1="20" y2="20"/><line x1="12" x2="12" y1="4" y2="20"/></svg>
|
||||
|
Before Width: | Height: | Size: 329 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-a-large-small"><path d="M21 14h-5"/><path d="M16 16v-3.5a2.5 2.5 0 0 1 5 0V16"/><path d="M4.5 13h6"/><path d="m3 16 4.5-9 4.5 9"/></svg>
|
||||
|
Before Width: | Height: | Size: 339 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bold"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>
|
||||
|
Before Width: | Height: | Size: 296 B |
@@ -1,6 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 13.6667H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 2.33333H12" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 11L8 5L11 11" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 9H10" stroke="#B3B3B3" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 539 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-eye"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
|
Before Width: | Height: | Size: 301 B |
@@ -70,14 +70,6 @@
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"a": "project_panel::NewFile",
|
||||
"shift-a": "project_panel::NewDirectory",
|
||||
"f2": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-d": "project_panel::Duplicate",
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
"ctrl-[": "project_panel::CollapseSelectedEntry",
|
||||
"ctrl-b": "project_panel::CollapseSelectedEntry",
|
||||
"alt-b": "project_panel::CollapseSelectedEntry",
|
||||
|
||||
@@ -55,13 +55,11 @@
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::LineUp",
|
||||
"ctrl-down": "editor::LineDown",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"alt-pageup": "editor::PageUp",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"alt-pagedown": "editor::PageDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
@@ -589,9 +587,8 @@
|
||||
"alt-ctrl-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"delete": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"backspace": "project_panel::Trash",
|
||||
"delete": "project_panel::Trash",
|
||||
"ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"alt-ctrl-r": "project_panel::RevealInFinder",
|
||||
@@ -659,13 +656,7 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom"
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -61,17 +61,13 @@
|
||||
"cmd-shift-z": "editor::Redo",
|
||||
"up": "editor::MoveUp",
|
||||
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||
"pageup": "editor::MovePageUp",
|
||||
"shift-pageup": "editor::SelectPageUp",
|
||||
"cmd-pageup": "editor::PageUp",
|
||||
"ctrl-pageup": "editor::LineUp",
|
||||
"pageup": "editor::PageUp",
|
||||
"shift-pageup": "editor::MovePageUp",
|
||||
"home": "editor::MoveToBeginningOfLine",
|
||||
"down": "editor::MoveDown",
|
||||
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||
"pagedown": "editor::MovePageDown",
|
||||
"shift-pagedown": "editor::SelectPageDown",
|
||||
"cmd-pagedown": "editor::PageDown",
|
||||
"ctrl-pagedown": "editor::LineDown",
|
||||
"pagedown": "editor::PageDown",
|
||||
"shift-pagedown": "editor::MovePageDown",
|
||||
"end": "editor::MoveToEndOfLine",
|
||||
"left": "editor::MoveLeft",
|
||||
"right": "editor::MoveRight",
|
||||
@@ -609,7 +605,6 @@
|
||||
"left": "project_panel::CollapseSelectedEntry",
|
||||
"right": "project_panel::ExpandSelectedEntry",
|
||||
"cmd-n": "project_panel::NewFile",
|
||||
"cmd-d": "project_panel::Duplicate",
|
||||
"alt-cmd-n": "project_panel::NewDirectory",
|
||||
"cmd-x": "project_panel::Cut",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
@@ -619,9 +614,8 @@
|
||||
"enter": "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-backspace": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"cmd-delete": ["project_panel::Delete", { "skip_prompt": false }],
|
||||
"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",
|
||||
@@ -703,13 +697,7 @@
|
||||
"pagedown": ["terminal::SendKeystroke", "pagedown"],
|
||||
"escape": ["terminal::SendKeystroke", "escape"],
|
||||
"enter": ["terminal::SendKeystroke", "enter"],
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"],
|
||||
"shift-pageup": "terminal::ScrollPageUp",
|
||||
"shift-pagedown": "terminal::ScrollPageDown",
|
||||
"shift-up": "terminal::ScrollLineUp",
|
||||
"shift-down": "terminal::ScrollLineDown",
|
||||
"shift-home": "terminal::ScrollToTop",
|
||||
"shift-end": "terminal::ScrollToBottom"
|
||||
"ctrl-c": ["terminal::SendKeystroke", "ctrl-c"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
"bindings": {
|
||||
"cmd-shift-o": "file_finder::Toggle",
|
||||
"cmd-shift-a": "command_palette::Toggle",
|
||||
"shift shift": "command_palette::Toggle",
|
||||
"cmd-alt-o": "project_symbols::Toggle",
|
||||
"cmd-1": "workspace::ToggleLeftDock",
|
||||
"cmd-6": "diagnostics::Deploy"
|
||||
@@ -95,10 +94,6 @@
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"enter": "project_panel::Open",
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +87,7 @@
|
||||
},
|
||||
{
|
||||
"context": "ProjectPanel",
|
||||
"bindings": {
|
||||
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": true }],
|
||||
"cmd-d": "project_panel::Duplicate",
|
||||
"cmd-n": "project_panel::NewFolder",
|
||||
"return": "project_panel::Rename",
|
||||
"cmd-c": "project_panel::Copy",
|
||||
"cmd-v": "project_panel::Paste",
|
||||
"cmd-alt-c": "project_panel::CopyPath"
|
||||
}
|
||||
"bindings": {}
|
||||
},
|
||||
{
|
||||
"context": "Dock",
|
||||
|
||||
@@ -47,16 +47,19 @@
|
||||
"{": "vim::StartOfParagraph",
|
||||
"}": "vim::EndOfParagraph",
|
||||
"|": "vim::GoToColumn",
|
||||
|
||||
// Word motions
|
||||
"w": "vim::NextWordStart",
|
||||
"e": "vim::NextWordEnd",
|
||||
"b": "vim::PreviousWordStart",
|
||||
"g e": "vim::PreviousWordEnd",
|
||||
|
||||
// Subword motions
|
||||
// "w": "vim::NextSubwordStart",
|
||||
// "b": "vim::PreviousSubwordStart",
|
||||
// "e": "vim::NextSubwordEnd",
|
||||
// "g e": "vim::PreviousSubwordEnd",
|
||||
|
||||
"shift-w": [
|
||||
"vim::NextWordStart",
|
||||
{
|
||||
@@ -75,12 +78,8 @@
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"g shift-e": [
|
||||
"vim::PreviousWordEnd",
|
||||
{
|
||||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"g shift-e": ["vim::PreviousWordEnd", { "ignorePunctuation": true }],
|
||||
|
||||
"/": "vim::Search",
|
||||
"g /": "pane::DeploySearch",
|
||||
"?": [
|
||||
@@ -126,39 +125,16 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"m": [
|
||||
"vim::PushOperator",
|
||||
"Mark"
|
||||
],
|
||||
"'": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Jump": {
|
||||
"line": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"`": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Jump": {
|
||||
"line": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"m": ["vim::PushOperator", "Mark"],
|
||||
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
|
||||
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
|
||||
";": "vim::RepeatFind",
|
||||
",": "vim::RepeatFindReversed",
|
||||
"ctrl-o": "pane::GoBack",
|
||||
"ctrl-i": "pane::GoForward",
|
||||
"ctrl-]": "editor::GoToDefinition",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl-[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
"v": "vim::ToggleVisual",
|
||||
"shift-v": "vim::ToggleVisualLine",
|
||||
"ctrl-v": "vim::ToggleVisualBlock",
|
||||
@@ -284,10 +260,7 @@
|
||||
// z commands
|
||||
"z t": "editor::ScrollCursorTop",
|
||||
"z z": "editor::ScrollCursorCenter",
|
||||
"z .": [
|
||||
"workspace::SendKeystrokes",
|
||||
"z z ^"
|
||||
],
|
||||
"z .": ["workspace::SendKeystrokes", "z z ^"],
|
||||
"z b": "editor::ScrollCursorBottom",
|
||||
"z c": "editor::Fold",
|
||||
"z o": "editor::UnfoldLines",
|
||||
@@ -305,123 +278,36 @@
|
||||
}
|
||||
],
|
||||
// Count support
|
||||
"1": [
|
||||
"vim::Number",
|
||||
1
|
||||
],
|
||||
"2": [
|
||||
"vim::Number",
|
||||
2
|
||||
],
|
||||
"3": [
|
||||
"vim::Number",
|
||||
3
|
||||
],
|
||||
"4": [
|
||||
"vim::Number",
|
||||
4
|
||||
],
|
||||
"5": [
|
||||
"vim::Number",
|
||||
5
|
||||
],
|
||||
"6": [
|
||||
"vim::Number",
|
||||
6
|
||||
],
|
||||
"7": [
|
||||
"vim::Number",
|
||||
7
|
||||
],
|
||||
"8": [
|
||||
"vim::Number",
|
||||
8
|
||||
],
|
||||
"9": [
|
||||
"vim::Number",
|
||||
9
|
||||
],
|
||||
"1": ["vim::Number", 1],
|
||||
"2": ["vim::Number", 2],
|
||||
"3": ["vim::Number", 3],
|
||||
"4": ["vim::Number", 4],
|
||||
"5": ["vim::Number", 5],
|
||||
"6": ["vim::Number", 6],
|
||||
"7": ["vim::Number", 7],
|
||||
"8": ["vim::Number", 8],
|
||||
"9": ["vim::Number", 9],
|
||||
// window related commands (ctrl-w X)
|
||||
"ctrl-w left": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"ctrl-w right": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"ctrl-w up": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w down": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Down"
|
||||
],
|
||||
"ctrl-w h": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"ctrl-w l": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"ctrl-w k": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w j": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Down"
|
||||
],
|
||||
"ctrl-w ctrl-h": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"ctrl-w ctrl-l": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"ctrl-w ctrl-k": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w ctrl-j": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Down"
|
||||
],
|
||||
"ctrl-w shift-left": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"ctrl-w shift-right": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"ctrl-w shift-up": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w shift-down": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Down"
|
||||
],
|
||||
"ctrl-w shift-h": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"ctrl-w shift-l": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"ctrl-w shift-k": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w shift-j": [
|
||||
"workspace::SwapPaneInDirection",
|
||||
"Down"
|
||||
],
|
||||
"ctrl-w left": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w right": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w up": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w down": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w ctrl-h": ["workspace::ActivatePaneInDirection", "Left"],
|
||||
"ctrl-w ctrl-l": ["workspace::ActivatePaneInDirection", "Right"],
|
||||
"ctrl-w ctrl-k": ["workspace::ActivatePaneInDirection", "Up"],
|
||||
"ctrl-w ctrl-j": ["workspace::ActivatePaneInDirection", "Down"],
|
||||
"ctrl-w shift-left": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-w shift-right": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-up": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-w shift-down": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-w shift-h": ["workspace::SwapPaneInDirection", "Left"],
|
||||
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
|
||||
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
|
||||
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
|
||||
"ctrl-w g t": "pane::ActivateNextItem",
|
||||
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
||||
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
||||
@@ -443,14 +329,9 @@
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems",
|
||||
"ctrl-w o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-w n": [
|
||||
"workspace::NewFileInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w ctrl-n": [
|
||||
"workspace::NewFileInDirection",
|
||||
"Up"
|
||||
],
|
||||
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
|
||||
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
|
||||
|
||||
"ctrl-w d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w g d": "editor::GoToDefinitionSplit",
|
||||
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
|
||||
@@ -472,21 +353,12 @@
|
||||
"context": "Editor && vim_mode == normal && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
".": "vim::Repeat",
|
||||
"c": [
|
||||
"vim::PushOperator",
|
||||
"Change"
|
||||
],
|
||||
"c": ["vim::PushOperator", "Change"],
|
||||
"shift-c": "vim::ChangeToEndOfLine",
|
||||
"d": [
|
||||
"vim::PushOperator",
|
||||
"Delete"
|
||||
],
|
||||
"d": ["vim::PushOperator", "Delete"],
|
||||
"shift-d": "vim::DeleteToEndOfLine",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"y": [
|
||||
"vim::PushOperator",
|
||||
"Yank"
|
||||
],
|
||||
"y": ["vim::PushOperator", "Yank"],
|
||||
"shift-y": "vim::YankLine",
|
||||
"i": "vim::InsertBefore",
|
||||
"shift-i": "vim::InsertFirstNonWhitespace",
|
||||
@@ -508,36 +380,15 @@
|
||||
],
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
],
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"s": "vim::Substitute",
|
||||
"shift-s": "vim::SubstituteLine",
|
||||
">": [
|
||||
"vim::PushOperator",
|
||||
"Indent"
|
||||
],
|
||||
"<": [
|
||||
"vim::PushOperator",
|
||||
"Outdent"
|
||||
],
|
||||
"g u": [
|
||||
"vim::PushOperator",
|
||||
"Lowercase"
|
||||
],
|
||||
"g shift-u": [
|
||||
"vim::PushOperator",
|
||||
"Uppercase"
|
||||
],
|
||||
"g ~": [
|
||||
"vim::PushOperator",
|
||||
"OppositeCase"
|
||||
],
|
||||
"\"": [
|
||||
"vim::PushOperator",
|
||||
"Register"
|
||||
],
|
||||
">": ["vim::PushOperator", "Indent"],
|
||||
"<": ["vim::PushOperator", "Outdent"],
|
||||
"g u": ["vim::PushOperator", "Lowercase"],
|
||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||
// tree-sitter related commands
|
||||
@@ -552,10 +403,7 @@
|
||||
{
|
||||
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
||||
"bindings": {
|
||||
"\"": [
|
||||
"vim::PushOperator",
|
||||
"Register"
|
||||
],
|
||||
"\"": ["vim::PushOperator", "Register"],
|
||||
// tree-sitter related commands
|
||||
"[ x": "editor::SelectLargerSyntaxNode",
|
||||
"] x": "editor::SelectSmallerSyntaxNode"
|
||||
@@ -564,10 +412,7 @@
|
||||
{
|
||||
"context": "Editor && VimCount && vim_mode != insert",
|
||||
"bindings": {
|
||||
"0": [
|
||||
"vim::Number",
|
||||
0
|
||||
]
|
||||
"0": ["vim::Number", 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -618,10 +463,7 @@
|
||||
{
|
||||
"context": "Editor && vim_mode == normal && vim_operator == d",
|
||||
"bindings": {
|
||||
"s": [
|
||||
"vim::PushOperator",
|
||||
"DeleteSurrounds"
|
||||
]
|
||||
"s": ["vim::PushOperator", "DeleteSurrounds"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -743,22 +585,10 @@
|
||||
"shift-i": "vim::InsertBefore",
|
||||
"shift-a": "vim::InsertAfter",
|
||||
"shift-j": "vim::JoinLines",
|
||||
"r": [
|
||||
"vim::PushOperator",
|
||||
"Replace"
|
||||
],
|
||||
"ctrl-c": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl-[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"r": ["vim::PushOperator", "Replace"],
|
||||
"ctrl-c": ["vim::SwitchMode", "Normal"],
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"],
|
||||
">": "vim::Indent",
|
||||
"<": "vim::Outdent",
|
||||
"i": [
|
||||
@@ -806,10 +636,7 @@
|
||||
"ctrl-u": "editor::DeleteToBeginningOfLine",
|
||||
"ctrl-t": "vim::Indent",
|
||||
"ctrl-d": "vim::Outdent",
|
||||
"ctrl-r": [
|
||||
"vim::PushOperator",
|
||||
"Register"
|
||||
]
|
||||
"ctrl-r": ["vim::PushOperator", "Register"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -828,14 +655,8 @@
|
||||
"bindings": {
|
||||
"tab": "vim::Tab",
|
||||
"enter": "vim::Enter",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl-[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
"escape": ["vim::SwitchMode", "Normal"],
|
||||
"ctrl-[": ["vim::SwitchMode", "Normal"]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -855,8 +676,7 @@
|
||||
{
|
||||
"context": "EmptyPane || SharedScreen",
|
||||
"bindings": {
|
||||
":": "command_palette::Toggle",
|
||||
"g /": "pane::DeploySearch"
|
||||
":": "command_palette::Toggle"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
{
|
||||
// The name of the Zed theme to use for the UI.
|
||||
//
|
||||
// `mode` is one of:
|
||||
// The theme can also be set to follow system preferences:
|
||||
//
|
||||
// "theme": {
|
||||
// "mode": "system",
|
||||
// "light": "One Light",
|
||||
// "dark": "One Dark"
|
||||
// }
|
||||
//
|
||||
// Where `mode` is one of:
|
||||
// - "system": Use the theme that corresponds to the system's appearance
|
||||
// - "light": Use the theme indicated by the "light" field
|
||||
// - "dark": Use the theme indicated by the "dark" field
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark",
|
||||
},
|
||||
"theme": "One Dark",
|
||||
// The name of a base set of key bindings to use.
|
||||
// This setting can take four values, each named after another
|
||||
// text editor:
|
||||
@@ -25,7 +29,7 @@
|
||||
"inline_completion_provider": "copilot"
|
||||
},
|
||||
// The name of a font to use for rendering text in the editor
|
||||
"buffer_font_family": "Zed Plex Mono",
|
||||
"buffer_font_family": "Zed Mono",
|
||||
// The OpenType features to enable for text in the editor.
|
||||
"buffer_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -47,8 +51,7 @@
|
||||
// },
|
||||
"buffer_line_height": "comfortable",
|
||||
// The name of a font to use for rendering text in the UI
|
||||
// (On macOS) You can set this to ".SysmtemUIFont" to use the system font
|
||||
"ui_font_family": "Zed Plex Sans",
|
||||
"ui_font_family": ".SystemUIFont",
|
||||
// The OpenType features to enable for text in the UI
|
||||
"ui_font_features": {
|
||||
// Disable ligatures:
|
||||
@@ -406,9 +409,7 @@
|
||||
// The list of language servers to use (or disable) for all languages.
|
||||
//
|
||||
// This is typically customized on a per-language basis.
|
||||
"language_servers": [
|
||||
"..."
|
||||
],
|
||||
"language_servers": ["..."],
|
||||
// When to automatically save edited buffers. This setting can
|
||||
// take four values.
|
||||
//
|
||||
@@ -543,9 +544,7 @@
|
||||
},
|
||||
"inline_completions": {
|
||||
// A list of globs representing files that inline completions should be disabled for.
|
||||
"disabled_globs": [
|
||||
".env"
|
||||
]
|
||||
"disabled_globs": [".env"]
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -656,12 +655,7 @@
|
||||
// Default directories to search for virtual environments, relative
|
||||
// to the current working directory. We recommend overriding this
|
||||
// in your project's settings, rather than globally.
|
||||
"directories": [
|
||||
".env",
|
||||
"env",
|
||||
".venv",
|
||||
"venv"
|
||||
],
|
||||
"directories": [".env", "env", ".venv", "venv"],
|
||||
// Can also be `csh`, `fish`, and `nushell`
|
||||
"activate_script": "default"
|
||||
}
|
||||
@@ -675,7 +669,7 @@
|
||||
// "font_size": 15,
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Plex Mono",
|
||||
// "font_family": "Zed Mono",
|
||||
// Sets the maximum number of lines in the terminal's scrollback buffer.
|
||||
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
|
||||
// Existing terminals will not pick up this change until they are recreated.
|
||||
@@ -694,12 +688,7 @@
|
||||
// "TOML": ["Embargo.lock"]
|
||||
// }
|
||||
//
|
||||
"file_types": {
|
||||
"JSONC": [
|
||||
"**/.zed/**/*.json",
|
||||
"**/zed/**/*.json"
|
||||
]
|
||||
},
|
||||
"file_types": {},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
// If you don't want any of these extensions, add this field to your settings
|
||||
@@ -712,9 +701,7 @@
|
||||
"Astro": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-astro"
|
||||
]
|
||||
"plugins": ["prettier-plugin-astro"]
|
||||
}
|
||||
},
|
||||
"Blade": {
|
||||
@@ -734,12 +721,7 @@
|
||||
}
|
||||
},
|
||||
"Elixir": {
|
||||
"language_servers": [
|
||||
"elixir-ls",
|
||||
"!next-ls",
|
||||
"!lexical",
|
||||
"..."
|
||||
]
|
||||
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
|
||||
},
|
||||
"Go": {
|
||||
"code_actions_on_format": {
|
||||
@@ -752,12 +734,7 @@
|
||||
}
|
||||
},
|
||||
"HEEX": {
|
||||
"language_servers": [
|
||||
"elixir-ls",
|
||||
"!next-ls",
|
||||
"!lexical",
|
||||
"..."
|
||||
]
|
||||
"language_servers": ["elixir-ls", "!next-ls", "!lexical", "..."]
|
||||
},
|
||||
"HTML": {
|
||||
"prettier": {
|
||||
@@ -767,17 +744,11 @@
|
||||
"Java": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-java"
|
||||
]
|
||||
"plugins": ["prettier-plugin-java"]
|
||||
}
|
||||
},
|
||||
"JavaScript": {
|
||||
"language_servers": [
|
||||
"!typescript-language-server",
|
||||
"vtsls",
|
||||
"..."
|
||||
],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -796,17 +767,11 @@
|
||||
"PHP": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"@prettier/plugin-php"
|
||||
]
|
||||
"plugins": ["@prettier/plugin-php"]
|
||||
}
|
||||
},
|
||||
"Ruby": {
|
||||
"language_servers": [
|
||||
"solargraph",
|
||||
"!ruby-lsp",
|
||||
"..."
|
||||
]
|
||||
"language_servers": ["solargraph", "!ruby-lsp", "..."]
|
||||
},
|
||||
"SCSS": {
|
||||
"prettier": {
|
||||
@@ -816,25 +781,17 @@
|
||||
"SQL": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-sql"
|
||||
]
|
||||
"plugins": ["prettier-plugin-sql"]
|
||||
}
|
||||
},
|
||||
"Svelte": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
]
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
}
|
||||
},
|
||||
"TSX": {
|
||||
"language_servers": [
|
||||
"!typescript-language-server",
|
||||
"vtsls",
|
||||
"..."
|
||||
],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -845,11 +802,7 @@
|
||||
}
|
||||
},
|
||||
"TypeScript": {
|
||||
"language_servers": [
|
||||
"!typescript-language-server",
|
||||
"vtsls",
|
||||
"..."
|
||||
],
|
||||
"language_servers": ["!typescript-language-server", "vtsls", "..."],
|
||||
"prettier": {
|
||||
"allowed": true
|
||||
}
|
||||
@@ -862,9 +815,7 @@
|
||||
"XML": {
|
||||
"prettier": {
|
||||
"allowed": true,
|
||||
"plugins": [
|
||||
"@prettier/plugin-xml"
|
||||
]
|
||||
"plugins": ["@prettier/plugin-xml"]
|
||||
}
|
||||
},
|
||||
"YAML": {
|
||||
|
||||
@@ -8,10 +8,5 @@
|
||||
// from the command palette or from `Zed` application menu.
|
||||
{
|
||||
"ui_font_size": 16,
|
||||
"buffer_font_size": 16,
|
||||
"theme": {
|
||||
"mode": "system",
|
||||
"light": "One Light",
|
||||
"dark": "One Dark",
|
||||
},
|
||||
"buffer_font_size": 16
|
||||
}
|
||||
|
||||
@@ -52,13 +52,4 @@ impl Assets {
|
||||
|
||||
cx.text_system().add_fonts(embedded_fonts)
|
||||
}
|
||||
|
||||
pub fn load_test_fonts(&self, cx: &AppContext) {
|
||||
cx.text_system()
|
||||
.add_fonts(vec![self
|
||||
.load("fonts/plex-mono/ZedPlexMono-Regular.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use slash_command::{
|
||||
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
||||
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
|
||||
active_command, auto_command, default_command, diagnostics_command, fetch_command,
|
||||
file_command, now_command, project_command, prompt_command, rustdoc_command, search_command,
|
||||
tabs_command, term_command,
|
||||
};
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
@@ -310,6 +311,7 @@ pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
|
||||
|
||||
fn register_slash_commands(cx: &mut AppContext) {
|
||||
let slash_command_registry = SlashCommandRegistry::global(cx);
|
||||
slash_command_registry.register_command(auto_command::AutoCommand, true);
|
||||
slash_command_registry.register_command(file_command::FileSlashCommand, true);
|
||||
slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
|
||||
slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
|
||||
|
||||
@@ -169,7 +169,6 @@ pub enum AssistantProvider {
|
||||
model: OpenAiModel,
|
||||
api_url: String,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
available_models: Vec<OpenAiModel>,
|
||||
},
|
||||
Anthropic {
|
||||
model: AnthropicModel,
|
||||
@@ -189,7 +188,6 @@ impl Default for AssistantProvider {
|
||||
model: OpenAiModel::default(),
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +202,6 @@ pub enum AssistantProviderContent {
|
||||
default_model: Option<OpenAiModel>,
|
||||
api_url: Option<String>,
|
||||
low_speed_timeout_in_seconds: Option<u64>,
|
||||
available_models: Option<Vec<OpenAiModel>>,
|
||||
},
|
||||
#[serde(rename = "anthropic")]
|
||||
Anthropic {
|
||||
@@ -275,7 +272,6 @@ impl AssistantSettingsContent {
|
||||
default_model: settings.default_open_ai_model.clone(),
|
||||
api_url: Some(open_ai_api_url.clone()),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
})
|
||||
} else {
|
||||
settings.default_open_ai_model.clone().map(|open_ai_model| {
|
||||
@@ -283,7 +279,6 @@ impl AssistantSettingsContent {
|
||||
default_model: Some(open_ai_model),
|
||||
api_url: None,
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -331,14 +326,6 @@ impl AssistantSettingsContent {
|
||||
*model = Some(new_model);
|
||||
}
|
||||
}
|
||||
Some(AssistantProviderContent::Ollama {
|
||||
default_model: model,
|
||||
..
|
||||
}) => {
|
||||
if let LanguageModel::Ollama(new_model) = new_model {
|
||||
*model = Some(new_model);
|
||||
}
|
||||
}
|
||||
provider => match new_model {
|
||||
LanguageModel::Cloud(model) => {
|
||||
*provider = Some(AssistantProviderContent::ZedDotDev {
|
||||
@@ -350,7 +337,6 @@ impl AssistantSettingsContent {
|
||||
default_model: Some(model),
|
||||
api_url: None,
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Some(Default::default()),
|
||||
})
|
||||
}
|
||||
LanguageModel::Anthropic(model) => {
|
||||
@@ -495,18 +481,15 @@ impl Settings for AssistantSettings {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
AssistantProviderContent::OpenAi {
|
||||
default_model: model_override,
|
||||
api_url: api_url_override,
|
||||
low_speed_timeout_in_seconds: low_speed_timeout_in_seconds_override,
|
||||
available_models: available_models_override,
|
||||
},
|
||||
) => {
|
||||
merge(model, model_override);
|
||||
merge(api_url, api_url_override);
|
||||
merge(available_models, available_models_override);
|
||||
if let Some(low_speed_timeout_in_seconds_override) =
|
||||
low_speed_timeout_in_seconds_override
|
||||
{
|
||||
@@ -567,12 +550,10 @@ impl Settings for AssistantSettings {
|
||||
default_model: model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
} => AssistantProvider::OpenAi {
|
||||
model: model.unwrap_or_default(),
|
||||
api_url: api_url.unwrap_or_else(|| open_ai::OPEN_AI_API_URL.into()),
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models: available_models.unwrap_or_default(),
|
||||
},
|
||||
AssistantProviderContent::Anthropic {
|
||||
default_model: model,
|
||||
@@ -629,7 +610,6 @@ mod tests {
|
||||
model: OpenAiModel::FourOmni,
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -652,7 +632,6 @@ mod tests {
|
||||
model: OpenAiModel::FourOmni,
|
||||
api_url: "test-url".into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
SettingsStore::update_global(cx, |store, cx| {
|
||||
@@ -673,7 +652,6 @@ mod tests {
|
||||
model: OpenAiModel::Four,
|
||||
api_url: open_ai::OPEN_AI_API_URL.into(),
|
||||
low_speed_timeout_in_seconds: None,
|
||||
available_models: Default::default(),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -24,20 +24,6 @@ use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// 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.
|
||||
fn choose_openai_model(
|
||||
model: &::open_ai::Model,
|
||||
available_models: &[::open_ai::Model],
|
||||
) -> ::open_ai::Model {
|
||||
available_models
|
||||
.iter()
|
||||
.find(|&m| m == model)
|
||||
.or_else(|| available_models.first())
|
||||
.unwrap_or_else(|| model)
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
let mut settings_version = 0;
|
||||
let provider = match &AssistantSettings::get_global(cx).provider {
|
||||
@@ -48,9 +34,8 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
} => CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
@@ -92,11 +77,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
) => {
|
||||
provider.update(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
settings_version,
|
||||
@@ -152,11 +136,10 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
model,
|
||||
api_url,
|
||||
low_speed_timeout_in_seconds,
|
||||
available_models,
|
||||
},
|
||||
) => {
|
||||
*provider = CompletionProvider::OpenAi(OpenAiCompletionProvider::new(
|
||||
choose_openai_model(model, available_models),
|
||||
model.clone(),
|
||||
api_url.clone(),
|
||||
client.http_client(),
|
||||
low_speed_timeout_in_seconds.map(Duration::from_secs),
|
||||
@@ -218,10 +201,10 @@ impl CompletionProvider {
|
||||
cx.global::<Self>()
|
||||
}
|
||||
|
||||
pub fn available_models(&self, cx: &AppContext) -> Vec<LanguageModel> {
|
||||
pub fn available_models(&self) -> Vec<LanguageModel> {
|
||||
match self {
|
||||
CompletionProvider::OpenAi(provider) => provider
|
||||
.available_models(cx)
|
||||
.available_models()
|
||||
.map(LanguageModel::OpenAi)
|
||||
.collect(),
|
||||
CompletionProvider::Anthropic(provider) => provider
|
||||
|
||||
@@ -236,7 +236,7 @@ pub fn preprocess_anthropic_request(request: &mut LanguageModelRequest) {
|
||||
}
|
||||
|
||||
if !system_message.is_empty() {
|
||||
new_messages.insert(
|
||||
request.messages.insert(
|
||||
0,
|
||||
LanguageModelRequestMessage {
|
||||
role: Role::System,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::assistant_settings::CloudModel;
|
||||
use crate::assistant_settings::{AssistantProvider, AssistantSettings};
|
||||
use crate::{
|
||||
assistant_settings::OpenAiModel, CompletionProvider, LanguageModel, LanguageModelRequest, Role,
|
||||
};
|
||||
@@ -57,26 +56,8 @@ impl OpenAiCompletionProvider {
|
||||
self.settings_version = settings_version;
|
||||
}
|
||||
|
||||
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() {
|
||||
// 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()
|
||||
pub fn available_models(&self) -> impl Iterator<Item = OpenAiModel> {
|
||||
OpenAiModel::iter()
|
||||
}
|
||||
|
||||
pub fn settings_version(&self) -> usize {
|
||||
@@ -232,8 +213,7 @@ pub fn count_open_ai_tokens(
|
||||
| LanguageModel::Cloud(CloudModel::Claude3_5Sonnet)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Opus)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Sonnet)
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku)
|
||||
| LanguageModel::OpenAi(OpenAiModel::Custom { .. }) => {
|
||||
| LanguageModel::Cloud(CloudModel::Claude3Haiku) => {
|
||||
// Tiktoken doesn't yet support these models, so we manually use the
|
||||
// same tokenizer as GPT-4.
|
||||
tiktoken_rs::num_tokens_from_messages("gpt-4", &messages)
|
||||
|
||||
@@ -1298,8 +1298,7 @@ impl Render for PromptEditor {
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
for model in CompletionProvider::global(cx).available_models(cx)
|
||||
{
|
||||
for model in CompletionProvider::global(cx).available_models() {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let model = model.clone();
|
||||
|
||||
@@ -23,7 +23,7 @@ impl RenderOnce for ModelSelector {
|
||||
.with_handle(self.handle)
|
||||
.menu(move |cx| {
|
||||
ContextMenu::build(cx, |mut menu, cx| {
|
||||
for model in CompletionProvider::global(cx).available_models(cx) {
|
||||
for model in CompletionProvider::global(cx).available_models() {
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let model = model.clone();
|
||||
|
||||
@@ -5,17 +5,17 @@ use crate::{
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{HashMap, HashSet};
|
||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle};
|
||||
use collections::HashMap;
|
||||
use editor::{actions::Tab, CurrentLineHighlight, Editor, EditorEvent};
|
||||
use futures::{
|
||||
future::{self, BoxFuture, Shared},
|
||||
FutureExt,
|
||||
};
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{
|
||||
actions, point, size, transparent_black, AppContext, BackgroundExecutor, Bounds, EventEmitter,
|
||||
Global, HighlightStyle, PromptLevel, ReadGlobal, Subscription, Task, TextStyle,
|
||||
TitlebarOptions, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||
actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
|
||||
Bounds, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
|
||||
Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
|
||||
};
|
||||
use heed::{types::SerdeBincode, Database, RoTxn};
|
||||
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
|
||||
@@ -34,7 +34,7 @@ use std::{
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
|
||||
SharedString, Styled, Tooltip, ViewContext, VisualContext,
|
||||
SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@@ -42,12 +42,7 @@ use workspace::Workspace;
|
||||
|
||||
actions!(
|
||||
prompt_library,
|
||||
[
|
||||
NewPrompt,
|
||||
DeletePrompt,
|
||||
DuplicatePrompt,
|
||||
ToggleDefaultPrompt
|
||||
]
|
||||
[NewPrompt, DeletePrompt, ToggleDefaultPrompt]
|
||||
);
|
||||
|
||||
/// Init starts loading the PromptStore in the background and assigns
|
||||
@@ -114,13 +109,12 @@ pub struct PromptLibrary {
|
||||
}
|
||||
|
||||
struct PromptEditor {
|
||||
title_editor: View<Editor>,
|
||||
body_editor: View<Editor>,
|
||||
editor: View<Editor>,
|
||||
token_count: Option<usize>,
|
||||
pending_token_count: Task<Option<()>>,
|
||||
next_title_and_body_to_save: Option<(String, Rope)>,
|
||||
next_body_to_save: Option<Rope>,
|
||||
pending_save: Option<Task<Option<()>>>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
struct PromptPickerDelegate {
|
||||
@@ -351,8 +345,7 @@ impl PromptLibrary {
|
||||
|
||||
let prompt_metadata = self.store.metadata(prompt_id).unwrap();
|
||||
let prompt_editor = self.prompt_editors.get_mut(&prompt_id).unwrap();
|
||||
let title = prompt_editor.title_editor.read(cx).text(cx);
|
||||
let body = prompt_editor.body_editor.update(cx, |editor, cx| {
|
||||
let body = prompt_editor.editor.update(cx, |editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
@@ -366,24 +359,20 @@ impl PromptLibrary {
|
||||
let store = self.store.clone();
|
||||
let executor = cx.background_executor().clone();
|
||||
|
||||
prompt_editor.next_title_and_body_to_save = Some((title, body));
|
||||
prompt_editor.next_body_to_save = Some(body);
|
||||
if prompt_editor.pending_save.is_none() {
|
||||
prompt_editor.pending_save = Some(cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
loop {
|
||||
let title_and_body = this.update(&mut cx, |this, _| {
|
||||
let next_body_to_save = this.update(&mut cx, |this, _| {
|
||||
this.prompt_editors
|
||||
.get_mut(&prompt_id)?
|
||||
.next_title_and_body_to_save
|
||||
.next_body_to_save
|
||||
.take()
|
||||
})?;
|
||||
|
||||
if let Some((title, body)) = title_and_body {
|
||||
let title = if title.trim().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(SharedString::from(title))
|
||||
};
|
||||
if let Some(body) = next_body_to_save {
|
||||
let title = title_from_body(body.chars_at(0));
|
||||
store
|
||||
.save(prompt_id, title, prompt_metadata.default, body)
|
||||
.await
|
||||
@@ -416,12 +405,6 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.duplicate_prompt(active_prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_default_for_active_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt_id) = self.active_prompt_id {
|
||||
self.toggle_default_for_prompt(active_prompt_id, cx);
|
||||
@@ -442,11 +425,11 @@ impl PromptLibrary {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
if focus {
|
||||
prompt_editor
|
||||
.body_editor
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.focus(cx));
|
||||
}
|
||||
self.set_active_prompt(Some(prompt_id), cx);
|
||||
} else if let Some(prompt_metadata) = self.store.metadata(prompt_id) {
|
||||
} else {
|
||||
let language_registry = self.language_registry.clone();
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
let prompt = self.store.load(prompt_id);
|
||||
@@ -455,20 +438,13 @@ impl PromptLibrary {
|
||||
let markdown = language_registry.language_for_name("Markdown").await;
|
||||
this.update(&mut cx, |this, cx| match prompt {
|
||||
Ok(prompt) => {
|
||||
let title_editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_width(cx);
|
||||
editor.set_placeholder_text("Untitled", cx);
|
||||
editor.set_text(prompt_metadata.title.unwrap_or_default(), cx);
|
||||
editor
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(prompt, cx);
|
||||
buffer.set_language(markdown.log_err(), cx);
|
||||
buffer.set_language_registry(language_registry);
|
||||
buffer
|
||||
});
|
||||
let body_editor = cx.new_view(|cx| {
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(prompt, cx);
|
||||
buffer.set_language(markdown.log_err(), cx);
|
||||
buffer.set_language_registry(language_registry);
|
||||
buffer
|
||||
});
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
@@ -484,24 +460,19 @@ impl PromptLibrary {
|
||||
}
|
||||
editor
|
||||
});
|
||||
let _subscriptions = vec![
|
||||
cx.subscribe(&title_editor, move |this, editor, event, cx| {
|
||||
this.handle_prompt_title_editor_event(prompt_id, editor, event, cx)
|
||||
}),
|
||||
cx.subscribe(&body_editor, move |this, editor, event, cx| {
|
||||
this.handle_prompt_body_editor_event(prompt_id, editor, event, cx)
|
||||
}),
|
||||
];
|
||||
let _subscription =
|
||||
cx.subscribe(&editor, move |this, _editor, event, cx| {
|
||||
this.handle_prompt_editor_event(prompt_id, event, cx)
|
||||
});
|
||||
this.prompt_editors.insert(
|
||||
prompt_id,
|
||||
PromptEditor {
|
||||
title_editor,
|
||||
body_editor,
|
||||
next_title_and_body_to_save: None,
|
||||
editor,
|
||||
next_body_to_save: None,
|
||||
pending_save: None,
|
||||
token_count: None,
|
||||
pending_token_count: Task::ready(None),
|
||||
_subscriptions,
|
||||
_subscription,
|
||||
},
|
||||
);
|
||||
this.set_active_prompt(Some(prompt_id), cx);
|
||||
@@ -575,51 +546,10 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duplicate_prompt(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt) = self.prompt_editors.get(&prompt_id) {
|
||||
const DUPLICATE_SUFFIX: &str = " copy";
|
||||
let title_to_duplicate = prompt.title_editor.read(cx).text(cx);
|
||||
let existing_titles = self
|
||||
.prompt_editors
|
||||
.iter()
|
||||
.filter(|&(&id, _)| id != prompt_id)
|
||||
.map(|(_, prompt_editor)| prompt_editor.title_editor.read(cx).text(cx))
|
||||
.filter(|title| title.starts_with(&title_to_duplicate))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let title = if existing_titles.is_empty() {
|
||||
title_to_duplicate + DUPLICATE_SUFFIX
|
||||
} else {
|
||||
let mut i = 1;
|
||||
loop {
|
||||
let new_title = format!("{title_to_duplicate}{DUPLICATE_SUFFIX} {i}");
|
||||
if !existing_titles.contains(&new_title) {
|
||||
break new_title;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
};
|
||||
|
||||
let new_id = PromptId::new();
|
||||
let body = prompt.body_editor.read(cx).text(cx);
|
||||
let save = self
|
||||
.store
|
||||
.save(new_id, Some(title.into()), false, body.into());
|
||||
self.picker.update(cx, |picker, cx| picker.refresh(cx));
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
save.await?;
|
||||
this.update(&mut cx, |prompt_library, cx| {
|
||||
prompt_library.load_prompt(new_id, true, cx)
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_active_prompt(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
|
||||
if let Some(active_prompt) = self.active_prompt_id {
|
||||
self.prompt_editors[&active_prompt]
|
||||
.body_editor
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.focus(cx));
|
||||
cx.stop_propagation();
|
||||
}
|
||||
@@ -635,7 +565,7 @@ impl PromptLibrary {
|
||||
return;
|
||||
};
|
||||
|
||||
let prompt_editor = &self.prompt_editors[&active_prompt_id].body_editor;
|
||||
let prompt_editor = &self.prompt_editors[&active_prompt_id].editor;
|
||||
let provider = CompletionProvider::global(cx);
|
||||
if provider.is_authenticated() {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
@@ -659,73 +589,50 @@ impl PromptLibrary {
|
||||
}
|
||||
}
|
||||
|
||||
fn move_down_from_title(&mut self, _: &editor::actions::MoveDown, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_id) = self.active_prompt_id {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
cx.focus_view(&prompt_editor.body_editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_up_from_body(&mut self, _: &editor::actions::MoveUp, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt_id) = self.active_prompt_id {
|
||||
if let Some(prompt_editor) = self.prompt_editors.get(&prompt_id) {
|
||||
cx.focus_view(&prompt_editor.title_editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_prompt_title_editor_event(
|
||||
fn handle_prompt_editor_event(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
title_editor: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
self.save_prompt(prompt_id, cx);
|
||||
self.count_tokens(prompt_id, cx);
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
title_editor.update(cx, |title_editor, cx| {
|
||||
title_editor.change_selections(None, cx, |selections| {
|
||||
let cursor = selections.oldest_anchor().head();
|
||||
selections.select_anchor_ranges([cursor..cursor]);
|
||||
});
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let EditorEvent::BufferEdited = event {
|
||||
let prompt_editor = self.prompt_editors.get(&prompt_id).unwrap();
|
||||
let buffer = prompt_editor
|
||||
.editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap();
|
||||
|
||||
fn handle_prompt_body_editor_event(
|
||||
&mut self,
|
||||
prompt_id: PromptId,
|
||||
body_editor: View<Editor>,
|
||||
event: &EditorEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
match event {
|
||||
EditorEvent::BufferEdited => {
|
||||
self.save_prompt(prompt_id, cx);
|
||||
self.count_tokens(prompt_id, cx);
|
||||
}
|
||||
EditorEvent::Blurred => {
|
||||
body_editor.update(cx, |body_editor, cx| {
|
||||
body_editor.change_selections(None, cx, |selections| {
|
||||
let cursor = selections.oldest_anchor().head();
|
||||
selections.select_anchor_ranges([cursor..cursor]);
|
||||
});
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let mut chars = buffer.chars_at(0);
|
||||
match chars.next() {
|
||||
Some('#') => {
|
||||
if chars.next() != Some(' ') {
|
||||
drop(chars);
|
||||
buffer.edit([(1..1, " ")], None, cx);
|
||||
}
|
||||
}
|
||||
Some(' ') => {
|
||||
drop(chars);
|
||||
buffer.edit([(0..0, "#")], None, cx);
|
||||
}
|
||||
_ => {
|
||||
drop(chars);
|
||||
buffer.edit([(0..0, "# ")], None, cx);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.save_prompt(prompt_id, cx);
|
||||
self.count_tokens(prompt_id, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn count_tokens(&mut self, prompt_id: PromptId, cx: &mut ViewContext<Self>) {
|
||||
if let Some(prompt) = self.prompt_editors.get_mut(&prompt_id) {
|
||||
let editor = &prompt.body_editor.read(cx);
|
||||
let editor = &prompt.editor.read(cx);
|
||||
let buffer = &editor.buffer().read(cx).as_singleton().unwrap().read(cx);
|
||||
let body = buffer.as_rope().clone();
|
||||
prompt.pending_token_count = cx.spawn(|this, mut cx| {
|
||||
@@ -773,7 +680,7 @@ impl PromptLibrary {
|
||||
.child(
|
||||
h_flex()
|
||||
.p(Spacing::Small.rems(cx))
|
||||
.h_9()
|
||||
.h(TitleBar::height(cx))
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.justify_end()
|
||||
@@ -801,218 +708,122 @@ impl PromptLibrary {
|
||||
.flex_none()
|
||||
.min_w_64()
|
||||
.children(self.active_prompt_id.and_then(|prompt_id| {
|
||||
let buffer_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
|
||||
let prompt_metadata = self.store.metadata(prompt_id)?;
|
||||
let prompt_editor = &self.prompt_editors[&prompt_id];
|
||||
let focus_handle = prompt_editor.body_editor.focus_handle(cx);
|
||||
let focus_handle = prompt_editor.editor.focus_handle(cx);
|
||||
let current_model = CompletionProvider::global(cx).model();
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let token_count = prompt_editor.token_count.map(|count| count.to_string());
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
h_flex()
|
||||
.id("prompt-editor-inner")
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.pl(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::Large.rems(cx))
|
||||
.items_start()
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.focus(&focus_handle);
|
||||
}))
|
||||
.child(
|
||||
h_flex()
|
||||
.group("active-editor-header")
|
||||
.pr(Spacing::XXLarge.rems(cx))
|
||||
.pt(Spacing::XSmall.rems(cx))
|
||||
.pb(Spacing::Large.rems(cx))
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex().gap_1().child(
|
||||
div()
|
||||
.max_w_80()
|
||||
.on_action(cx.listener(Self::move_down_from_title))
|
||||
.border_1()
|
||||
.border_color(transparent_black())
|
||||
.rounded_md()
|
||||
.group_hover("active-editor-header", |this| {
|
||||
this.border_color(
|
||||
cx.theme().colors().border_variant,
|
||||
)
|
||||
})
|
||||
.child(EditorElement::new(
|
||||
&prompt_editor.title_editor,
|
||||
EditorStyle {
|
||||
background: cx.theme().system().transparent,
|
||||
local_player: cx.theme().players().local(),
|
||||
text: TextStyle {
|
||||
color: cx
|
||||
.theme()
|
||||
.colors()
|
||||
.editor_foreground,
|
||||
font_family: settings
|
||||
.ui_font
|
||||
.family
|
||||
.clone(),
|
||||
font_features: settings
|
||||
.ui_font
|
||||
.features
|
||||
.clone(),
|
||||
font_size: HeadlineSize::Large
|
||||
.size()
|
||||
.into(),
|
||||
font_weight: settings.ui_font.weight,
|
||||
line_height: relative(
|
||||
settings.buffer_line_height.value(),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
scrollbar_width: Pixels::ZERO,
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
status: cx.theme().status().clone(),
|
||||
inlay_hints_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().hint),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
suggestions_style: HighlightStyle {
|
||||
color: Some(cx.theme().status().predictive),
|
||||
..HighlightStyle::default()
|
||||
},
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.child(div()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.gap(Spacing::XXLarge.rems(cx))
|
||||
.children(prompt_editor.token_count.map(
|
||||
|token_count| {
|
||||
let token_count: SharedString =
|
||||
token_count.to_string().into();
|
||||
let label_token_count: SharedString =
|
||||
token_count.to_string().into();
|
||||
|
||||
h_flex()
|
||||
.id("token_count")
|
||||
.tooltip(move |cx| {
|
||||
let token_count =
|
||||
token_count.clone();
|
||||
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"{} tokens",
|
||||
token_count.clone()
|
||||
),
|
||||
None,
|
||||
format!(
|
||||
"Model: {}",
|
||||
current_model
|
||||
.display_name()
|
||||
),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.child(
|
||||
Label::new(format!(
|
||||
"{} tokens",
|
||||
label_token_count.clone()
|
||||
))
|
||||
.color(Color::Muted),
|
||||
)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"delete-prompt",
|
||||
IconName::Trash,
|
||||
)
|
||||
.size(ButtonSize::Large)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Delete Prompt",
|
||||
&DeletePrompt,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(DeletePrompt));
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"duplicate-prompt",
|
||||
IconName::BookCopy,
|
||||
)
|
||||
.size(ButtonSize::Large)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Duplicate Prompt",
|
||||
&DuplicatePrompt,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
DuplicatePrompt,
|
||||
));
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new(
|
||||
"toggle-default-prompt",
|
||||
IconName::Sparkle,
|
||||
)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.selected(prompt_metadata.default)
|
||||
.selected_icon(IconName::SparkleFilled)
|
||||
.icon_color(if prompt_metadata.default {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.shape(IconButtonShape::Square)
|
||||
.size(ButtonSize::Large)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if prompt_metadata.default {
|
||||
"Remove from Default Prompt"
|
||||
} else {
|
||||
"Add to Default Prompt"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
ToggleDefaultPrompt,
|
||||
));
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.on_action(cx.listener(Self::focus_picker))
|
||||
.on_action(cx.listener(Self::inline_assist))
|
||||
.on_action(cx.listener(Self::move_up_from_body))
|
||||
.flex_grow()
|
||||
.h_full()
|
||||
.child(prompt_editor.body_editor.clone()),
|
||||
.pt(Spacing::XXLarge.rems(cx))
|
||||
.pl(Spacing::XXLarge.rems(cx))
|
||||
.child(prompt_editor.editor.clone()),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_12()
|
||||
.py(Spacing::Large.rems(cx))
|
||||
.justify_start()
|
||||
.items_end()
|
||||
.gap_1()
|
||||
.child(h_flex().h_8().font_family(buffer_font).when_some_else(
|
||||
token_count,
|
||||
|tokens_ready, token_count| {
|
||||
tokens_ready.pr_3().justify_end().child(
|
||||
// This isn't actually a button, it just let's us easily add
|
||||
// a tooltip to the token count.
|
||||
Button::new("token_count", token_count.clone())
|
||||
.style(ButtonStyle::Transparent)
|
||||
.color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!("{} tokens", token_count,),
|
||||
None,
|
||||
format!(
|
||||
"Model: {}",
|
||||
current_model.display_name()
|
||||
),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
},
|
||||
|tokens_loading| {
|
||||
tokens_loading.w_12().justify_center().child(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(4)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(
|
||||
percentage(delta),
|
||||
))
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
))
|
||||
.child(
|
||||
h_flex().justify_center().w_12().h_8().child(
|
||||
IconButton::new("toggle-default-prompt", IconName::Sparkle)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.selected(prompt_metadata.default)
|
||||
.selected_icon(IconName::SparkleFilled)
|
||||
.icon_color(if prompt_metadata.default {
|
||||
Color::Accent
|
||||
} else {
|
||||
Color::Muted
|
||||
})
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if prompt_metadata.default {
|
||||
"Remove from Default Prompt"
|
||||
} else {
|
||||
"Add to Default Prompt"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(ToggleDefaultPrompt));
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().justify_center().w_12().h_8().child(
|
||||
IconButton::new("delete-prompt", IconName::Trash)
|
||||
.size(ButtonSize::Large)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Delete Prompt",
|
||||
&DeletePrompt,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(DeletePrompt));
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}))
|
||||
@@ -1029,7 +840,6 @@ impl Render for PromptLibrary {
|
||||
.key_context("PromptLibrary")
|
||||
.on_action(cx.listener(|this, &NewPrompt, cx| this.new_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &DeletePrompt, cx| this.delete_active_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &DuplicatePrompt, cx| this.duplicate_active_prompt(cx)))
|
||||
.on_action(cx.listener(|this, &ToggleDefaultPrompt, cx| {
|
||||
this.toggle_default_for_active_prompt(cx)
|
||||
}))
|
||||
@@ -1305,3 +1115,24 @@ pub struct GlobalPromptStore(
|
||||
);
|
||||
|
||||
impl Global for GlobalPromptStore {}
|
||||
|
||||
fn title_from_body(body: impl IntoIterator<Item = char>) -> Option<SharedString> {
|
||||
let mut chars = body.into_iter().take_while(|c| *c != '\n').peekable();
|
||||
|
||||
let mut level = 0;
|
||||
while let Some('#') = chars.peek() {
|
||||
level += 1;
|
||||
chars.next();
|
||||
}
|
||||
|
||||
if level > 0 {
|
||||
let title = chars.collect::<String>().trim().to_string();
|
||||
if title.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(title.into())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,106 +6,118 @@ pub fn generate_content_prompt(
|
||||
language_name: Option<&str>,
|
||||
buffer: BufferSnapshot,
|
||||
range: Range<usize>,
|
||||
_project_name: Option<String>,
|
||||
project_name: Option<String>,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut prompt = String::new();
|
||||
|
||||
let content_type = match language_name {
|
||||
None | Some("Markdown" | "Plain Text") => {
|
||||
writeln!(
|
||||
prompt,
|
||||
"Here's a file of text that I'm going to ask you to make an edit to."
|
||||
)?;
|
||||
"text"
|
||||
writeln!(prompt, "You are an expert engineer.")?;
|
||||
"Text"
|
||||
}
|
||||
Some(language_name) => {
|
||||
writeln!(prompt, "You are an expert {language_name} engineer.")?;
|
||||
writeln!(
|
||||
prompt,
|
||||
"Here's a file of {language_name} that I'm going to ask you to make an edit to."
|
||||
"Your answer MUST always and only be valid {}.",
|
||||
language_name
|
||||
)?;
|
||||
"code"
|
||||
"Code"
|
||||
}
|
||||
};
|
||||
|
||||
const MAX_CTX: usize = 50000;
|
||||
let mut is_truncated = false;
|
||||
if let Some(project_name) = project_name {
|
||||
writeln!(
|
||||
prompt,
|
||||
"You are currently working inside the '{project_name}' project in code editor Zed."
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"The user has the following file open in the editor:"
|
||||
)?;
|
||||
if range.is_empty() {
|
||||
prompt.push_str("The point you'll need to insert at is marked with <insert_here></insert_here>.\n\n<document>");
|
||||
write!(prompt, "```")?;
|
||||
if let Some(language_name) = language_name {
|
||||
write!(prompt, "{language_name}")?;
|
||||
}
|
||||
|
||||
for chunk in buffer.as_rope().chunks_in_range(0..range.start) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
prompt.push_str("<|CURSOR|>");
|
||||
for chunk in buffer.as_rope().chunks_in_range(range.start..buffer.len()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"Assume the cursor is located where the `<|CURSOR|>` span is."
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"{content_type} can't be replaced, so assume your answer will be inserted at the cursor.",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Generate {content_type} based on the users prompt: {user_prompt}",
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
prompt.push_str("The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n\n<document>");
|
||||
}
|
||||
// Include file content.
|
||||
let before_range = 0..range.start;
|
||||
let truncated_before = if before_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
range.start - MAX_CTX..range.start
|
||||
} else {
|
||||
before_range
|
||||
};
|
||||
let mut non_rewrite_len = truncated_before.len();
|
||||
for chunk in buffer.text_for_range(truncated_before) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !range.is_empty() {
|
||||
prompt.push_str("<rewrite_this>\n");
|
||||
write!(prompt, "```")?;
|
||||
for chunk in buffer.as_rope().chunks() {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"In particular, the following piece of text is selected:"
|
||||
)?;
|
||||
write!(prompt, "```")?;
|
||||
if let Some(language_name) = language_name {
|
||||
write!(prompt, "{language_name}")?;
|
||||
}
|
||||
prompt.push('\n');
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
prompt.push_str("\n<rewrite_this>");
|
||||
} else {
|
||||
prompt.push_str("<insert_here></insert_here>");
|
||||
}
|
||||
let after_range = range.end..buffer.len();
|
||||
let truncated_after = if after_range.len() > MAX_CTX {
|
||||
is_truncated = true;
|
||||
range.end..range.end + MAX_CTX
|
||||
} else {
|
||||
after_range
|
||||
};
|
||||
non_rewrite_len += truncated_after.len();
|
||||
for chunk in buffer.text_for_range(truncated_after) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
|
||||
write!(prompt, "</document>\n\n").unwrap();
|
||||
|
||||
if is_truncated {
|
||||
writeln!(prompt, "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n")?;
|
||||
}
|
||||
|
||||
if range.is_empty() {
|
||||
writeln!(
|
||||
prompt,
|
||||
"You can't replace {content_type}, your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Generate {content_type} based on the following prompt:\n\n<prompt>\n{user_prompt}\n</prompt>",
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(prompt, "Match the indentation in the original file in the inserted {content_type}, don't include any indentation on blank lines.\n").unwrap();
|
||||
prompt.push_str("Immediately start with the following format with no remarks:\n\n```\n{{INSERTED_CODE}}\n```");
|
||||
} else {
|
||||
writeln!(prompt, "Edit the section of {content_type} in <rewrite_this></rewrite_this> tags based on the following prompt:'").unwrap();
|
||||
writeln!(prompt, "\n<prompt>\n{user_prompt}\n</prompt>\n").unwrap();
|
||||
let rewrite_len = range.end - range.start;
|
||||
if rewrite_len < 20000 && rewrite_len * 2 < non_rewrite_len {
|
||||
writeln!(prompt, "And here's the section to rewrite based on that prompt again for reference:\n\n<rewrite_this>\n").unwrap();
|
||||
for chunk in buffer.text_for_range(range.clone()) {
|
||||
prompt.push_str(chunk);
|
||||
}
|
||||
writeln!(prompt, "\n</rewrite_this>\n").unwrap();
|
||||
if !prompt.ends_with('\n') {
|
||||
prompt.push('\n');
|
||||
}
|
||||
writeln!(prompt, "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {content_type} will be preserved.\n").unwrap();
|
||||
write!(
|
||||
writeln!(prompt, "```")?;
|
||||
prompt.push('\n');
|
||||
|
||||
writeln!(
|
||||
prompt,
|
||||
"Start at the indentation level in the original file in the rewritten {content_type}. "
|
||||
"Modify the user's selected {content_type} based upon the users prompt: {user_prompt}"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"You must reply with only the adjusted {content_type}, not the entire file."
|
||||
)
|
||||
.unwrap();
|
||||
prompt.push_str("Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.");
|
||||
prompt.push_str("\n\nImmediately start with the following format with no remarks:\n\n```\n{{REWRITTEN_CODE}}\n```");
|
||||
}
|
||||
|
||||
writeln!(prompt, "Never make remarks about the output.").unwrap();
|
||||
writeln!(
|
||||
prompt,
|
||||
"Do not return anything else, except the generated {content_type}."
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(prompt)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use ui::ActiveTheme;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub mod active_command;
|
||||
pub mod auto_command;
|
||||
pub mod default_command;
|
||||
pub mod diagnostics_command;
|
||||
pub mod fetch_command;
|
||||
|
||||
193
crates/assistant/src/slash_command/auto_command.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use super::create_label_for_command;
|
||||
use super::{SlashCommand, SlashCommandOutput};
|
||||
use crate::{CompletionProvider, LanguageModelRequest, LanguageModelRequestMessage, Role};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AppContext, Task, WeakView};
|
||||
use language::{CodeLabel, LspAdapterDelegate};
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
use ui::WindowContext;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct AutoCommand;
|
||||
|
||||
impl SlashCommand for AutoCommand {
|
||||
fn name(&self) -> String {
|
||||
"auto".into()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Automatically infer what context to add, based on your prompt".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
"Automatically Infer Context".into()
|
||||
}
|
||||
|
||||
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||
create_label_for_command("auto", &["--prompt"], cx)
|
||||
}
|
||||
|
||||
fn complete_argument(
|
||||
self: Arc<Self>,
|
||||
_query: String,
|
||||
_cancellation_flag: Arc<AtomicBool>,
|
||||
_workspace: Option<WeakView<Workspace>>,
|
||||
_cx: &mut AppContext,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
// There's no autocomplete for a prompt, since it's arbitrary text.
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
self: Arc<Self>,
|
||||
argument: Option<&str>,
|
||||
_workspace: WeakView<Workspace>,
|
||||
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<SlashCommandOutput>> {
|
||||
let Some(argument) = argument else {
|
||||
return Task::ready(Err(anyhow!("missing prompt")));
|
||||
};
|
||||
|
||||
let prompt = format!("{PROMPT_INSTRUCTIONS_BEFORE_SUMMARY}\n{SUMMARY}\n{PROMPT_INSTRUCTIONS_AFTER_SUMMARY}\n{argument}");
|
||||
let request = LanguageModelRequest {
|
||||
model: CompletionProvider::global(cx).model(),
|
||||
messages: vec![LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: prompt,
|
||||
}],
|
||||
stop: vec![],
|
||||
temperature: 1.0,
|
||||
};
|
||||
|
||||
let stream = CompletionProvider::global(cx).complete(request);
|
||||
let mut wip_action: String = String::new();
|
||||
let task: Task<Result<String>> = cx.spawn(|_cx| async move {
|
||||
let mut actions_text = String::new();
|
||||
let stream_completion = async {
|
||||
let mut messages = stream.await?;
|
||||
|
||||
while let Some(message) = messages.next().await {
|
||||
let text = message?;
|
||||
|
||||
chunked_line(&mut wip_action, &text, |line| {
|
||||
actions_text.push('/');
|
||||
actions_text.push_str(line);
|
||||
actions_text.push('\n');
|
||||
});
|
||||
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
stream_completion.await?;
|
||||
|
||||
Ok(actions_text)
|
||||
});
|
||||
|
||||
// As a convenience, append /auto's argument to the end of the prompt so you don't have to write it again.
|
||||
let argument = argument.to_string();
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut text = task.await?;
|
||||
|
||||
text.push_str(&argument);
|
||||
|
||||
Ok(SlashCommandOutput {
|
||||
text,
|
||||
sections: Vec::new(),
|
||||
run_commands_in_text: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
const PROMPT_INSTRUCTIONS_BEFORE_SUMMARY: &str = r#"
|
||||
I'm going to give you a prompt. I don't want you to respond
|
||||
to the prompt itself. I want you to figure out which of the following
|
||||
actions on my project, if any, would help you answer the prompt.
|
||||
|
||||
Here are the actions:
|
||||
|
||||
## file
|
||||
|
||||
This action's parameter is a file path to one of the files
|
||||
in the project. If you ask for this action, I will tell you
|
||||
the full contents of the file, so you can learn all the
|
||||
details of the file.
|
||||
|
||||
## search
|
||||
|
||||
This action's parameter is a string to search for across
|
||||
the project. It will tell you which files this string
|
||||
(or similar strings; it is a semantic search) appear in,
|
||||
as well as some context of the lines surrounding each result.
|
||||
|
||||
---
|
||||
|
||||
That was the end of the list of actions.
|
||||
|
||||
Here is an XML summary of each of the files in my project:
|
||||
"#;
|
||||
|
||||
const PROMPT_INSTRUCTIONS_AFTER_SUMMARY: &str = r#"
|
||||
Actions have a cost, so only include actions that you think
|
||||
will be helpful to you in doing a great job answering the
|
||||
prompt in the future.
|
||||
|
||||
You must respond ONLY with a list of actions you would like to
|
||||
perform. Each action should be on its own line, and followed by a space and then its parameter.
|
||||
|
||||
Actions can be performed more than once with different parameters.
|
||||
Here is an example valid response:
|
||||
|
||||
```
|
||||
file path/to/my/file.txt
|
||||
file path/to/another/file.txt
|
||||
search something to search for
|
||||
search something else to search for
|
||||
```
|
||||
|
||||
Once again, do not forget: you must respond ONLY in the format of
|
||||
one action per line, and the action name should be followed by
|
||||
its parameter. Your response must not include anything other
|
||||
than a list of actions, with one action per line, in this format.
|
||||
It is extremely important that you do not deviate from this format even slightly!
|
||||
|
||||
This is the end of my instructions for how to respond. The rest is the prompt:
|
||||
"#;
|
||||
|
||||
const SUMMARY: &str = "";
|
||||
|
||||
fn chunked_line(wip: &mut String, chunk: &str, mut on_line_end: impl FnMut(&str)) {
|
||||
// The first iteration of the loop should just push to wip
|
||||
// and nothing else. We only push what we encountered in
|
||||
// previous iterations of the loop.
|
||||
//
|
||||
// This correctly handles both the scenario where no
|
||||
// newlines are encountered (the loop will only run once,
|
||||
// and so will only push to wip), as well as the scenario
|
||||
// where the chunk contains at least one newline but
|
||||
// does not end in a newline (the last iteration of the
|
||||
// loop will update wip but will not run anything).
|
||||
let mut is_first_iteration = true;
|
||||
|
||||
for line in chunk.split('\n') {
|
||||
if is_first_iteration {
|
||||
is_first_iteration = false;
|
||||
} else {
|
||||
// Since this isn't the first iteration of the loop, we definitely hit a newline
|
||||
// at the end of the previous iteration! Run the function on whatever wip we have.
|
||||
on_line_end(wip);
|
||||
wip.clear();
|
||||
}
|
||||
|
||||
wip.push_str(line);
|
||||
}
|
||||
}
|
||||
@@ -196,24 +196,23 @@ mod linux {
|
||||
impl Detect {
|
||||
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
let path = if let Some(path) = path {
|
||||
path.to_path_buf().canonicalize()?
|
||||
path.to_path_buf().canonicalize()
|
||||
} else {
|
||||
let cli = env::current_exe()?;
|
||||
let dir = cli
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.ok_or_else(|| anyhow!("no parent path for cli"))?;
|
||||
|
||||
// libexec is the standard, lib/zed is for Arch (and other non-libexec distros),
|
||||
// ./zed is for the target directory in development builds.
|
||||
let possible_locations =
|
||||
["../libexec/zed-editor", "../lib/zed/zed-editor", "./zed"];
|
||||
possible_locations
|
||||
.iter()
|
||||
.find_map(|p| dir.join(p).canonicalize().ok().filter(|path| path != &cli))
|
||||
.ok_or_else(|| {
|
||||
anyhow!("could not find any of: {}", possible_locations.join(", "))
|
||||
})?
|
||||
};
|
||||
match dir.join("libexec").join("zed-editor").canonicalize() {
|
||||
Ok(path) => Ok(path),
|
||||
// In development cli and zed are in the ./target/ directory together
|
||||
Err(e) => match cli.parent().unwrap().join("zed").canonicalize() {
|
||||
Ok(path) if path != cli => Ok(path),
|
||||
_ => Err(e),
|
||||
},
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(App(path))
|
||||
}
|
||||
|
||||
@@ -611,7 +611,6 @@ impl Telemetry {
|
||||
|
||||
let request_body = EventRequestBody {
|
||||
installation_id: state.installation_id.as_deref().map(Into::into),
|
||||
metrics_id: state.metrics_id.as_deref().map(Into::into),
|
||||
session_id: state.session_id.clone(),
|
||||
is_staff: state.is_staff,
|
||||
app_version: state.app_version.clone(),
|
||||
|
||||
@@ -87,27 +87,51 @@ impl Global {
|
||||
}
|
||||
|
||||
pub fn observed_any(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| *right > 0 && left >= right)
|
||||
let mut lhs = self.0.iter();
|
||||
let mut rhs = other.0.iter();
|
||||
loop {
|
||||
if let Some(left) = lhs.next() {
|
||||
if let Some(right) = rhs.next() {
|
||||
if *right > 0 && left >= right {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observed_all(&self, other: &Self) -> bool {
|
||||
let mut lhs = self.0.iter();
|
||||
let mut rhs = other.0.iter();
|
||||
self.0.iter().all(|left| match rhs.next() {
|
||||
Some(right) => left >= right,
|
||||
None => true,
|
||||
}) && rhs.next().is_none()
|
||||
loop {
|
||||
if let Some(left) = lhs.next() {
|
||||
if let Some(right) = rhs.next() {
|
||||
if left < right {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return rhs.next().is_none();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn changed_since(&self, other: &Self) -> bool {
|
||||
self.0.len() > other.0.len()
|
||||
|| self
|
||||
.0
|
||||
.iter()
|
||||
.zip(other.0.iter())
|
||||
.any(|(left, right)| left > right)
|
||||
if self.0.len() > other.0.len() {
|
||||
return true;
|
||||
}
|
||||
for (left, right) in self.0.iter().zip(other.0.iter()) {
|
||||
if left > right {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
|
||||
|
||||
@@ -664,7 +664,6 @@ where
|
||||
#[derive(Serialize, Debug, clickhouse::Row)]
|
||||
pub struct EditorEventRow {
|
||||
installation_id: String,
|
||||
metrics_id: String,
|
||||
operation: String,
|
||||
app_version: String,
|
||||
file_extension: String,
|
||||
@@ -714,7 +713,6 @@ impl EditorEventRow {
|
||||
os_version: body.os_version.clone().unwrap_or_default(),
|
||||
architecture: body.architecture.clone(),
|
||||
installation_id: body.installation_id.clone().unwrap_or_default(),
|
||||
metrics_id: body.metrics_id.clone().unwrap_or_default(),
|
||||
session_id: body.session_id.clone(),
|
||||
is_staff: body.is_staff,
|
||||
time: time.timestamp_millis(),
|
||||
|
||||
@@ -30,13 +30,17 @@ test-support = [
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
call.workspace = true
|
||||
channel.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
emojis.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feedback.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
@@ -47,6 +51,8 @@ notifications.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
release_channel.workspace = true
|
||||
rich_text.workspace = true
|
||||
rpc.workspace = true
|
||||
@@ -58,13 +64,14 @@ settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
theme_selector.workspace = true
|
||||
time_format.workspace = true
|
||||
time.workspace = true
|
||||
title_bar.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vcs_menu.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -2,7 +2,10 @@ mod channel_modal;
|
||||
mod contact_finder;
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
use crate::{channel_view::ChannelView, chat_panel::ChatPanel, CollaborationPanelSettings};
|
||||
use crate::{
|
||||
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
|
||||
CollaborationPanelSettings,
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
|
||||
@@ -31,8 +34,7 @@ use std::{mem, sync::Arc};
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, Avatar, AvatarAvailabilityIndicator, Button, Color, ContextMenu,
|
||||
Facepile, Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem,
|
||||
Tooltip,
|
||||
Icon, IconButton, IconName, IconSize, Indicator, Label, ListHeader, ListItem, Tooltip,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use workspace::{
|
||||
@@ -2540,7 +2542,7 @@ impl CollabPanel {
|
||||
None
|
||||
} else {
|
||||
let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
|
||||
let result = Facepile::new(
|
||||
let result = FacePile::new(
|
||||
participants
|
||||
.iter()
|
||||
.map(|user| Avatar::new(user.avatar_uri.clone()).into_any_element())
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
mod call_controls;
|
||||
mod collab;
|
||||
mod platforms;
|
||||
|
||||
use crate::platforms::{platform_linux, platform_mac, platform_windows};
|
||||
use crate::face_pile::FacePile;
|
||||
use auto_update::AutoUpdateStatus;
|
||||
use call::{ActiveCall, ParticipantLocation};
|
||||
use client::{Client, UserStore};
|
||||
use collab::render_color_ribbon;
|
||||
use call::{ActiveCall, ParticipantLocation, Room};
|
||||
use client::{proto::PeerId, Client, User, UserStore};
|
||||
use gpui::{
|
||||
actions, div, px, Action, AnyElement, AppContext, Element, InteractiveElement, Interactivity,
|
||||
IntoElement, Model, ParentElement, Render, Stateful, StatefulInteractiveElement, Styled,
|
||||
Subscription, ViewContext, VisualContext, WeakView,
|
||||
actions, canvas, div, point, px, Action, AnyElement, AppContext, Element, Hsla,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Path, Render,
|
||||
StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, WeakView,
|
||||
};
|
||||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::RecentProjects;
|
||||
use rpc::proto::DevServerStatus;
|
||||
use rpc::proto::{self, DevServerStatus};
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
use theme::{ActiveTheme, ThemeSettings};
|
||||
use ui::{
|
||||
h_flex, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, IconButton,
|
||||
IconName, Indicator, PopoverMenu, TintColor, Tooltip,
|
||||
h_flex, prelude::*, Avatar, AvatarAudioStatusIndicator, Button, ButtonLike, ButtonStyle,
|
||||
ContextMenu, Icon, IconButton, IconName, Indicator, PopoverMenu, TintColor, TitleBar, Tooltip,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu};
|
||||
@@ -43,16 +37,13 @@ actions!(
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, cx| {
|
||||
let item = cx.new_view(|cx| TitleBar::new("title-bar", workspace, cx));
|
||||
workspace.set_titlebar_item(item.into(), cx)
|
||||
let titlebar_item = cx.new_view(|cx| CollabTitlebarItem::new(workspace, cx));
|
||||
workspace.set_titlebar_item(titlebar_item.into(), cx)
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct TitleBar {
|
||||
platform_style: PlatformStyle,
|
||||
content: Stateful<Div>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
pub struct CollabTitlebarItem {
|
||||
project: Model<Project>,
|
||||
user_store: Model<UserStore>,
|
||||
client: Arc<Client>,
|
||||
@@ -60,348 +51,319 @@ pub struct TitleBar {
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl Render for TitleBar {
|
||||
impl Render for CollabTitlebarItem {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let room = ActiveCall::global(cx).read(cx).room().cloned();
|
||||
let current_user = self.user_store.read(cx).current_user();
|
||||
let client = self.client.clone();
|
||||
let project_id = self.project.read(cx).remote_id();
|
||||
let workspace = self.workspace.upgrade();
|
||||
let close_action = Box::new(workspace::CloseWindow);
|
||||
|
||||
let platform_supported = cfg!(target_os = "macos");
|
||||
let height = Self::height(cx);
|
||||
|
||||
h_flex()
|
||||
.id("titlebar")
|
||||
.w_full()
|
||||
.pt(Self::top_padding(cx))
|
||||
.h(height + Self::top_padding(cx))
|
||||
.map(|this| {
|
||||
if cx.is_fullscreen() {
|
||||
this.pl_2()
|
||||
} else if self.platform_style == PlatformStyle::Mac {
|
||||
this.pl(px(platform_mac::TRAFFIC_LIGHT_PADDING))
|
||||
} else {
|
||||
this.pl_2()
|
||||
}
|
||||
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
|
||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||
.when(cfg!(not(windows)), |this| {
|
||||
this.on_click(|event, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
cx.zoom_window();
|
||||
}
|
||||
})
|
||||
})
|
||||
.bg(cx.theme().colors().title_bar_background)
|
||||
.content_stretch()
|
||||
// left side
|
||||
.child(
|
||||
div()
|
||||
.id("titlebar-content")
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.render_application_menu(cx))
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.children(self.render_project_branch(cx))
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("collaborator-list")
|
||||
.w_full()
|
||||
// note: on windows titlebar behaviour is handled by the platform implementation
|
||||
.when(cfg!(not(windows)), |this| {
|
||||
this.on_click(|event, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
cx.zoom_window();
|
||||
}
|
||||
})
|
||||
})
|
||||
// left side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.children(self.render_application_menu(cx))
|
||||
.children(self.render_project_host(cx))
|
||||
.child(self.render_project_name(cx))
|
||||
.children(self.render_project_branch(cx))
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.id("collaborator-list")
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.overflow_x_scroll()
|
||||
.when_some(
|
||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||
|this, ((current_user, peer_id), room)| {
|
||||
let player_colors = cx.theme().players();
|
||||
let room = room.read(cx);
|
||||
let mut remote_participants =
|
||||
room.remote_participants().values().collect::<Vec<_>>();
|
||||
remote_participants.sort_by_key(|p| p.participant_index.0);
|
||||
.gap_1()
|
||||
.overflow_x_scroll()
|
||||
.when_some(
|
||||
current_user.clone().zip(client.peer_id()).zip(room.clone()),
|
||||
|this, ((current_user, peer_id), room)| {
|
||||
let player_colors = cx.theme().players();
|
||||
let room = room.read(cx);
|
||||
let mut remote_participants =
|
||||
room.remote_participants().values().collect::<Vec<_>>();
|
||||
remote_participants.sort_by_key(|p| p.participant_index.0);
|
||||
|
||||
let current_user_face_pile = self.render_collaborator(
|
||||
¤t_user,
|
||||
peer_id,
|
||||
true,
|
||||
room.is_speaking(),
|
||||
room.is_muted(),
|
||||
None,
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
);
|
||||
let current_user_face_pile = self.render_collaborator(
|
||||
¤t_user,
|
||||
peer_id,
|
||||
true,
|
||||
room.is_speaking(),
|
||||
room.is_muted(),
|
||||
None,
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
);
|
||||
|
||||
this.children(current_user_face_pile.map(|face_pile| {
|
||||
v_flex()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_colors.local().cursor))
|
||||
}))
|
||||
.children(
|
||||
remote_participants.iter().filter_map(|collaborator| {
|
||||
let player_color = player_colors
|
||||
.color_for_participant(collaborator.participant_index.0);
|
||||
let is_following = workspace
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.is_being_followed(collaborator.peer_id);
|
||||
let is_present = project_id.map_or(false, |project_id| {
|
||||
collaborator.location
|
||||
== ParticipantLocation::SharedProject { project_id }
|
||||
});
|
||||
this.children(current_user_face_pile.map(|face_pile| {
|
||||
v_flex()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_colors.local().cursor))
|
||||
}))
|
||||
.children(
|
||||
remote_participants.iter().filter_map(|collaborator| {
|
||||
let player_color = player_colors
|
||||
.color_for_participant(collaborator.participant_index.0);
|
||||
let is_following = workspace
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.is_being_followed(collaborator.peer_id);
|
||||
let is_present = project_id.map_or(false, |project_id| {
|
||||
collaborator.location
|
||||
== ParticipantLocation::SharedProject { project_id }
|
||||
});
|
||||
|
||||
let facepile = self.render_collaborator(
|
||||
&collaborator.user,
|
||||
collaborator.peer_id,
|
||||
is_present,
|
||||
collaborator.speaking,
|
||||
collaborator.muted,
|
||||
is_following.then_some(player_color.selection),
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
)?;
|
||||
let face_pile = self.render_collaborator(
|
||||
&collaborator.user,
|
||||
collaborator.peer_id,
|
||||
is_present,
|
||||
collaborator.speaking,
|
||||
collaborator.muted,
|
||||
is_following.then_some(player_color.selection),
|
||||
&room,
|
||||
project_id,
|
||||
¤t_user,
|
||||
cx,
|
||||
)?;
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.id(("collaborator", collaborator.user.id))
|
||||
.child(facepile)
|
||||
.child(render_color_ribbon(player_color.cursor))
|
||||
.cursor_pointer()
|
||||
.on_click({
|
||||
let peer_id = collaborator.peer_id;
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.follow(peer_id, cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
Some(
|
||||
v_flex()
|
||||
.id(("collaborator", collaborator.user.id))
|
||||
.child(face_pile)
|
||||
.child(render_color_ribbon(player_color.cursor))
|
||||
.cursor_pointer()
|
||||
.on_click({
|
||||
let peer_id = collaborator.peer_id;
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace.follow(peer_id, cx);
|
||||
})
|
||||
.tooltip({
|
||||
let login = collaborator.user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(format!("Follow {login}"), cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
// right side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.when_some(room, |this, room| {
|
||||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local();
|
||||
let is_dev_server_project = project.dev_server_project_id().is_some();
|
||||
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(
|
||||
(is_local || is_dev_server_project) && can_share_projects,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.ok();
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
IconButton::new("leave-call", ui::IconName::Exit)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.pr_2(),
|
||||
)
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
ui::IconName::MicMute
|
||||
} else {
|
||||
ui::IconName::Mic
|
||||
},
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share microphone"
|
||||
} else if is_muted {
|
||||
"Unmute microphone"
|
||||
} else {
|
||||
"Mute microphone"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(platform_supported && is_muted)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_mute(&Default::default(), cx);
|
||||
.tooltip({
|
||||
let login = collaborator.user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(format!("Follow {login}"), cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
IconButton::new(
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
ui::IconName::AudioOff
|
||||
} else {
|
||||
ui::IconName::AudioOn
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_deafened)
|
||||
.disabled(!platform_supported)
|
||||
.tooltip(move |cx| {
|
||||
if !platform_supported {
|
||||
Tooltip::text("Cannot share microphone", cx)
|
||||
} else if can_use_microphone {
|
||||
Tooltip::with_meta(
|
||||
"Deafen Audio",
|
||||
None,
|
||||
"Mic will be muted",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::text("Deafen Audio", cx)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_deafen(&Default::default(), cx)
|
||||
}),
|
||||
)
|
||||
.when(can_share_projects, |this| {
|
||||
this.child(
|
||||
IconButton::new("screen-share", ui::IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_screen_sharing)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share screen"
|
||||
} else if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
"Share Screen"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
call_controls::toggle_screen_sharing(&Default::default(), cx)
|
||||
}),
|
||||
}),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
// right side
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.pr_1()
|
||||
.on_mouse_move(|_, cx| cx.stop_propagation())
|
||||
.when_some(room, |this, room| {
|
||||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local();
|
||||
let is_dev_server_project = project.dev_server_project_id().is_some();
|
||||
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(
|
||||
(is_local || is_dev_server_project) && can_share_projects,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.child(div().pr_2())
|
||||
})
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.child(self.render_sign_in_button(cx))
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
}),
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
},
|
||||
)
|
||||
)
|
||||
.when(
|
||||
self.platform_style == PlatformStyle::Windows && !cx.is_fullscreen(),
|
||||
|title_bar| title_bar.child(platform_windows::WindowsWindowControls::new(height)),
|
||||
)
|
||||
.when(
|
||||
self.platform_style == PlatformStyle::Linux
|
||||
&& !cx.is_fullscreen()
|
||||
&& cx.should_render_window_controls(),
|
||||
|title_bar| {
|
||||
title_bar
|
||||
.child(platform_linux::LinuxWindowControls::new(height, close_action))
|
||||
.on_mouse_down(gpui::MouseButton::Right, move |ev, cx| {
|
||||
cx.show_window_menu(ev.position)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
IconButton::new("leave-call", ui::IconName::Exit)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(|cx| Tooltip::text("Leave call", cx))
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click(move |_, cx| {
|
||||
ActiveCall::global(cx)
|
||||
.update(cx, |call, cx| call.hang_up(cx))
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.pr_2(),
|
||||
)
|
||||
.when(can_use_microphone, |this| {
|
||||
this.child(
|
||||
IconButton::new(
|
||||
"mute-microphone",
|
||||
if is_muted {
|
||||
ui::IconName::MicMute
|
||||
} else {
|
||||
ui::IconName::Mic
|
||||
},
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share microphone"
|
||||
} else if is_muted {
|
||||
"Unmute microphone"
|
||||
} else {
|
||||
"Mute microphone"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(platform_supported && is_muted)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
|
||||
)
|
||||
})
|
||||
.on_mouse_move(move |ev, cx| {
|
||||
if ev.dragging() {
|
||||
cx.start_system_move();
|
||||
}
|
||||
.child(
|
||||
IconButton::new(
|
||||
"mute-sound",
|
||||
if is_deafened {
|
||||
ui::IconName::AudioOff
|
||||
} else {
|
||||
ui::IconName::AudioOn
|
||||
},
|
||||
)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_deafened)
|
||||
.disabled(!platform_supported)
|
||||
.tooltip(move |cx| {
|
||||
if !platform_supported {
|
||||
Tooltip::text("Cannot share microphone", cx)
|
||||
} else if can_use_microphone {
|
||||
Tooltip::with_meta(
|
||||
"Deafen Audio",
|
||||
None,
|
||||
"Mic will be muted",
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::text("Deafen Audio", cx)
|
||||
}
|
||||
})
|
||||
.on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
|
||||
)
|
||||
.when(can_share_projects, |this| {
|
||||
this.child(
|
||||
IconButton::new("screen-share", ui::IconName::Screen)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon_size(IconSize::Small)
|
||||
.selected(is_screen_sharing)
|
||||
.disabled(!platform_supported)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if !platform_supported {
|
||||
"Cannot share screen"
|
||||
} else if is_screen_sharing {
|
||||
"Stop Sharing Screen"
|
||||
} else {
|
||||
"Share Screen"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(move |_, cx| {
|
||||
crate::toggle_screen_sharing(&Default::default(), cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
.child(div().pr_2())
|
||||
})
|
||||
.map(|el| {
|
||||
let status = self.client.status();
|
||||
let status = &*status.borrow();
|
||||
if matches!(status, client::Status::Connected { .. }) {
|
||||
el.child(self.render_user_menu_button(cx))
|
||||
} else {
|
||||
el.children(self.render_connection_status(status, cx))
|
||||
.child(self.render_sign_in_button(cx))
|
||||
.child(self.render_user_menu_button(cx))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TitleBar {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
workspace: &Workspace,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
fn render_color_ribbon(color: Hsla) -> impl Element {
|
||||
canvas(
|
||||
move |_, _| {},
|
||||
move |bounds, _, cx| {
|
||||
let height = bounds.size.height;
|
||||
let horizontal_offset = height;
|
||||
let vertical_offset = px(height.0 / 2.0);
|
||||
let mut path = Path::new(bounds.lower_left());
|
||||
path.curve_to(
|
||||
bounds.origin + point(horizontal_offset, vertical_offset),
|
||||
bounds.origin + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
|
||||
path.curve_to(
|
||||
bounds.lower_right(),
|
||||
bounds.upper_right() + point(px(0.0), vertical_offset),
|
||||
);
|
||||
path.line_to(bounds.lower_left());
|
||||
cx.paint_path(path, color);
|
||||
},
|
||||
)
|
||||
.h_1()
|
||||
.w_full()
|
||||
}
|
||||
|
||||
impl CollabTitlebarItem {
|
||||
pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
|
||||
let project = workspace.project().clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
@@ -418,9 +380,6 @@ impl TitleBar {
|
||||
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
|
||||
|
||||
Self {
|
||||
platform_style: PlatformStyle::platform(),
|
||||
content: div().id(id.into()),
|
||||
children: SmallVec::new(),
|
||||
workspace: workspace.weak_handle(),
|
||||
project,
|
||||
user_store,
|
||||
@@ -429,45 +388,6 @@ impl TitleBar {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn height(cx: &mut WindowContext) -> Pixels {
|
||||
(1.75 * cx.rem_size()).max(px(34.))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn height(_cx: &mut WindowContext) -> Pixels {
|
||||
// todo(windows) instead of hard coded size report the actual size to the Windows platform API
|
||||
px(32.)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn top_padding(_cx: &WindowContext) -> Pixels {
|
||||
px(0.)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn top_padding(cx: &WindowContext) -> Pixels {
|
||||
use windows::Win32::UI::{
|
||||
HiDpi::GetSystemMetricsForDpi,
|
||||
WindowsAndMessaging::{SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI},
|
||||
};
|
||||
|
||||
// This top padding is not dependent on the title bar style and is instead a quirk of maximized windows on Windows:
|
||||
// https://devblogs.microsoft.com/oldnewthing/20150304-00/?p=44543
|
||||
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
|
||||
if cx.is_maximized() {
|
||||
px((padding * 2) as f32)
|
||||
} else {
|
||||
px(0.)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the platform style.
|
||||
pub fn platform_style(mut self, style: PlatformStyle) -> Self {
|
||||
self.platform_style = style;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render_application_menu(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
cfg!(not(target_os = "macos")).then(|| {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
@@ -792,6 +712,97 @@ impl TitleBar {
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_collaborator(
|
||||
&self,
|
||||
user: &Arc<User>,
|
||||
peer_id: PeerId,
|
||||
is_present: bool,
|
||||
is_speaking: bool,
|
||||
is_muted: bool,
|
||||
leader_selection_color: Option<Hsla>,
|
||||
room: &Room,
|
||||
project_id: Option<u64>,
|
||||
current_user: &Arc<User>,
|
||||
cx: &ViewContext<Self>,
|
||||
) -> Option<Div> {
|
||||
if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
|
||||
return None;
|
||||
}
|
||||
|
||||
const FACEPILE_LIMIT: usize = 3;
|
||||
let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
|
||||
let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT);
|
||||
|
||||
Some(
|
||||
div()
|
||||
.m_0p5()
|
||||
.p_0p5()
|
||||
// When the collaborator is not followed, still draw this wrapper div, but leave
|
||||
// it transparent, so that it does not shift the layout when following.
|
||||
.when_some(leader_selection_color, |div, color| {
|
||||
div.rounded_md().bg(color)
|
||||
})
|
||||
.child(
|
||||
FacePile::empty()
|
||||
.child(
|
||||
Avatar::new(user.avatar_uri.clone())
|
||||
.grayscale(!is_present)
|
||||
.border_color(if is_speaking {
|
||||
cx.theme().status().info
|
||||
} else {
|
||||
// We draw the border in a transparent color rather to avoid
|
||||
// the layout shift that would come with adding/removing the border.
|
||||
gpui::transparent_black()
|
||||
})
|
||||
.when(is_muted, |avatar| {
|
||||
avatar.indicator(
|
||||
AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted)
|
||||
.tooltip({
|
||||
let github_login = user.github_login.clone();
|
||||
move |cx| {
|
||||
Tooltip::text(
|
||||
format!("{} is muted", github_login),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(followers.iter().take(FACEPILE_LIMIT).filter_map(
|
||||
|follower_peer_id| {
|
||||
let follower = room
|
||||
.remote_participants()
|
||||
.values()
|
||||
.find_map(|p| {
|
||||
(p.peer_id == *follower_peer_id).then_some(&p.user)
|
||||
})
|
||||
.or_else(|| {
|
||||
(self.client.peer_id() == Some(*follower_peer_id))
|
||||
.then_some(current_user)
|
||||
})?
|
||||
.clone();
|
||||
|
||||
Some(div().mt(-px(4.)).child(
|
||||
Avatar::new(follower.avatar_uri.clone()).size(rems(0.75)),
|
||||
))
|
||||
},
|
||||
))
|
||||
.children(if extra_count > 0 {
|
||||
Some(
|
||||
div()
|
||||
.ml_1()
|
||||
.child(Label::new(format!("+{extra_count}")))
|
||||
.into_any_element(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if cx.is_window_active() {
|
||||
ActiveCall::global(cx)
|
||||
@@ -949,17 +960,3 @@ impl TitleBar {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for TitleBar {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
self.content.interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl StatefulInteractiveElement for TitleBar {}
|
||||
|
||||
impl ParentElement for TitleBar {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
pub mod channel_view;
|
||||
pub mod chat_panel;
|
||||
pub mod collab_panel;
|
||||
mod collab_titlebar_item;
|
||||
mod face_pile;
|
||||
pub mod notification_panel;
|
||||
pub mod notifications;
|
||||
mod panel_settings;
|
||||
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use call::{report_call_event_for_room, ActiveCall};
|
||||
pub use collab_panel::CollabPanel;
|
||||
pub use collab_titlebar_item::CollabTitlebarItem;
|
||||
use gpui::{
|
||||
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowKind, WindowOptions,
|
||||
actions, point, AppContext, Pixels, PlatformDisplay, Size, Task, WindowBackgroundAppearance,
|
||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
pub use panel_settings::{
|
||||
@@ -19,7 +23,12 @@ pub use panel_settings::{
|
||||
use release_channel::ReleaseChannel;
|
||||
use settings::Settings;
|
||||
use ui::px;
|
||||
use workspace::AppState;
|
||||
use workspace::{notifications::DetachAndPromptErr, AppState};
|
||||
|
||||
actions!(
|
||||
collab,
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
@@ -27,13 +36,63 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
NotificationPanelSettings::register(cx);
|
||||
MessageEditorSettings::register(cx);
|
||||
|
||||
vcs_menu::init(cx);
|
||||
collab_titlebar_item::init(cx);
|
||||
collab_panel::init(cx);
|
||||
channel_view::init(cx);
|
||||
chat_panel::init(cx);
|
||||
collab_panel::init(cx);
|
||||
notification_panel::init(cx);
|
||||
notifications::init(&app_state, cx);
|
||||
title_bar::init(cx);
|
||||
vcs_menu::init(cx);
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut WindowContext) {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
report_call_event_for_room(
|
||||
"disable screen share",
|
||||
room.id(),
|
||||
room.channel_id(),
|
||||
&client,
|
||||
);
|
||||
Task::ready(room.unshare_screen(cx))
|
||||
} else {
|
||||
report_call_event_for_room(
|
||||
"enable screen share",
|
||||
room.id(),
|
||||
room.channel_id(),
|
||||
&client,
|
||||
);
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", cx, |e, _| Some(format!("{:?}\n\nPlease check that you have given Zed permissions to record your screen in Settings.", e)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
room.update(cx, |room, cx| {
|
||||
let operation = if room.is_muted() {
|
||||
"enable microphone"
|
||||
} else {
|
||||
"disable microphone"
|
||||
};
|
||||
report_call_event_for_room(operation, room.id(), room.channel_id(), &client);
|
||||
|
||||
room.toggle_mute(cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
room.update(cx, |room, cx| room.toggle_deafen(cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn notification_window_options(
|
||||
@@ -65,6 +124,6 @@ fn notification_window_options(
|
||||
display_id: Some(screen.id()),
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
app_id: Some(app_id.to_owned()),
|
||||
window_min_size: None,
|
||||
window_min_size: Size::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
use crate::prelude::*;
|
||||
use gpui::AnyElement;
|
||||
use smallvec::SmallVec;
|
||||
use ui::prelude::*;
|
||||
|
||||
/// A facepile is a collection of faces stacked horizontally–
|
||||
/// always with the leftmost face on top and descending in z-index
|
||||
///
|
||||
/// Facepiles are used to display a group of people or things,
|
||||
/// such as a list of participants in a collaboration session.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Facepile {
|
||||
pub struct FacePile {
|
||||
base: Div,
|
||||
faces: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl Facepile {
|
||||
impl FacePile {
|
||||
pub fn empty() -> Self {
|
||||
Self::new(SmallVec::new())
|
||||
}
|
||||
@@ -23,7 +18,7 @@ impl Facepile {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Facepile {
|
||||
impl RenderOnce for FacePile {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
// Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
|
||||
self.base
|
||||
@@ -41,13 +36,13 @@ impl RenderOnce for Facepile {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for Facepile {
|
||||
impl ParentElement for FacePile {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.faces.extend(elements);
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for Facepile {
|
||||
impl Styled for FacePile {
|
||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
@@ -102,9 +102,6 @@ impl Render for ProjectDiagnosticsEditor {
|
||||
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.when(self.path_states.is_empty(), |el| {
|
||||
el.key_context("EmptyPane")
|
||||
})
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::toggle_warnings))
|
||||
.child(child)
|
||||
|
||||
@@ -720,7 +720,8 @@ impl DisplaySnapshot {
|
||||
if let Some(severity) = chunk.diagnostic_severity {
|
||||
// Omit underlines for HINT/INFO diagnostics on 'unnecessary' code.
|
||||
if severity <= DiagnosticSeverity::WARNING || !chunk.is_unnecessary {
|
||||
let diagnostic_color = super::diagnostic_style(severity, &editor_style.status);
|
||||
let diagnostic_color =
|
||||
super::diagnostic_style(severity, true, &editor_style.status);
|
||||
diagnostic_highlight.underline = Some(UnderlineStyle {
|
||||
color: Some(diagnostic_color),
|
||||
thickness: 1.0.into(),
|
||||
@@ -956,18 +957,16 @@ impl DisplaySnapshot {
|
||||
return false;
|
||||
}
|
||||
|
||||
(buffer_row.0 + 1..=max_row.0)
|
||||
.find_map(|next_row| {
|
||||
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
|
||||
if next_line_indent.raw_len() > line_indent.raw_len() {
|
||||
Some(true)
|
||||
} else if !next_line_indent.is_line_blank() {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
for next_row in (buffer_row.0 + 1)..=max_row.0 {
|
||||
let next_line_indent = self.line_indent_for_buffer_row(MultiBufferRow(next_row));
|
||||
if next_line_indent.raw_len() > line_indent.raw_len() {
|
||||
return true;
|
||||
} else if !next_line_indent.is_line_blank() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn foldable_range(
|
||||
|
||||
@@ -1221,7 +1221,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::display_map::inlay_map::InlayMap;
|
||||
use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
|
||||
use gpui::{div, font, px, Element};
|
||||
use gpui::{div, font, px, AssetSource, Element};
|
||||
use multi_buffer::MultiBuffer;
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
@@ -2014,7 +2014,12 @@ mod tests {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
impl TransformBlock {
|
||||
|
||||
@@ -335,7 +335,7 @@ pub enum SelectMode {
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum EditorMode {
|
||||
SingleLine { auto_width: bool },
|
||||
SingleLine,
|
||||
AutoHeight { max_lines: usize },
|
||||
Full,
|
||||
}
|
||||
@@ -1580,13 +1580,7 @@ impl Editor {
|
||||
pub fn single_line(cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::local("", cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
Self::new(
|
||||
EditorMode::SingleLine { auto_width: false },
|
||||
buffer,
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
Self::new(EditorMode::SingleLine, buffer, None, false, cx)
|
||||
}
|
||||
|
||||
pub fn multi_line(cx: &mut ViewContext<Self>) -> Self {
|
||||
@@ -1595,18 +1589,6 @@ impl Editor {
|
||||
Self::new(EditorMode::Full, buffer, None, false, cx)
|
||||
}
|
||||
|
||||
pub fn auto_width(cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::local("", cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
Self::new(
|
||||
EditorMode::SingleLine { auto_width: true },
|
||||
buffer,
|
||||
None,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
|
||||
let buffer = cx.new_model(|cx| Buffer::local("", cx));
|
||||
let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
@@ -1719,8 +1701,8 @@ impl Editor {
|
||||
|
||||
let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
|
||||
|
||||
let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
|
||||
.then(|| language_settings::SoftWrap::PreferLine);
|
||||
let soft_wrap_mode_override =
|
||||
(mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::PreferLine);
|
||||
|
||||
let mut project_subscriptions = Vec::new();
|
||||
if mode == EditorMode::Full {
|
||||
@@ -1767,7 +1749,7 @@ impl Editor {
|
||||
.detach();
|
||||
cx.on_blur(&focus_handle, Self::handle_blur).detach();
|
||||
|
||||
let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
|
||||
let show_indent_guides = if mode == EditorMode::SingleLine {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
@@ -1923,7 +1905,7 @@ impl Editor {
|
||||
let mut key_context = KeyContext::new_with_defaults();
|
||||
key_context.add("Editor");
|
||||
let mode = match self.mode {
|
||||
EditorMode::SingleLine { .. } => "single_line",
|
||||
EditorMode::SingleLine => "single_line",
|
||||
EditorMode::AutoHeight { .. } => "auto_height",
|
||||
EditorMode::Full => "full",
|
||||
};
|
||||
@@ -2131,7 +2113,7 @@ impl Editor {
|
||||
self.refresh_inline_completion(false, cx);
|
||||
}
|
||||
|
||||
pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> {
|
||||
pub fn placeholder_text(&self, _cx: &mut WindowContext) -> Option<&str> {
|
||||
self.placeholder_text.as_deref()
|
||||
}
|
||||
|
||||
@@ -2914,9 +2896,6 @@ impl Editor {
|
||||
let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
|
||||
let end_offset = start_offset + end_difference;
|
||||
let start_offset = start_offset + start_difference;
|
||||
if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
|
||||
continue;
|
||||
}
|
||||
let start = buffer_snapshot.anchor_after(start_offset);
|
||||
let end = buffer_snapshot.anchor_after(end_offset);
|
||||
linked_edits
|
||||
@@ -6681,7 +6660,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -6718,7 +6697,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -6749,7 +6728,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -6812,17 +6791,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.context_menu
|
||||
.write()
|
||||
.as_mut()
|
||||
.map(|menu| menu.select_first(self.project.as_ref(), cx))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -6870,7 +6839,7 @@ impl Editor {
|
||||
pub fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
|
||||
self.take_rename(true, cx);
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if self.mode == EditorMode::SingleLine {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -6931,7 +6900,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7279,7 +7248,7 @@ impl Editor {
|
||||
_: &MoveToStartOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7299,7 +7268,7 @@ impl Editor {
|
||||
_: &MoveToEndOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7319,7 +7288,7 @@ impl Editor {
|
||||
_: &SelectToStartOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7339,7 +7308,7 @@ impl Editor {
|
||||
_: &SelectToEndOfParagraph,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7355,7 +7324,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -7375,7 +7344,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
@@ -8234,7 +8203,7 @@ impl Editor {
|
||||
let advance_downwards = action.advance_downwards
|
||||
&& selections_on_single_row
|
||||
&& !selections_selecting
|
||||
&& !matches!(this.mode, EditorMode::SingleLine { .. });
|
||||
&& this.mode != EditorMode::SingleLine;
|
||||
|
||||
if advance_downwards {
|
||||
let snapshot = this.buffer.read(cx).snapshot(cx);
|
||||
@@ -8826,7 +8795,13 @@ impl Editor {
|
||||
let display_point = initial_point.to_display_point(snapshot);
|
||||
let mut hunks = hunks
|
||||
.map(|hunk| diff_hunk_to_display(&hunk, &snapshot))
|
||||
.filter(|hunk| is_wrapped || !hunk.contains_display_row(display_point.row()))
|
||||
.filter(|hunk| {
|
||||
if is_wrapped {
|
||||
true
|
||||
} else {
|
||||
!hunk.contains_display_row(display_point.row())
|
||||
}
|
||||
})
|
||||
.dedup();
|
||||
|
||||
if let Some(hunk) = hunks.next() {
|
||||
@@ -12104,7 +12079,7 @@ impl Render for Editor {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let text_style = match self.mode {
|
||||
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
|
||||
color: cx.theme().colors().editor_foreground,
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
@@ -12133,7 +12108,7 @@ impl Render for Editor {
|
||||
};
|
||||
|
||||
let background = match self.mode {
|
||||
EditorMode::SingleLine { .. } => cx.theme().system().transparent,
|
||||
EditorMode::SingleLine => cx.theme().system().transparent,
|
||||
EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
|
||||
EditorMode::Full => cx.theme().colors().editor_background,
|
||||
};
|
||||
@@ -12397,7 +12372,6 @@ impl ViewInputHandler for Editor {
|
||||
let font_id = cx.text_system().resolve_font(&style.text.font());
|
||||
let font_size = style.text.font_size.to_pixels(cx.rem_size());
|
||||
let line_height = style.text.line_height_in_pixels(cx.rem_size());
|
||||
|
||||
let em_width = cx
|
||||
.text_system()
|
||||
.typographic_bounds(font_id, font_size, 'm')
|
||||
@@ -12525,7 +12499,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
|
||||
let group_id: SharedString = cx.block_id.to_string().into();
|
||||
|
||||
let mut text_style = cx.text_style().clone();
|
||||
text_style.color = diagnostic_style(diagnostic.severity, cx.theme().status());
|
||||
text_style.color = diagnostic_style(diagnostic.severity, true, cx.theme().status());
|
||||
let theme_settings = ThemeSettings::get_global(cx);
|
||||
text_style.font_family = theme_settings.buffer_font.family.clone();
|
||||
text_style.font_style = theme_settings.buffer_font.style;
|
||||
@@ -12621,19 +12595,25 @@ pub fn highlight_diagnostic_message(diagnostic: &Diagnostic) -> (SharedString, V
|
||||
prev_offset = ix + 1;
|
||||
if in_code_block {
|
||||
code_ranges.push(prev_len..text_without_backticks.len());
|
||||
in_code_block = false;
|
||||
} else {
|
||||
in_code_block = true;
|
||||
}
|
||||
in_code_block = !in_code_block;
|
||||
}
|
||||
|
||||
(text_without_backticks.into(), code_ranges)
|
||||
}
|
||||
|
||||
fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
|
||||
match severity {
|
||||
DiagnosticSeverity::ERROR => colors.error,
|
||||
DiagnosticSeverity::WARNING => colors.warning,
|
||||
DiagnosticSeverity::INFORMATION => colors.info,
|
||||
DiagnosticSeverity::HINT => colors.info,
|
||||
fn diagnostic_style(severity: DiagnosticSeverity, valid: bool, colors: &StatusColors) -> Hsla {
|
||||
match (severity, valid) {
|
||||
(DiagnosticSeverity::ERROR, true) => colors.error,
|
||||
(DiagnosticSeverity::ERROR, false) => colors.error,
|
||||
(DiagnosticSeverity::WARNING, true) => colors.warning,
|
||||
(DiagnosticSeverity::WARNING, false) => colors.warning,
|
||||
(DiagnosticSeverity::INFORMATION, true) => colors.info,
|
||||
(DiagnosticSeverity::INFORMATION, false) => colors.info,
|
||||
(DiagnosticSeverity::HINT, true) => colors.info,
|
||||
(DiagnosticSeverity::HINT, false) => colors.info,
|
||||
_ => colors.ignored,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
|
||||
WindowOptions,
|
||||
div, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
@@ -12489,7 +12489,12 @@ pub(crate) fn update_test_project_settings(
|
||||
|
||||
pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
|
||||
_ = cx.update(|cx| {
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
|
||||
@@ -1119,12 +1119,11 @@ impl EditorElement {
|
||||
ScrollBeyondLastLine::Off => 1.0,
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin,
|
||||
};
|
||||
let total_rows =
|
||||
(snapshot.max_point().row().as_f32() + scroll_beyond_last_line).max(rows_per_page);
|
||||
let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line;
|
||||
let height = bounds.size.height;
|
||||
let px_per_row = height / total_rows;
|
||||
let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.);
|
||||
let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0);
|
||||
|
||||
Some(ScrollbarLayout {
|
||||
hitbox: cx.insert_hitbox(track_bounds, false),
|
||||
@@ -1832,10 +1831,10 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
fn layout_lines(
|
||||
&self,
|
||||
rows: Range<DisplayRow>,
|
||||
line_number_layouts: &[Option<ShapedLine>],
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<LineWithInvisibles> {
|
||||
if rows.start >= rows.end {
|
||||
@@ -1844,7 +1843,7 @@ impl EditorElement {
|
||||
|
||||
// Show the placeholder when the editor is empty
|
||||
if snapshot.is_empty() {
|
||||
let font_size = style.text.font_size.to_pixels(cx.rem_size());
|
||||
let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
|
||||
let placeholder_color = cx.theme().colors().text_placeholder;
|
||||
let placeholder_text = snapshot.placeholder_text();
|
||||
|
||||
@@ -1859,7 +1858,7 @@ impl EditorElement {
|
||||
.filter_map(move |line| {
|
||||
let run = TextRun {
|
||||
len: line.len(),
|
||||
font: style.text.font(),
|
||||
font: self.style.text.font(),
|
||||
color: placeholder_color,
|
||||
background_color: None,
|
||||
underline: Default::default(),
|
||||
@@ -1878,10 +1877,10 @@ impl EditorElement {
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let chunks = snapshot.highlighted_chunks(rows.clone(), true, style);
|
||||
let chunks = snapshot.highlighted_chunks(rows.clone(), true, &self.style);
|
||||
LineWithInvisibles::from_chunks(
|
||||
chunks,
|
||||
&style.text,
|
||||
&self.style.text,
|
||||
MAX_LINE_LEN,
|
||||
rows.len(),
|
||||
line_number_layouts,
|
||||
@@ -4476,7 +4475,7 @@ impl EditorElement {
|
||||
// We currently use single-line and auto-height editors in UI contexts,
|
||||
// so we don't want to scale everything with the buffer font size, as it
|
||||
// ends up looking off.
|
||||
EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => None,
|
||||
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4500,43 +4499,12 @@ impl Element for EditorElement {
|
||||
editor.set_style(self.style.clone(), cx);
|
||||
|
||||
let layout_id = match editor.mode {
|
||||
EditorMode::SingleLine { auto_width } => {
|
||||
EditorMode::SingleLine => {
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
let height = self.style.text.line_height_in_pixels(rem_size);
|
||||
if auto_width {
|
||||
let editor_handle = cx.view().clone();
|
||||
let style = self.style.clone();
|
||||
cx.request_measured_layout(Style::default(), move |_, _, cx| {
|
||||
let editor_snapshot =
|
||||
editor_handle.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
let line = Self::layout_lines(
|
||||
DisplayRow(0)..DisplayRow(1),
|
||||
&[],
|
||||
&editor_snapshot,
|
||||
&style,
|
||||
cx,
|
||||
)
|
||||
.pop()
|
||||
.unwrap();
|
||||
|
||||
let font_id = cx.text_system().resolve_font(&style.text.font());
|
||||
let font_size = style.text.font_size.to_pixels(cx.rem_size());
|
||||
let em_width = cx
|
||||
.text_system()
|
||||
.typographic_bounds(font_id, font_size, 'm')
|
||||
.unwrap()
|
||||
.size
|
||||
.width;
|
||||
|
||||
size(line.width + em_width, height)
|
||||
})
|
||||
} else {
|
||||
let mut style = Style::default();
|
||||
style.size.height = height.into();
|
||||
style.size.width = relative(1.).into();
|
||||
cx.request_layout(style, None)
|
||||
}
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = self.style.text.line_height_in_pixels(rem_size).into();
|
||||
cx.request_layout(style, None)
|
||||
}
|
||||
EditorMode::AutoHeight { max_lines } => {
|
||||
let editor_handle = cx.view().clone();
|
||||
@@ -4677,17 +4645,17 @@ impl Element for EditorElement {
|
||||
text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
|
||||
|
||||
let height_in_lines = bounds.size.height / line_height;
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
let max_scroll_top = if matches!(snapshot.mode, EditorMode::AutoHeight { .. }) {
|
||||
(max_row - height_in_lines + 1.).max(0.)
|
||||
(snapshot.max_point().row().as_f32() - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
let settings = EditorSettings::get_global(cx);
|
||||
let max_row = snapshot.max_point().row().as_f32();
|
||||
match settings.scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => max_row,
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.).max(0.),
|
||||
ScrollBeyondLastLine::Off => (max_row - height_in_lines + 1.0).max(0.0),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
(max_row - height_in_lines + 1. + settings.vertical_scroll_margin)
|
||||
.max(0.)
|
||||
(max_row - height_in_lines + 1.0 + settings.vertical_scroll_margin)
|
||||
.max(0.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4795,13 +4763,8 @@ impl Element for EditorElement {
|
||||
);
|
||||
|
||||
let mut max_visible_line_width = Pixels::ZERO;
|
||||
let mut line_layouts = Self::layout_lines(
|
||||
start_row..end_row,
|
||||
&line_numbers,
|
||||
&snapshot,
|
||||
&self.style,
|
||||
cx,
|
||||
);
|
||||
let mut line_layouts =
|
||||
self.layout_lines(start_row..end_row, &line_numbers, &snapshot, cx);
|
||||
for line_with_invisibles in &line_layouts {
|
||||
if line_with_invisibles.width > max_visible_line_width {
|
||||
max_visible_line_width = line_with_invisibles.width;
|
||||
@@ -4829,43 +4792,16 @@ impl Element for EditorElement {
|
||||
)
|
||||
});
|
||||
|
||||
let start_buffer_row =
|
||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
let end_buffer_row =
|
||||
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
|
||||
let scroll_max = point(
|
||||
((scroll_width - text_hitbox.size.width) / em_width).max(0.0),
|
||||
max_row.as_f32(),
|
||||
);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let clamped = editor.scroll_manager.clamp_scroll_left(scroll_max.x);
|
||||
|
||||
let autoscrolled = if autoscroll_horizontally {
|
||||
editor.autoscroll_horizontally(
|
||||
start_row,
|
||||
text_hitbox.size.width,
|
||||
scroll_width,
|
||||
em_width,
|
||||
&line_layouts,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if clamped || autoscrolled {
|
||||
snapshot = editor.snapshot(cx);
|
||||
scroll_position = snapshot.scroll_position();
|
||||
}
|
||||
});
|
||||
|
||||
let scroll_pixel_position = point(
|
||||
scroll_position.x * em_width,
|
||||
scroll_position.y * line_height,
|
||||
);
|
||||
|
||||
let start_buffer_row =
|
||||
MultiBufferRow(start_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
let end_buffer_row =
|
||||
MultiBufferRow(end_anchor.to_point(&snapshot.buffer_snapshot).row);
|
||||
|
||||
let indent_guides = self.layout_indent_guides(
|
||||
content_origin,
|
||||
text_hitbox.origin,
|
||||
@@ -6129,7 +6065,7 @@ mod tests {
|
||||
});
|
||||
|
||||
for editor_mode_without_invisibles in [
|
||||
EditorMode::SingleLine { auto_width: false },
|
||||
EditorMode::SingleLine,
|
||||
EditorMode::AutoHeight { max_lines: 100 },
|
||||
] {
|
||||
let invisibles = collect_invisibles_from_new_editor(
|
||||
|
||||
@@ -203,24 +203,13 @@ impl ScrollManager {
|
||||
let scroll_top = scroll_position.y;
|
||||
let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line {
|
||||
ScrollBeyondLastLine::OnePage => scroll_top,
|
||||
ScrollBeyondLastLine::Off => {
|
||||
if let Some(height_in_lines) = self.visible_line_count {
|
||||
let max_row = map.max_point().row().0 as f32;
|
||||
scroll_top.min(max_row - height_in_lines + 1.).max(0.)
|
||||
} else {
|
||||
scroll_top
|
||||
}
|
||||
}
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => {
|
||||
if let Some(height_in_lines) = self.visible_line_count {
|
||||
let max_row = map.max_point().row().0 as f32;
|
||||
scroll_top
|
||||
.min(max_row - height_in_lines + 1. + self.vertical_scroll_margin)
|
||||
.max(0.)
|
||||
} else {
|
||||
scroll_top
|
||||
}
|
||||
}
|
||||
ScrollBeyondLastLine::Off => scroll_top
|
||||
.min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0),
|
||||
ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min(
|
||||
(map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap()
|
||||
+ 1.0
|
||||
+ self.vertical_scroll_margin,
|
||||
),
|
||||
};
|
||||
|
||||
let scroll_top_buffer_point =
|
||||
@@ -466,7 +455,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
if matches!(self.mode, EditorMode::SingleLine { .. }) {
|
||||
if matches!(self.mode, EditorMode::SingleLine) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn marked_display_snapshot(
|
||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
|
||||
let font = Font {
|
||||
family: "Zed Plex Mono".into(),
|
||||
family: "Zed Mono".into(),
|
||||
features: FontFeatures::default(),
|
||||
weight: FontWeight::default(),
|
||||
style: FontStyle::default(),
|
||||
|
||||
@@ -10,7 +10,7 @@ use serde_json::json;
|
||||
use crate::{Editor, ToPoint};
|
||||
use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
use gpui::{AssetSource, View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageQueries,
|
||||
@@ -39,7 +39,12 @@ impl EditorLspTestContext {
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
cx.update(|cx| {
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![assets::Assets
|
||||
.load("fonts/zed-mono/zed-mono-extended.ttf")
|
||||
.unwrap()
|
||||
.unwrap()])
|
||||
.unwrap();
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
|
||||
@@ -80,7 +80,6 @@ backtrace = "0.3"
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
||||
http = { workspace = true, features = ["test-support"] }
|
||||
unicode-segmentation.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
embed-resource = "2.4"
|
||||
@@ -158,7 +157,3 @@ path = "examples/image/image.rs"
|
||||
[[example]]
|
||||
name = "set_menus"
|
||||
path = "examples/set_menus.rs"
|
||||
|
||||
[[example]]
|
||||
name = "input"
|
||||
path = "examples/input.rs"
|
||||
|
||||
@@ -1,489 +0,0 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::*;
|
||||
use unicode_segmentation::*;
|
||||
|
||||
actions!(
|
||||
text_input,
|
||||
[
|
||||
Backspace,
|
||||
Delete,
|
||||
Left,
|
||||
Right,
|
||||
SelectLeft,
|
||||
SelectRight,
|
||||
SelectAll,
|
||||
Home,
|
||||
End,
|
||||
ShowCharacterPalette
|
||||
]
|
||||
);
|
||||
|
||||
struct TextInput {
|
||||
focus_handle: FocusHandle,
|
||||
content: SharedString,
|
||||
selected_range: Range<usize>,
|
||||
selection_reversed: bool,
|
||||
marked_range: Option<Range<usize>>,
|
||||
last_layout: Option<ShapedLine>,
|
||||
}
|
||||
|
||||
impl TextInput {
|
||||
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.content.len(), cx)
|
||||
}
|
||||
|
||||
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
}
|
||||
|
||||
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(self.content.len(), cx);
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx)
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
}
|
||||
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn cursor_offset(&self) -> usize {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start
|
||||
} else {
|
||||
self.selected_range.end
|
||||
}
|
||||
}
|
||||
|
||||
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start = offset
|
||||
} else {
|
||||
self.selected_range.end = offset
|
||||
};
|
||||
if self.selected_range.end < self.selected_range.start {
|
||||
self.selection_reversed = !self.selection_reversed;
|
||||
self.selected_range = self.selected_range.end..self.selected_range.start;
|
||||
}
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn offset_from_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf8_offset = 0;
|
||||
let mut utf16_count = 0;
|
||||
|
||||
for ch in self.content.chars() {
|
||||
if utf16_count >= offset {
|
||||
break;
|
||||
}
|
||||
utf16_count += ch.len_utf16();
|
||||
utf8_offset += ch.len_utf8();
|
||||
}
|
||||
|
||||
utf8_offset
|
||||
}
|
||||
|
||||
fn offset_to_utf16(&self, offset: usize) -> usize {
|
||||
let mut utf16_offset = 0;
|
||||
let mut utf8_count = 0;
|
||||
|
||||
for ch in self.content.chars() {
|
||||
if utf8_count >= offset {
|
||||
break;
|
||||
}
|
||||
utf8_count += ch.len_utf8();
|
||||
utf16_offset += ch.len_utf16();
|
||||
}
|
||||
|
||||
utf16_offset
|
||||
}
|
||||
|
||||
fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
|
||||
self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
|
||||
}
|
||||
|
||||
fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
|
||||
self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
|
||||
}
|
||||
|
||||
fn previous_boundary(&self, offset: usize) -> usize {
|
||||
self.content
|
||||
.grapheme_indices(true)
|
||||
.rev()
|
||||
.find_map(|(idx, _)| (idx < offset).then_some(idx))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn next_boundary(&self, offset: usize) -> usize {
|
||||
self.content
|
||||
.grapheme_indices(true)
|
||||
.find_map(|(idx, _)| (idx > offset).then_some(idx))
|
||||
.unwrap_or(self.content.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewInputHandler for TextInput {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
Some(self.content[range].to_string())
|
||||
}
|
||||
|
||||
fn selected_text_range(&mut self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
Some(self.range_to_utf16(&self.selected_range))
|
||||
}
|
||||
|
||||
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.range_to_utf16(range))
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let range = range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.or(self.marked_range.clone())
|
||||
.unwrap_or(self.selected_range.clone());
|
||||
|
||||
self.content =
|
||||
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
|
||||
.into();
|
||||
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
|
||||
self.marked_range.take();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let range = range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.or(self.marked_range.clone())
|
||||
.unwrap_or(self.selected_range.clone());
|
||||
|
||||
self.content =
|
||||
(self.content[0..range.start].to_owned() + new_text + &self.content[range.end..])
|
||||
.into();
|
||||
self.marked_range = Some(range.start..range.start + new_text.len());
|
||||
self.selected_range = new_selected_range_utf16
|
||||
.as_ref()
|
||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
|
||||
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let Some(last_layout) = self.last_layout.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
Some(Bounds::from_corners(
|
||||
point(
|
||||
bounds.left() + last_layout.x_for_index(range.start),
|
||||
bounds.top(),
|
||||
),
|
||||
point(
|
||||
bounds.left() + last_layout.x_for_index(range.end),
|
||||
bounds.bottom(),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct TextElement {
|
||||
input: View<TextInput>,
|
||||
}
|
||||
|
||||
struct PrepaintState {
|
||||
line: Option<ShapedLine>,
|
||||
cursor: Option<PaintQuad>,
|
||||
selection: Option<PaintQuad>,
|
||||
}
|
||||
|
||||
impl IntoElement for TextElement {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for TextElement {
|
||||
type RequestLayoutState = ();
|
||||
|
||||
type PrepaintState = PrepaintState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = cx.line_height().into();
|
||||
(cx.request_layout(style, []), ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self::PrepaintState {
|
||||
let input = self.input.read(cx);
|
||||
let content = input.content.clone();
|
||||
let selected_range = input.selected_range.clone();
|
||||
let cursor = input.cursor_offset();
|
||||
let style = cx.text_style();
|
||||
let run = TextRun {
|
||||
len: input.content.len(),
|
||||
font: style.font(),
|
||||
color: style.color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
let runs = if let Some(marked_range) = input.marked_range.as_ref() {
|
||||
vec![
|
||||
TextRun {
|
||||
len: marked_range.start,
|
||||
..run.clone()
|
||||
},
|
||||
TextRun {
|
||||
len: marked_range.end - marked_range.start,
|
||||
underline: Some(UnderlineStyle {
|
||||
color: Some(run.color),
|
||||
thickness: px(1.0),
|
||||
wavy: false,
|
||||
}),
|
||||
..run.clone()
|
||||
},
|
||||
TextRun {
|
||||
len: input.content.len() - marked_range.end,
|
||||
..run.clone()
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|run| run.len > 0)
|
||||
.collect()
|
||||
} else {
|
||||
vec![run]
|
||||
};
|
||||
|
||||
let font_size = style.font_size.to_pixels(cx.rem_size());
|
||||
let line = cx
|
||||
.text_system()
|
||||
.shape_line(content, font_size, &runs)
|
||||
.unwrap();
|
||||
|
||||
let cursor_pos = line.x_for_index(cursor);
|
||||
let (selection, cursor) = if selected_range.is_empty() {
|
||||
(
|
||||
None,
|
||||
Some(fill(
|
||||
Bounds::new(
|
||||
point(bounds.left() + cursor_pos, bounds.top()),
|
||||
size(px(2.), bounds.bottom() - bounds.top()),
|
||||
),
|
||||
gpui::blue(),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Some(fill(
|
||||
Bounds::from_corners(
|
||||
point(
|
||||
bounds.left() + line.x_for_index(selected_range.start),
|
||||
bounds.top(),
|
||||
),
|
||||
point(
|
||||
bounds.left() + line.x_for_index(selected_range.end),
|
||||
bounds.bottom(),
|
||||
),
|
||||
),
|
||||
rgba(0x3311FF30),
|
||||
)),
|
||||
None,
|
||||
)
|
||||
};
|
||||
PrepaintState {
|
||||
line: Some(line),
|
||||
cursor,
|
||||
selection,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
||||
cx.handle_input(
|
||||
&focus_handle,
|
||||
ElementInputHandler::new(bounds, self.input.clone()),
|
||||
);
|
||||
if let Some(selection) = prepaint.selection.take() {
|
||||
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() {
|
||||
cx.paint_quad(cursor);
|
||||
}
|
||||
self.input.update(cx, |input, _cx| {
|
||||
input.last_layout = Some(line);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextInput {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.key_context("TextInput")
|
||||
.track_focus(&self.focus_handle)
|
||||
.on_action(cx.listener(Self::backspace))
|
||||
.on_action(cx.listener(Self::delete))
|
||||
.on_action(cx.listener(Self::left))
|
||||
.on_action(cx.listener(Self::right))
|
||||
.on_action(cx.listener(Self::select_left))
|
||||
.on_action(cx.listener(Self::select_right))
|
||||
.on_action(cx.listener(Self::select_all))
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.bg(rgb(0xeeeeee))
|
||||
.size_full()
|
||||
.line_height(px(30.))
|
||||
.text_size(px(24.))
|
||||
.child(
|
||||
div()
|
||||
.h(px(30. + 4. * 2.))
|
||||
.w_full()
|
||||
.p(px(4.))
|
||||
.bg(white())
|
||||
.child(TextElement {
|
||||
input: cx.view().clone(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("backspace", Backspace, None),
|
||||
KeyBinding::new("delete", Delete, None),
|
||||
KeyBinding::new("left", Left, None),
|
||||
KeyBinding::new("right", Right, None),
|
||||
KeyBinding::new("shift-left", SelectLeft, None),
|
||||
KeyBinding::new("shift-right", SelectRight, None),
|
||||
KeyBinding::new("cmd-a", SelectAll, None),
|
||||
KeyBinding::new("home", Home, None),
|
||||
KeyBinding::new("end", End, None),
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None),
|
||||
]);
|
||||
let window = cx
|
||||
.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|cx| {
|
||||
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,
|
||||
})
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
window
|
||||
.update(cx, |view, cx| {
|
||||
view.focus_handle.focus(cx);
|
||||
cx.activate(true)
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
@@ -51,7 +51,7 @@ fn main() {
|
||||
kind: WindowKind::PopUp,
|
||||
is_movable: false,
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_min_size: Size::default(),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{size, DevicePixels, Result, SharedString, Size};
|
||||
|
||||
use image::{Delay, Frame};
|
||||
use image::RgbaImage;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt,
|
||||
@@ -34,19 +34,18 @@ pub struct ImageId(usize);
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
pub(crate) struct RenderImageParams {
|
||||
pub(crate) image_id: ImageId,
|
||||
pub(crate) frame_index: usize,
|
||||
}
|
||||
|
||||
/// A cached and processed image.
|
||||
pub struct ImageData {
|
||||
/// The ID associated with this image
|
||||
pub id: ImageId,
|
||||
data: Vec<Frame>,
|
||||
data: RgbaImage,
|
||||
}
|
||||
|
||||
impl ImageData {
|
||||
/// Create a new image from the given data.
|
||||
pub fn new(data: Vec<Frame>) -> Self {
|
||||
pub fn new(data: RgbaImage) -> Self {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
Self {
|
||||
@@ -56,32 +55,22 @@ impl ImageData {
|
||||
}
|
||||
|
||||
/// Convert this image into a byte slice.
|
||||
pub fn as_bytes(&self, frame_index: usize) -> &[u8] {
|
||||
&self.data[frame_index].buffer()
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Get the size of this image, in pixels.
|
||||
pub fn size(&self, frame_index: usize) -> Size<DevicePixels> {
|
||||
let (width, height) = self.data[frame_index].buffer().dimensions();
|
||||
/// Get the size of this image, in pixels
|
||||
pub fn size(&self) -> Size<DevicePixels> {
|
||||
let (width, height) = self.data.dimensions();
|
||||
size(width.into(), height.into())
|
||||
}
|
||||
|
||||
/// Get the delay of this frame from the previous
|
||||
pub fn delay(&self, frame_index: usize) -> Delay {
|
||||
self.data[frame_index].delay()
|
||||
}
|
||||
|
||||
/// Get the number of frames for this image.
|
||||
pub fn frame_count(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImageData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ImageData")
|
||||
.field("id", &self.id)
|
||||
.field("size", &self.size(0))
|
||||
.field("size", &self.data.dimensions())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io::Cursor};
|
||||
|
||||
use crate::{
|
||||
point, px, size, AbsoluteLength, Asset, Bounds, DefiniteLength, DevicePixels, Element,
|
||||
@@ -8,15 +8,12 @@ use crate::{
|
||||
LayoutId, Length, Pixels, SharedUri, Size, StyleRefinement, Styled, SvgSize, UriOrPath,
|
||||
WindowContext,
|
||||
};
|
||||
|
||||
use futures::{AsyncReadExt, Future};
|
||||
use image::codecs::gif::GifDecoder;
|
||||
use image::{AnimationDecoder, Frame, ImageBuffer, ImageError, ImageFormat};
|
||||
use image::{ImageBuffer, ImageError};
|
||||
#[cfg(target_os = "macos")]
|
||||
use media::core_video::CVImageBuffer;
|
||||
|
||||
use http;
|
||||
use std::time::{Duration, Instant};
|
||||
use thiserror::Error;
|
||||
use util::ResultExt;
|
||||
|
||||
@@ -233,14 +230,8 @@ impl Img {
|
||||
}
|
||||
}
|
||||
|
||||
/// The image state between frames
|
||||
struct ImgState {
|
||||
frame_index: usize,
|
||||
last_frame_time: Instant,
|
||||
}
|
||||
|
||||
impl Element for Img {
|
||||
type RequestLayoutState = usize;
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
@@ -252,72 +243,29 @@ impl Element for Img {
|
||||
global_id: Option<&GlobalElementId>,
|
||||
cx: &mut WindowContext,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
cx.with_optional_element_state(global_id, |state, cx| {
|
||||
let mut state = state.map(|state| {
|
||||
state.unwrap_or(ImgState {
|
||||
frame_index: 0,
|
||||
last_frame_time: Instant::now(),
|
||||
})
|
||||
});
|
||||
|
||||
let frame_index = state.as_ref().map(|state| state.frame_index).unwrap_or(0);
|
||||
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.request_layout(global_id, cx, |mut style, cx| {
|
||||
if let Some(data) = self.source.data(cx) {
|
||||
if let Some(state) = &mut state {
|
||||
let frame_count = data.frame_count();
|
||||
if frame_count > 1 {
|
||||
if state.last_frame_time.elapsed()
|
||||
>= Duration::from(data.delay(state.frame_index))
|
||||
{
|
||||
let time_difference = state.last_frame_time.elapsed()
|
||||
- Duration::from(data.delay(state.frame_index));
|
||||
|
||||
if state.frame_index == frame_count - 1 {
|
||||
state.frame_index = 0;
|
||||
} else {
|
||||
state.frame_index = state.frame_index + 1;
|
||||
};
|
||||
|
||||
state.last_frame_time = Instant::now() - time_difference
|
||||
}
|
||||
let layout_id = self
|
||||
.interactivity
|
||||
.request_layout(global_id, cx, |mut style, cx| {
|
||||
if let Some(data) = self.source.data(cx) {
|
||||
let image_size = data.size();
|
||||
match (style.size.width, style.size.height) {
|
||||
(Length::Auto, Length::Auto) => {
|
||||
style.size = Size {
|
||||
width: Length::Definite(DefiniteLength::Absolute(
|
||||
AbsoluteLength::Pixels(px(image_size.width.0 as f32)),
|
||||
)),
|
||||
height: Length::Definite(DefiniteLength::Absolute(
|
||||
AbsoluteLength::Pixels(px(image_size.height.0 as f32)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
let image_size = data.size(frame_index);
|
||||
match (style.size.width, style.size.height) {
|
||||
(Length::Auto, Length::Auto) => {
|
||||
style.size = Size {
|
||||
width: Length::Definite(DefiniteLength::Absolute(
|
||||
AbsoluteLength::Pixels(px(image_size.width.0 as f32)),
|
||||
)),
|
||||
height: Length::Definite(DefiniteLength::Absolute(
|
||||
AbsoluteLength::Pixels(px(image_size.height.0 as f32)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if global_id.is_some() && data.frame_count() > 1 {
|
||||
let parent_id = cx.parent_view_id();
|
||||
cx.on_next_frame(move |cx| {
|
||||
if let Some(parent_id) = parent_id {
|
||||
cx.notify(parent_id)
|
||||
} else {
|
||||
cx.refresh()
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
cx.request_layout(style, [])
|
||||
});
|
||||
|
||||
((layout_id, frame_index), state)
|
||||
})
|
||||
cx.request_layout(style, [])
|
||||
});
|
||||
(layout_id, ())
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
@@ -335,7 +283,7 @@ impl Element for Img {
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
frame_index: &mut Self::RequestLayoutState,
|
||||
_: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
cx: &mut WindowContext,
|
||||
) {
|
||||
@@ -345,15 +293,9 @@ impl Element for Img {
|
||||
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
|
||||
if let Some(data) = source.data(cx) {
|
||||
let new_bounds = self.object_fit.get_bounds(bounds, data.size(*frame_index));
|
||||
cx.paint_image(
|
||||
new_bounds,
|
||||
corner_radii,
|
||||
data.clone(),
|
||||
*frame_index,
|
||||
self.grayscale,
|
||||
)
|
||||
.log_err();
|
||||
let new_bounds = self.object_fit.get_bounds(bounds, data.size());
|
||||
cx.paint_image(new_bounds, corner_radii, data.clone(), self.grayscale)
|
||||
.log_err();
|
||||
}
|
||||
|
||||
match source {
|
||||
@@ -442,33 +384,12 @@ impl Asset for Image {
|
||||
};
|
||||
|
||||
let data = if let Ok(format) = image::guess_format(&bytes) {
|
||||
let data = match format {
|
||||
ImageFormat::Gif => {
|
||||
let mut gif = GifDecoder::new(Cursor::new(&bytes))?
|
||||
.into_frames()
|
||||
.collect_frames()?;
|
||||
let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
|
||||
|
||||
for frame in &mut gif {
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in frame.buffer_mut().chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
}
|
||||
|
||||
gif
|
||||
}
|
||||
_ => {
|
||||
let mut data =
|
||||
image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
|
||||
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
vec![Frame::new(data)]
|
||||
}
|
||||
};
|
||||
// Convert from RGBA to BGRA.
|
||||
for pixel in data.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
}
|
||||
|
||||
ImageData::new(data)
|
||||
} else {
|
||||
@@ -478,7 +399,7 @@ impl Asset for Image {
|
||||
let buffer =
|
||||
ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take()).unwrap();
|
||||
|
||||
ImageData::new(vec![Frame::new(buffer)])
|
||||
ImageData::new(buffer)
|
||||
};
|
||||
|
||||
Ok(Arc::new(data))
|
||||
|
||||
@@ -569,7 +569,7 @@ pub struct WindowOptions {
|
||||
pub app_id: Option<String>,
|
||||
|
||||
/// Window minimum size
|
||||
pub window_min_size: Option<Size<Pixels>>,
|
||||
pub window_min_size: Size<Pixels>,
|
||||
}
|
||||
|
||||
/// The variables that can be configured when creating a new window
|
||||
@@ -599,7 +599,7 @@ pub(crate) struct WindowParams {
|
||||
pub window_background: WindowBackgroundAppearance,
|
||||
|
||||
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
||||
pub window_min_size: Option<Size<Pixels>>,
|
||||
pub window_min_size: Size<Pixels>,
|
||||
}
|
||||
|
||||
/// Represents the status of how a window should be opened.
|
||||
@@ -648,7 +648,7 @@ impl Default for WindowOptions {
|
||||
display_id: None,
|
||||
window_background: WindowBackgroundAppearance::default(),
|
||||
app_id: None,
|
||||
window_min_size: None,
|
||||
window_min_size: Size::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ impl CosmicTextSystemState {
|
||||
) -> Result<SmallVec<[FontId; 4]>> {
|
||||
// TODO: Determine the proper system UI font.
|
||||
let name = if name == ".SystemUIFont" {
|
||||
"Zed Plex Sans"
|
||||
"Zed Sans"
|
||||
} else {
|
||||
name
|
||||
};
|
||||
|
||||
@@ -94,27 +94,6 @@ impl Keystroke {
|
||||
}
|
||||
}
|
||||
|
||||
//Allow for the user to specify a keystroke modifier as the key itself
|
||||
//This sets the `key` to the modifier, and disables the modifier
|
||||
if key.is_none() {
|
||||
if shift {
|
||||
key = Some("shift".to_string());
|
||||
shift = false;
|
||||
} else if control {
|
||||
key = Some("control".to_string());
|
||||
control = false;
|
||||
} else if alt {
|
||||
key = Some("alt".to_string());
|
||||
alt = false;
|
||||
} else if platform {
|
||||
key = Some("platform".to_string());
|
||||
platform = false;
|
||||
} else if function {
|
||||
key = Some("function".to_string());
|
||||
function = false;
|
||||
}
|
||||
}
|
||||
|
||||
let key = key.ok_or_else(|| anyhow!("Invalid keystroke `{}`", source))?;
|
||||
|
||||
Ok(Keystroke {
|
||||
@@ -207,10 +186,6 @@ impl std::fmt::Display for Keystroke {
|
||||
"right" => '→',
|
||||
"tab" => '⇥',
|
||||
"escape" => '⎋',
|
||||
"shift" => '⇧',
|
||||
"control" => '⌃',
|
||||
"alt" => '⌥',
|
||||
"platform" => '⌘',
|
||||
key => {
|
||||
if key.len() == 1 {
|
||||
key.chars().next().unwrap().to_ascii_uppercase()
|
||||
@@ -266,15 +241,6 @@ impl Modifiers {
|
||||
}
|
||||
}
|
||||
|
||||
/// How many modifier keys are pressed
|
||||
pub fn number_of_modifiers(&self) -> u8 {
|
||||
self.control as u8
|
||||
+ self.alt as u8
|
||||
+ self.shift as u8
|
||||
+ self.platform as u8
|
||||
+ self.function as u8
|
||||
}
|
||||
|
||||
/// helper method for Modifiers with no modifiers
|
||||
pub fn none() -> Modifiers {
|
||||
Default::default()
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
use std::any::{type_name, Any};
|
||||
use std::cell::{self, RefCell};
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -509,27 +508,6 @@ pub(super) fn is_within_click_distance(a: Point<Pixels>, b: Point<Pixels>) -> bo
|
||||
diff.x.abs() <= DOUBLE_CLICK_DISTANCE && diff.y.abs() <= DOUBLE_CLICK_DISTANCE
|
||||
}
|
||||
|
||||
pub(super) fn get_xkb_compose_state(cx: &xkb::Context) -> Option<xkb::compose::State> {
|
||||
let mut locales = Vec::default();
|
||||
if let Some(locale) = std::env::var_os("LC_CTYPE") {
|
||||
locales.push(locale);
|
||||
}
|
||||
locales.push(OsString::from("C"));
|
||||
let mut state: Option<xkb::compose::State> = None;
|
||||
for locale in locales {
|
||||
if let Ok(table) =
|
||||
xkb::compose::Table::new_from_locale(&cx, &locale, xkb::compose::COMPILE_NO_FLAGS)
|
||||
{
|
||||
state = Some(xkb::compose::State::new(
|
||||
&table,
|
||||
xkb::compose::STATE_NO_FLAGS,
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
pub(super) unsafe fn read_fd(mut fd: FileDescriptor) -> Result<String> {
|
||||
let mut file = File::from_raw_fd(fd.as_raw_fd());
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::ffi::OsString;
|
||||
use std::hash::Hash;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::path::PathBuf;
|
||||
@@ -64,6 +65,7 @@ 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::is_within_click_distance;
|
||||
use crate::platform::linux::wayland::clipboard::{
|
||||
Clipboard, DataOffer, FILE_LIST_MIME_TYPE, TEXT_MIME_TYPE,
|
||||
};
|
||||
@@ -72,7 +74,6 @@ 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};
|
||||
use crate::platform::PlatformWindow;
|
||||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, FileDropEvent, ForegroundExecutor, MouseExitEvent, Size,
|
||||
@@ -670,7 +671,7 @@ impl LinuxClient for WaylandClient {
|
||||
return;
|
||||
};
|
||||
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
|
||||
state.clipboard.set_primary(item);
|
||||
state.clipboard.set_primary(item.text);
|
||||
let serial = state.serial_tracker.get(SerialKind::KeyPress);
|
||||
let data_source = primary_selection_manager.create_source(&state.globals.qh, ());
|
||||
data_source.offer(state.clipboard.self_mime());
|
||||
@@ -688,7 +689,7 @@ impl LinuxClient for WaylandClient {
|
||||
return;
|
||||
};
|
||||
if state.mouse_focused_window.is_some() || state.keyboard_focused_window.is_some() {
|
||||
state.clipboard.set(item);
|
||||
state.clipboard.set(item.text);
|
||||
let serial = state.serial_tracker.get(SerialKind::KeyPress);
|
||||
let data_source = data_device_manager.create_data_source(&state.globals.qh, ());
|
||||
data_source.offer(state.clipboard.self_mime());
|
||||
@@ -698,11 +699,25 @@ impl LinuxClient for WaylandClient {
|
||||
}
|
||||
|
||||
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||
self.0.borrow_mut().clipboard.read_primary()
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.clipboard
|
||||
.read_primary()
|
||||
.map(|s| crate::ClipboardItem {
|
||||
text: s,
|
||||
metadata: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||
self.0.borrow_mut().clipboard.read()
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.clipboard
|
||||
.read()
|
||||
.map(|s| crate::ClipboardItem {
|
||||
text: s,
|
||||
metadata: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||
@@ -1053,8 +1068,21 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
.flatten()
|
||||
.expect("Failed to create keymap")
|
||||
};
|
||||
let table = {
|
||||
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
|
||||
xkb::compose::Table::new_from_locale(
|
||||
&xkb_context,
|
||||
&locale,
|
||||
xkb::compose::COMPILE_NO_FLAGS,
|
||||
)
|
||||
.log_err()
|
||||
.unwrap()
|
||||
};
|
||||
state.keymap_state = Some(xkb::State::new(&keymap));
|
||||
state.compose_state = get_xkb_compose_state(&xkb_context);
|
||||
state.compose_state = Some(xkb::compose::State::new(
|
||||
&table,
|
||||
xkb::compose::STATE_NO_FLAGS,
|
||||
));
|
||||
}
|
||||
wl_keyboard::Event::Enter {
|
||||
serial, surface, ..
|
||||
@@ -1134,7 +1162,6 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
compose.feed(keysym);
|
||||
match compose.status() {
|
||||
xkb::Status::Composing => {
|
||||
keystroke.ime_key = None;
|
||||
state.pre_edit_text =
|
||||
compose.utf8().or(Keystroke::underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
@@ -1147,9 +1174,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientStatePtr {
|
||||
xkb::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = compose.utf8();
|
||||
if let Some(keysym) = compose.keysym() {
|
||||
keystroke.key = xkb::keysym_get_name(keysym);
|
||||
}
|
||||
keystroke.key = xkb::keysym_get_name(compose.keysym().unwrap());
|
||||
}
|
||||
xkb::Status::Cancelled => {
|
||||
let pre_edit = state.pre_edit_text.take();
|
||||
|
||||
@@ -9,7 +9,7 @@ use filedescriptor::Pipe;
|
||||
use wayland_client::{protocol::wl_data_offer::WlDataOffer, Connection};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::ZwpPrimarySelectionOfferV1;
|
||||
|
||||
use crate::{platform::linux::platform::read_fd, ClipboardItem, WaylandClientStatePtr};
|
||||
use crate::{platform::linux::platform::read_fd, WaylandClientStatePtr};
|
||||
|
||||
pub(crate) const TEXT_MIME_TYPE: &str = "text/plain;charset=utf-8";
|
||||
pub(crate) const FILE_LIST_MIME_TYPE: &str = "text/uri-list";
|
||||
@@ -23,13 +23,13 @@ pub(crate) struct Clipboard {
|
||||
self_mime: String,
|
||||
|
||||
// Internal clipboard
|
||||
contents: Option<ClipboardItem>,
|
||||
primary_contents: Option<ClipboardItem>,
|
||||
contents: Option<String>,
|
||||
primary_contents: Option<String>,
|
||||
|
||||
// External clipboard
|
||||
cached_read: Option<ClipboardItem>,
|
||||
cached_read: Option<String>,
|
||||
current_offer: Option<DataOffer<WlDataOffer>>,
|
||||
cached_primary_read: Option<ClipboardItem>,
|
||||
cached_primary_read: Option<String>,
|
||||
current_primary_offer: Option<DataOffer<ZwpPrimarySelectionOfferV1>>,
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ impl Clipboard {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, item: ClipboardItem) {
|
||||
self.contents = Some(item);
|
||||
pub fn set(&mut self, text: String) {
|
||||
self.contents = Some(text);
|
||||
}
|
||||
|
||||
pub fn set_primary(&mut self, item: ClipboardItem) {
|
||||
self.primary_contents = Some(item);
|
||||
pub fn set_primary(&mut self, text: String) {
|
||||
self.primary_contents = Some(text);
|
||||
}
|
||||
|
||||
pub fn set_offer(&mut self, data_offer: Option<DataOffer<WlDataOffer>>) {
|
||||
@@ -113,17 +113,17 @@ impl Clipboard {
|
||||
|
||||
pub fn send(&self, _mime_type: String, fd: OwnedFd) {
|
||||
if let Some(contents) = &self.contents {
|
||||
self.send_internal(fd, contents.text.as_bytes().to_owned());
|
||||
self.send_internal(fd, contents.as_bytes().to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_primary(&self, _mime_type: String, fd: OwnedFd) {
|
||||
if let Some(primary_contents) = &self.primary_contents {
|
||||
self.send_internal(fd, primary_contents.text.as_bytes().to_owned());
|
||||
self.send_internal(fd, primary_contents.as_bytes().to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Option<ClipboardItem> {
|
||||
pub fn read(&mut self) -> Option<String> {
|
||||
let offer = self.current_offer.clone()?;
|
||||
if let Some(cached) = self.cached_read.clone() {
|
||||
return Some(cached);
|
||||
@@ -145,8 +145,8 @@ impl Clipboard {
|
||||
|
||||
match unsafe { read_fd(fd) } {
|
||||
Ok(v) => {
|
||||
self.cached_read = Some(ClipboardItem::new(v));
|
||||
self.cached_read.clone()
|
||||
self.cached_read = Some(v.clone());
|
||||
Some(v)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("error reading clipboard pipe: {err:?}");
|
||||
@@ -155,7 +155,7 @@ impl Clipboard {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_primary(&mut self) -> Option<ClipboardItem> {
|
||||
pub fn read_primary(&mut self) -> Option<String> {
|
||||
let offer = self.current_primary_offer.clone()?;
|
||||
if let Some(cached) = self.cached_primary_read.clone() {
|
||||
return Some(cached);
|
||||
@@ -177,8 +177,8 @@ impl Clipboard {
|
||||
|
||||
match unsafe { read_fd(fd) } {
|
||||
Ok(v) => {
|
||||
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
|
||||
self.cached_primary_read.clone()
|
||||
self.cached_primary_read = Some(v.clone());
|
||||
Some(v)
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("error reading clipboard pipe: {err:?}");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsString;
|
||||
use std::ops::Deref;
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -28,13 +29,13 @@ use xkbcommon::xkb as xkbc;
|
||||
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, PlatformDisplay, PlatformInput,
|
||||
Point, ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
modifiers_from_xinput_info, point, px, AnyWindowHandle, Bounds, CursorStyle, DisplayId,
|
||||
Keystroke, Modifiers, ModifiersChangedEvent, Pixels, PlatformDisplay, PlatformInput, Point,
|
||||
ScrollDelta, Size, TouchPhase, WindowParams, X11Window,
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::{get_xkb_compose_state, open_uri_internal, SCROLL_LINES},
|
||||
super::{open_uri_internal, SCROLL_LINES},
|
||||
X11Display, X11WindowStatePtr, XcbAtoms,
|
||||
};
|
||||
use super::{button_of_key, modifiers_from_state, pressed_button_from_mask};
|
||||
@@ -115,7 +116,7 @@ pub struct X11ClientState {
|
||||
pub(crate) xim_handler: Option<XimHandler>,
|
||||
pub modifiers: Modifiers,
|
||||
|
||||
pub(crate) compose_state: Option<xkbc::compose::State>,
|
||||
pub(crate) compose_state: xkbc::compose::State,
|
||||
pub(crate) pre_edit_text: Option<String>,
|
||||
pub(crate) composing: bool,
|
||||
pub(crate) cursor_handle: cursor::Handle,
|
||||
@@ -128,7 +129,6 @@ pub struct X11ClientState {
|
||||
|
||||
pub(crate) common: LinuxCommon,
|
||||
pub(crate) clipboard: x11_clipboard::Clipboard,
|
||||
pub(crate) clipboard_item: Option<ClipboardItem>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -249,7 +249,18 @@ impl X11Client {
|
||||
);
|
||||
xkbc::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id)
|
||||
};
|
||||
let compose_state = get_xkb_compose_state(&xkb_context);
|
||||
let compose_state = {
|
||||
let locale = std::env::var_os("LC_CTYPE").unwrap_or(OsString::from("C"));
|
||||
let table = xkbc::compose::Table::new_from_locale(
|
||||
&xkb_context,
|
||||
&locale,
|
||||
xkbc::compose::COMPILE_NO_FLAGS,
|
||||
)
|
||||
.log_err()
|
||||
.unwrap();
|
||||
xkbc::compose::State::new(&table, xkbc::compose::STATE_NO_FLAGS)
|
||||
};
|
||||
|
||||
let resource_database = x11rb::resource_manager::new_from_default(&xcb_connection).unwrap();
|
||||
|
||||
let scale_factor = resource_database
|
||||
@@ -389,7 +400,7 @@ impl X11Client {
|
||||
ximc,
|
||||
xim_handler,
|
||||
|
||||
compose_state,
|
||||
compose_state: compose_state,
|
||||
pre_edit_text: None,
|
||||
composing: false,
|
||||
|
||||
@@ -402,7 +413,6 @@ impl X11Client {
|
||||
scroll_y: None,
|
||||
|
||||
clipboard,
|
||||
clipboard_item: None,
|
||||
})))
|
||||
}
|
||||
|
||||
@@ -514,9 +524,7 @@ impl X11Client {
|
||||
window.set_focused(false);
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.focused_window = None;
|
||||
if let Some(compose_state) = state.compose_state.as_mut() {
|
||||
compose_state.reset();
|
||||
}
|
||||
state.compose_state.reset();
|
||||
state.pre_edit_text.take();
|
||||
drop(state);
|
||||
self.disable_ime();
|
||||
@@ -562,42 +570,37 @@ impl X11Client {
|
||||
if keysym.is_modifier_key() {
|
||||
return Some(());
|
||||
}
|
||||
if let Some(mut compose_state) = state.compose_state.take() {
|
||||
compose_state.feed(keysym);
|
||||
match compose_state.status() {
|
||||
xkbc::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = compose_state.utf8();
|
||||
if let Some(keysym) = compose_state.keysym() {
|
||||
keystroke.key = xkbc::keysym_get_name(keysym);
|
||||
}
|
||||
}
|
||||
xkbc::Status::Composing => {
|
||||
keystroke.ime_key = None;
|
||||
state.pre_edit_text = compose_state
|
||||
.utf8()
|
||||
.or(crate::Keystroke::underlying_dead_key(keysym));
|
||||
let pre_edit =
|
||||
state.pre_edit_text.clone().unwrap_or(String::default());
|
||||
drop(state);
|
||||
window.handle_ime_preedit(pre_edit);
|
||||
state = self.0.borrow_mut();
|
||||
}
|
||||
xkbc::Status::Cancelled => {
|
||||
let pre_edit = state.pre_edit_text.take();
|
||||
drop(state);
|
||||
if let Some(pre_edit) = pre_edit {
|
||||
window.handle_ime_commit(pre_edit);
|
||||
}
|
||||
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
|
||||
window.handle_ime_preedit(current_key);
|
||||
}
|
||||
state = self.0.borrow_mut();
|
||||
compose_state.feed(keysym);
|
||||
}
|
||||
_ => {}
|
||||
state.compose_state.feed(keysym);
|
||||
match state.compose_state.status() {
|
||||
xkbc::Status::Composed => {
|
||||
state.pre_edit_text.take();
|
||||
keystroke.ime_key = state.compose_state.utf8();
|
||||
keystroke.key =
|
||||
xkbc::keysym_get_name(state.compose_state.keysym().unwrap());
|
||||
}
|
||||
state.compose_state = Some(compose_state);
|
||||
xkbc::Status::Composing => {
|
||||
state.pre_edit_text = state
|
||||
.compose_state
|
||||
.utf8()
|
||||
.or(crate::Keystroke::underlying_dead_key(keysym));
|
||||
let pre_edit = state.pre_edit_text.clone().unwrap_or(String::default());
|
||||
drop(state);
|
||||
window.handle_ime_preedit(pre_edit);
|
||||
state = self.0.borrow_mut();
|
||||
}
|
||||
xkbc::Status::Cancelled => {
|
||||
let pre_edit = state.pre_edit_text.take();
|
||||
drop(state);
|
||||
if let Some(pre_edit) = pre_edit {
|
||||
window.handle_ime_commit(pre_edit);
|
||||
}
|
||||
if let Some(current_key) = Keystroke::underlying_dead_key(keysym) {
|
||||
window.handle_ime_preedit(current_key);
|
||||
}
|
||||
state = self.0.borrow_mut();
|
||||
state.compose_state.feed(keysym);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
keystroke
|
||||
};
|
||||
@@ -646,9 +649,7 @@ impl X11Client {
|
||||
window.handle_ime_unmark();
|
||||
state = self.0.borrow_mut();
|
||||
} else if let Some(text) = state.pre_edit_text.take() {
|
||||
if let Some(compose_state) = state.compose_state.as_mut() {
|
||||
compose_state.reset();
|
||||
}
|
||||
state.compose_state.reset();
|
||||
drop(state);
|
||||
window.handle_ime_commit(text);
|
||||
state = self.0.borrow_mut();
|
||||
@@ -1096,7 +1097,7 @@ impl LinuxClient for X11Client {
|
||||
}
|
||||
|
||||
fn write_to_clipboard(&self, item: crate::ClipboardItem) {
|
||||
let mut state = self.0.borrow_mut();
|
||||
let state = self.0.borrow_mut();
|
||||
state
|
||||
.clipboard
|
||||
.store(
|
||||
@@ -1105,7 +1106,6 @@ impl LinuxClient for X11Client {
|
||||
item.text().as_bytes(),
|
||||
)
|
||||
.ok();
|
||||
state.clipboard_item.replace(item);
|
||||
}
|
||||
|
||||
fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
|
||||
@@ -1127,20 +1127,6 @@ impl LinuxClient for X11Client {
|
||||
|
||||
fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
|
||||
let state = self.0.borrow_mut();
|
||||
// if the last copy was from this app, return our cached item
|
||||
// which has metadata attached.
|
||||
if state
|
||||
.clipboard
|
||||
.setter
|
||||
.connection
|
||||
.get_selection_owner(state.clipboard.setter.atoms.clipboard)
|
||||
.ok()
|
||||
.and_then(|r| r.reply().ok())
|
||||
.map(|reply| reply.owner == state.clipboard.setter.window)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return state.clipboard_item.clone();
|
||||
}
|
||||
state
|
||||
.clipboard
|
||||
.load(
|
||||
@@ -1181,10 +1167,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);
|
||||
|
||||
@@ -34,6 +34,7 @@ use std::{
|
||||
};
|
||||
|
||||
use super::{X11Display, XINPUT_MASTER_DEVICE};
|
||||
|
||||
x11rb::atom_manager! {
|
||||
pub XcbAtoms: AtomsCookie {
|
||||
UTF8_STRING,
|
||||
@@ -47,7 +48,6 @@ x11rb::atom_manager! {
|
||||
_NET_WM_STATE_FULLSCREEN,
|
||||
_NET_WM_STATE_HIDDEN,
|
||||
_NET_WM_STATE_FOCUSED,
|
||||
_NET_ACTIVE_WINDOW,
|
||||
_NET_WM_MOVERESIZE,
|
||||
_NET_WM_WINDOW_TYPE,
|
||||
_NET_WM_WINDOW_TYPE_NOTIFICATION,
|
||||
@@ -802,21 +802,10 @@ impl PlatformWindow for X11Window {
|
||||
}
|
||||
|
||||
fn activate(&self) {
|
||||
let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
|
||||
let message = xproto::ClientMessageEvent::new(
|
||||
32,
|
||||
self.0.x_window,
|
||||
self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
|
||||
data,
|
||||
);
|
||||
let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
|
||||
self.0
|
||||
.xcb_connection
|
||||
.send_event(
|
||||
false,
|
||||
self.0.state.borrow().x_root_window,
|
||||
xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
|
||||
message,
|
||||
)
|
||||
.configure_window(self.0.x_window, &win_aux)
|
||||
.log_err();
|
||||
self.0
|
||||
.xcb_connection
|
||||
|
||||
@@ -647,12 +647,10 @@ impl MacWindow {
|
||||
|
||||
native_window.setMovable_(is_movable as BOOL);
|
||||
|
||||
if let Some(window_min_size) = window_min_size {
|
||||
native_window.setContentMinSize_(NSSize {
|
||||
width: window_min_size.width.to_f64(),
|
||||
height: window_min_size.height.to_f64(),
|
||||
});
|
||||
}
|
||||
native_window.setContentMinSize_(NSSize {
|
||||
width: window_min_size.width.to_f64(),
|
||||
height: window_min_size.height.to_f64(),
|
||||
});
|
||||
|
||||
if titlebar.map_or(true, |titlebar| titlebar.appears_transparent) {
|
||||
native_window.setTitlebarAppearsTransparent_(YES);
|
||||
@@ -1672,13 +1670,9 @@ extern "C" fn first_rect_for_character_range(
|
||||
range: NSRange,
|
||||
_: id,
|
||||
) -> NSRect {
|
||||
let frame: NSRect = unsafe {
|
||||
let state = get_window_state(this);
|
||||
let lock = state.lock();
|
||||
let mut frame = NSWindow::frame(lock.native_window);
|
||||
let content_layout_rect: CGRect = msg_send![lock.native_window, contentLayoutRect];
|
||||
frame.origin.y -= frame.size.height - content_layout_rect.size.height;
|
||||
frame
|
||||
let frame = unsafe {
|
||||
let window = get_window_state(this).lock().native_window;
|
||||
NSView::frame(window)
|
||||
};
|
||||
with_input_handler(this, |input_handler| {
|
||||
input_handler.bounds_for_range(range.to_range()?)
|
||||
|
||||
@@ -64,7 +64,7 @@ impl TextSystem {
|
||||
fallback_font_stack: smallvec![
|
||||
// TODO: This is currently Zed-specific.
|
||||
// We should allow GPUI users to provide their own fallback font stack.
|
||||
font("Zed Plex Mono"),
|
||||
font("Zed Mono"),
|
||||
font("Helvetica"),
|
||||
font("Cantarell"), // Gnome
|
||||
font("Ubuntu"), // Gnome (Ubuntu)
|
||||
|
||||
@@ -58,7 +58,7 @@ impl<'de> serde::Deserialize<'de> for FontFeatures {
|
||||
while let Some((key, value)) =
|
||||
access.next_entry::<String, Option<FeatureValue>>()?
|
||||
{
|
||||
if !is_valid_feature_tag(&key) {
|
||||
if key.len() != 4 && !key.is_ascii() {
|
||||
log::error!("Incorrect font feature tag: {}", key);
|
||||
continue;
|
||||
}
|
||||
@@ -142,7 +142,3 @@ impl schemars::JsonSchema for FontFeatures {
|
||||
schema.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_feature_tag(tag: &str) -> bool {
|
||||
tag.len() == 4 && tag.chars().all(|c| c.is_ascii_alphanumeric())
|
||||
}
|
||||
|
||||
@@ -154,12 +154,12 @@ mod tests {
|
||||
let cx = TestAppContext::new(dispatcher, None);
|
||||
cx.text_system()
|
||||
.add_fonts(vec![std::fs::read(
|
||||
"../../assets/fonts/plex-mono/ZedPlexMono-Regular.ttf",
|
||||
"../../assets/fonts/zed-mono/zed-mono-extended.ttf",
|
||||
)
|
||||
.unwrap()
|
||||
.into()])
|
||||
.unwrap();
|
||||
let id = cx.text_system().font_id(&font("Zed Plex Mono")).unwrap();
|
||||
let id = cx.text_system().font_id(&font("Zed Mono")).unwrap();
|
||||
|
||||
cx.update(|cx| {
|
||||
let text_system = cx.text_system().clone();
|
||||
|
||||
@@ -549,17 +549,10 @@ pub struct Window {
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
focus_enabled: bool,
|
||||
pending_input: Option<PendingInput>,
|
||||
pending_modifier: ModifierState,
|
||||
pending_input_observers: SubscriberSet<(), AnyObserver>,
|
||||
prompt: Option<RenderablePromptHandle>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ModifierState {
|
||||
modifiers: Modifiers,
|
||||
saw_keystroke: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum DrawPhase {
|
||||
None,
|
||||
@@ -830,7 +823,6 @@ impl Window {
|
||||
focus: None,
|
||||
focus_enabled: true,
|
||||
pending_input: None,
|
||||
pending_modifier: ModifierState::default(),
|
||||
pending_input_observers: SubscriberSet::new(),
|
||||
prompt: None,
|
||||
})
|
||||
@@ -2567,7 +2559,6 @@ impl<'a> WindowContext<'a> {
|
||||
bounds: Bounds<Pixels>,
|
||||
corner_radii: Corners<Pixels>,
|
||||
data: Arc<ImageData>,
|
||||
frame_index: usize,
|
||||
grayscale: bool,
|
||||
) -> Result<()> {
|
||||
debug_assert_eq!(
|
||||
@@ -2578,19 +2569,13 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
let scale_factor = self.scale_factor();
|
||||
let bounds = bounds.scale(scale_factor);
|
||||
let params = RenderImageParams {
|
||||
image_id: data.id,
|
||||
frame_index,
|
||||
};
|
||||
let params = RenderImageParams { image_id: data.id };
|
||||
|
||||
let tile = self
|
||||
.window
|
||||
.sprite_atlas
|
||||
.get_or_insert_with(¶ms.clone().into(), &mut || {
|
||||
Ok(Some((
|
||||
data.size(frame_index),
|
||||
Cow::Borrowed(data.as_bytes(frame_index)),
|
||||
)))
|
||||
Ok(Some((data.size(), Cow::Borrowed(data.as_bytes()))))
|
||||
})?
|
||||
.expect("Callback above only returns Some");
|
||||
let content_mask = self.content_mask().scale(scale_factor);
|
||||
@@ -3176,135 +3161,70 @@ impl<'a> WindowContext<'a> {
|
||||
.dispatch_tree
|
||||
.dispatch_path(node_id);
|
||||
|
||||
let mut bindings: SmallVec<[KeyBinding; 1]> = SmallVec::new();
|
||||
let mut pending = false;
|
||||
let mut keystroke: Option<Keystroke> = None;
|
||||
|
||||
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
|
||||
if event.modifiers.number_of_modifiers() == 0
|
||||
&& self.window.pending_modifier.modifiers.number_of_modifiers() == 1
|
||||
&& !self.window.pending_modifier.saw_keystroke
|
||||
{
|
||||
if event.modifiers.number_of_modifiers() == 0 {
|
||||
let key = match self.window.pending_modifier.modifiers {
|
||||
modifiers if modifiers.shift => Some("shift"),
|
||||
modifiers if modifiers.control => Some("control"),
|
||||
modifiers if modifiers.alt => Some("alt"),
|
||||
modifiers if modifiers.platform => Some("platform"),
|
||||
modifiers if modifiers.function => Some("function"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(key) = key {
|
||||
let key = Keystroke {
|
||||
key: key.to_string(),
|
||||
ime_key: None,
|
||||
modifiers: Modifiers::default(),
|
||||
};
|
||||
let KeymatchResult {
|
||||
bindings: modifier_bindings,
|
||||
pending: pending_bindings,
|
||||
} = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_key(&key, &dispatch_path);
|
||||
|
||||
keystroke = Some(key);
|
||||
bindings = modifier_bindings;
|
||||
pending = pending_bindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.window.pending_modifier.modifiers.number_of_modifiers() == 0
|
||||
&& event.modifiers.number_of_modifiers() == 1
|
||||
{
|
||||
self.window.pending_modifier.saw_keystroke = false
|
||||
}
|
||||
self.window.pending_modifier.modifiers = event.modifiers
|
||||
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
self.window.pending_modifier.saw_keystroke = true;
|
||||
let KeymatchResult {
|
||||
bindings: key_down_bindings,
|
||||
pending: key_down_pending,
|
||||
} = self
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
let KeymatchResult { bindings, pending } = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.dispatch_key(&key_down_event.keystroke, &dispatch_path);
|
||||
|
||||
keystroke = Some(key_down_event.keystroke.clone());
|
||||
if pending {
|
||||
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
|
||||
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus
|
||||
{
|
||||
currently_pending = PendingInput::default();
|
||||
}
|
||||
currently_pending.focus = self.window.focus;
|
||||
currently_pending
|
||||
.keystrokes
|
||||
.push(key_down_event.keystroke.clone());
|
||||
for binding in bindings {
|
||||
currently_pending.bindings.push(binding);
|
||||
}
|
||||
|
||||
bindings = key_down_bindings;
|
||||
pending = key_down_pending;
|
||||
}
|
||||
currently_pending.timer = Some(self.spawn(|mut cx| async move {
|
||||
cx.background_executor.timer(Duration::from_secs(1)).await;
|
||||
cx.update(move |cx| {
|
||||
cx.clear_pending_keystrokes();
|
||||
let Some(currently_pending) = cx.window.pending_input.take() else {
|
||||
return;
|
||||
};
|
||||
cx.pending_input_changed();
|
||||
cx.replay_pending_input(currently_pending);
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
|
||||
if keystroke.is_none() {
|
||||
self.finish_dispatch_key_event(event, dispatch_path);
|
||||
return;
|
||||
}
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
self.pending_input_changed();
|
||||
|
||||
if pending {
|
||||
let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
|
||||
if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
|
||||
currently_pending = PendingInput::default();
|
||||
}
|
||||
currently_pending.focus = self.window.focus;
|
||||
if let Some(keystroke) = keystroke {
|
||||
currently_pending.keystrokes.push(keystroke.clone());
|
||||
}
|
||||
for binding in bindings {
|
||||
currently_pending.bindings.push(binding);
|
||||
}
|
||||
self.propagate_event = false;
|
||||
|
||||
currently_pending.timer = Some(self.spawn(|mut cx| async move {
|
||||
cx.background_executor.timer(Duration::from_secs(1)).await;
|
||||
cx.update(move |cx| {
|
||||
cx.clear_pending_keystrokes();
|
||||
let Some(currently_pending) = cx.window.pending_input.take() else {
|
||||
return;
|
||||
};
|
||||
cx.replay_pending_input(currently_pending);
|
||||
cx.pending_input_changed();
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
|
||||
self.window.pending_input = Some(currently_pending);
|
||||
self.pending_input_changed();
|
||||
|
||||
self.propagate_event = false;
|
||||
return;
|
||||
} else if let Some(currently_pending) = self.window.pending_input.take() {
|
||||
self.pending_input_changed();
|
||||
if bindings
|
||||
.iter()
|
||||
.all(|binding| !currently_pending.used_by_binding(binding))
|
||||
{
|
||||
self.replay_pending_input(currently_pending)
|
||||
}
|
||||
}
|
||||
|
||||
if !bindings.is_empty() {
|
||||
self.clear_pending_keystrokes();
|
||||
}
|
||||
|
||||
self.propagate_event = true;
|
||||
for binding in bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||
if !self.propagate_event {
|
||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||
return;
|
||||
} else if let Some(currently_pending) = self.window.pending_input.take() {
|
||||
self.pending_input_changed();
|
||||
if bindings
|
||||
.iter()
|
||||
.all(|binding| !currently_pending.used_by_binding(binding))
|
||||
{
|
||||
self.replay_pending_input(currently_pending)
|
||||
}
|
||||
}
|
||||
|
||||
if !bindings.is_empty() {
|
||||
self.clear_pending_keystrokes();
|
||||
}
|
||||
|
||||
self.propagate_event = true;
|
||||
for binding in bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||
if !self.propagate_event {
|
||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finish_dispatch_key_event(event, dispatch_path)
|
||||
}
|
||||
|
||||
fn finish_dispatch_key_event(
|
||||
&mut self,
|
||||
event: &dyn Any,
|
||||
dispatch_path: SmallVec<[DispatchNodeId; 32]>,
|
||||
) {
|
||||
self.dispatch_key_down_up_event(event, &dispatch_path);
|
||||
if !self.propagate_event {
|
||||
return;
|
||||
|
||||
@@ -511,7 +511,7 @@ impl LanguageRegistry {
|
||||
) -> impl Future<Output = Result<Arc<Language>>> {
|
||||
let filename = path.file_name().and_then(|name| name.to_str());
|
||||
let extension = path.extension_or_hidden_file_name();
|
||||
let path_suffixes = [extension, filename, path.to_str()];
|
||||
let path_suffixes = [extension, filename];
|
||||
let empty = GlobSet::empty();
|
||||
|
||||
let rx = self.get_or_load_language(move |language_name, config| {
|
||||
|
||||
@@ -662,17 +662,6 @@ impl settings::Settings for AllLanguageSettings {
|
||||
.ok_or_else(Self::missing_default)?;
|
||||
|
||||
let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
|
||||
|
||||
for (language, suffixes) in &default_value.file_types {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
}
|
||||
|
||||
file_types.insert(language.clone(), builder.build()?);
|
||||
}
|
||||
|
||||
for user_settings in sources.customizations() {
|
||||
if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
|
||||
copilot_enabled = Some(copilot);
|
||||
@@ -712,15 +701,6 @@ impl settings::Settings for AllLanguageSettings {
|
||||
for (language, suffixes) in &user_settings.file_types {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
|
||||
let default_value = default_value.file_types.get(&language.clone());
|
||||
|
||||
// Merge the default value with the user's value.
|
||||
if let Some(suffixes) = default_value {
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
}
|
||||
}
|
||||
|
||||
for suffix in suffixes {
|
||||
builder.add(Glob::new(suffix)?);
|
||||
}
|
||||
|
||||
@@ -213,12 +213,7 @@ impl LspAdapter for JsonLspAdapter {
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
[
|
||||
("JSON".into(), "json".into()),
|
||||
("JSONC".into(), "jsonc".into()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
[("JSON".into(), "jsonc".into())].into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +348,7 @@ impl LspAdapter for NodeVersionAdapter {
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: destination_path,
|
||||
path: destination_path.join("package-version-server"),
|
||||
env: None,
|
||||
arguments: Default::default(),
|
||||
})
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
@@ -1,12 +0,0 @@
|
||||
name = "JSONC"
|
||||
grammar = "jsonc"
|
||||
path_suffixes = ["jsonc"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ",]}"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
tab_size = 2
|
||||
prettier_parser_name = "jsonc"
|
||||
@@ -1,14 +0,0 @@
|
||||
; Only produce one embedding for the entire file.
|
||||
(document) @item
|
||||
|
||||
; Collapse arrays, except for the first object.
|
||||
(array
|
||||
"[" @keep
|
||||
.
|
||||
(object)? @keep
|
||||
"]" @keep) @collapse
|
||||
|
||||
; Collapse string values (but not keys).
|
||||
(pair value: (string
|
||||
"\"" @keep
|
||||
"\"" @keep) @collapse)
|
||||
@@ -1,21 +0,0 @@
|
||||
(comment) @comment
|
||||
|
||||
(string) @string
|
||||
|
||||
(pair
|
||||
key: (string) @property.json_key)
|
||||
|
||||
(number) @number
|
||||
|
||||
[
|
||||
(true)
|
||||
(false)
|
||||
(null)
|
||||
] @constant
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
@@ -1,2 +0,0 @@
|
||||
(array "]" @end) @indent
|
||||
(object "}" @end) @indent
|
||||
@@ -1,2 +0,0 @@
|
||||
(pair
|
||||
key: (string (string_content) @name)) @item
|
||||
@@ -1 +0,0 @@
|
||||
(string) @string
|
||||
@@ -1,4 +0,0 @@
|
||||
(pair value: (number) @redact)
|
||||
(pair value: (string) @redact)
|
||||
(array (number) @redact)
|
||||
(array (string) @redact)
|
||||