Merge branch 'paint-context' into cells
1349
Cargo.lock
generated
25
Cargo.toml
|
@ -109,6 +109,31 @@ tree-sitter = "0.20"
|
|||
unindent = { version = "0.1.7" }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "1b0321ee85701d5036c334a6f04761cdc672e64c" }
|
||||
tree-sitter-c = "0.20.1"
|
||||
tree-sitter-cpp = "0.20.0"
|
||||
tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" }
|
||||
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "4ba9dab6e2602960d95b2b625f3386c27e08084e" }
|
||||
tree-sitter-elm = "5.6.4"
|
||||
tree-sitter-embedded-template = "0.20.0"
|
||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||
tree-sitter-rust = "0.20.3"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-php = { git = "https://github.com/tree-sitter/tree-sitter-php", rev = "d43130fd1525301e9826f420c5393a4d169819fc" }
|
||||
tree-sitter-python = "0.20.2"
|
||||
tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-ruby = "0.20.0"
|
||||
tree-sitter-html = "0.19.0"
|
||||
tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"}
|
||||
tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"}
|
||||
tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"}
|
||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930"}
|
||||
tree-sitter-lua = "0.0.14"
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "49226023693107fba9a1191136a4f47f38cdca73" }
|
||||
async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
|
||||
|
|
5
assets/icons/file_icons/archive.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 7.63H8" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="2" y="2" width="10" height="3" rx="0.5" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M2.59375 5H11.4375L10.5581 11.5664C10.5248 11.8146 10.313 12 10.0625 12H3.93944C3.68812 12 3.47585 11.8134 3.44358 11.5642L2.59375 5Z" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 529 B |
6
assets/icons/file_icons/audio.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 11C5.46973 11 4.1268 11.1873 3.31522 11.3327C2.94367 11.3992 2.60079 11.0563 2.66733 10.6848C2.81266 9.8732 3 8.53027 3 7C3 5.8387 2.89211 4.78529 2.77656 3.99011C2.73589 3.71017 3.19546 3.51715 3.36119 3.7464C4.09612 4.76304 5.23301 6.23301 6.5 7.5C7.76699 8.76699 9.23696 9.90388 10.2536 10.6388C10.4828 10.8045 10.2898 11.2641 10.0099 11.2234C9.21472 11.1079 8.1613 11 7 11Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.3594 3.35938C12.3594 3.35938 12.0146 2.9209 11.5312 2.4375C11.0479 1.9541 10.6406 1.64062 10.6406 1.64062" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M11.3516 7.36803C11.3516 7.36803 10.7962 5.88996 9.48438 4.57812C8.17254 3.26629 6.64062 2.64155 6.64062 2.64155" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<rect x="2.72266" y="8.73828" width="3.58525" height="2.72899" rx="0.5" transform="rotate(45 2.72266 8.73828)" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
6
assets/icons/file_icons/book.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 10L12 10.8374C12 10.9431 11.9665 11.046 11.9044 11.1315L11.1498 12.1691C11.0557 12.2985 10.9054 12.375 10.7454 12.375L3.25461 12.375C3.09464 12.375 2.94433 12.2985 2.85024 12.1691L2.09563 11.1315C2.03348 11.046 2 10.9431 2 10.8374L2 2" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 12V10L7 11H12V12H2Z" fill="black"/>
|
||||
<path d="M5.63246 2.04415C6.44914 2.31638 7 3.08066 7 3.94152V10.7306C7 11.0924 6.62757 11.3345 6.29693 11.1875L2.79693 9.63197C2.61637 9.55172 2.5 9.37266 2.5 9.17506V1.69371C2.5 1.35243 2.83435 1.11145 3.15811 1.21937L5.63246 2.04415Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.5 2C7.67157 2 7 2.67157 7 3.5V12C7 11.1954 10.2366 11.0382 11.5017 11.0075C11.7778 11.0008 12 10.7761 12 10.5V2.5C12 2.22386 11.7761 2 11.5 2H8.5Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1 KiB |
4
assets/icons/file_icons/camera.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10.5C12 10.7761 11.7761 11 11.5 11H2.5C2.22386 11 2 10.7761 2 10.5V4.88C2 4.60386 2.22386 4.38 2.5 4.38H4.4342C4.61518 4.38 4.78204 4.2822 4.87046 4.12428L5.35681 3.25572C5.44524 3.0978 5.61209 3 5.79308 3H8.20692C8.38791 3 8.55476 3.0978 8.64319 3.25572L9.12954 4.12428C9.21796 4.2822 9.38482 4.38 9.5658 4.38H11.5C11.7761 4.38 12 4.60386 12 4.88V10.5Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.005 9C7.90246 9 8.63 8.27246 8.63 7.375C8.63 6.47754 7.90246 5.75 7.005 5.75C6.10754 5.75 5.38 6.47754 5.38 7.375C5.38 8.27246 6.10754 9 7.005 9Z" fill="black" fill-opacity="0.33" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 851 B |
3
assets/icons/file_icons/chevron_down.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.63281 5.66406L6.99344 8.89844L10.3672 5.66406" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 246 B |
3
assets/icons/file_icons/chevron_left.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.35938 3.63281L5.125 6.99344L8.35938 10.3672" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 244 B |
3
assets/icons/file_icons/chevron_right.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.64062 3.64062L8.89062 7.00125L5.64062 10.375" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 245 B |
3
assets/icons/file_icons/chevron_up.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.63281 8.36719L6.99344 5.13281L10.3672 8.36719" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 246 B |
4
assets/icons/file_icons/code.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.375 2C2.5 2 2.5 3.5 2.5 4.5C2.5 5.5 2 6.50106 1 7C2 7.50106 2.5 8.5 2.5 9.5C2.5 10.5 2.5 12 4.375 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.63281 2C11.5078 2 11.5078 3.5 11.5078 4.5C11.5078 5.5 12.0078 6.50106 13.0078 7C12.0078 7.50106 11.5078 8.5 11.5078 9.5C11.5078 10.5 11.5078 12 9.63281 12" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 553 B |
5
assets/icons/file_icons/database.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="7" cy="4" rx="5" ry="2" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M12 4V10C12 11.1046 9.76142 12 7 12C4.23858 12 2 11.1046 2 10V4" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M12 7C12 8.10457 9.76142 9 7 9C4.23858 9 2 8.10457 2 7" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 423 B |
4
assets/icons/file_icons/eslint.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5413 7.3125C12.6529 7.11913 12.6529 6.88088 12.5413 6.6875L10.0413 2.35738C9.92962 2.164 9.72329 2.04488 9.5 2.04488L4.5 2.04488C4.27671 2.04488 4.07038 2.164 3.95873 2.35738L1.45873 6.6875C1.34709 6.88088 1.34709 7.11913 1.45873 7.3125L3.95873 11.6426C4.07038 11.836 4.27671 11.9551 4.5 11.9551L9.5 11.9551C9.72329 11.9551 9.92962 11.836 10.0413 11.6426L12.5413 7.3125Z" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
|
||||
<path d="M6.75 4.14434C6.9047 4.05502 7.0953 4.05502 7.25 4.14434L9.34808 5.35566C9.50278 5.44498 9.59808 5.61004 9.59808 5.78868V8.21132C9.59808 8.38996 9.50278 8.55502 9.34808 8.64434L7.25 9.85566C7.0953 9.94498 6.9047 9.94498 6.75 9.85566L4.65192 8.64434C4.49722 8.55502 4.40192 8.38996 4.40192 8.21132L4.40192 5.78868C4.40192 5.61004 4.49722 5.44498 4.65192 5.35566L6.75 4.14434Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 949 B |
5
assets/icons/file_icons/file.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 4H10" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 7H12" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 10H8" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 381 B |
159
assets/icons/file_icons/file_types.json
Normal file
|
@ -0,0 +1,159 @@
|
|||
{
|
||||
"suffixes": {
|
||||
"aac": "audio",
|
||||
"bash": "terminal",
|
||||
"bmp": "image",
|
||||
"c": "code",
|
||||
"conf": "settings",
|
||||
"cpp": "code",
|
||||
"cc": "code",
|
||||
"css": "code",
|
||||
"doc": "document",
|
||||
"docx": "document",
|
||||
"eslintrc": "eslint",
|
||||
"eslintrc.js": "eslint",
|
||||
"eslintrc.json": "eslint",
|
||||
"flac": "audio",
|
||||
"fish": "terminal",
|
||||
"gitattributes": "vcs",
|
||||
"gitignore": "vcs",
|
||||
"gitmodules": "vcs",
|
||||
"gif": "image",
|
||||
"go": "code",
|
||||
"h": "code",
|
||||
"handlebars": "code",
|
||||
"hbs": "template",
|
||||
"htm": "template",
|
||||
"html": "template",
|
||||
"svelte": "template",
|
||||
"hpp": "code",
|
||||
"ico": "image",
|
||||
"ini": "settings",
|
||||
"java": "code",
|
||||
"jpeg": "image",
|
||||
"jpg": "image",
|
||||
"js": "code",
|
||||
"json": "storage",
|
||||
"lock": "lock",
|
||||
"log": "log",
|
||||
"md": "document",
|
||||
"mdx": "document",
|
||||
"mp3": "audio",
|
||||
"mp4": "video",
|
||||
"ods": "document",
|
||||
"odp": "document",
|
||||
"odt": "document",
|
||||
"ogg": "video",
|
||||
"pdf": "document",
|
||||
"php": "code",
|
||||
"png": "image",
|
||||
"ppt": "document",
|
||||
"pptx": "document",
|
||||
"prettierrc": "prettier",
|
||||
"prettierignore": "prettier",
|
||||
"ps1": "terminal",
|
||||
"psd": "image",
|
||||
"py": "code",
|
||||
"rb": "code",
|
||||
"rkt": "code",
|
||||
"rs": "rust",
|
||||
"rtf": "document",
|
||||
"scm": "code",
|
||||
"sh": "terminal",
|
||||
"bashrc": "terminal",
|
||||
"bash_profile": "terminal",
|
||||
"bash_aliases": "terminal",
|
||||
"bash_logout": "terminal",
|
||||
"profile": "terminal",
|
||||
"zshrc": "terminal",
|
||||
"zshenv": "terminal",
|
||||
"zsh_profile": "terminal",
|
||||
"zsh_aliases": "terminal",
|
||||
"zsh_histfile": "terminal",
|
||||
"zlogin": "terminal",
|
||||
"sql": "code",
|
||||
"svg": "image",
|
||||
"swift": "code",
|
||||
"tiff": "image",
|
||||
"toml": "toml",
|
||||
"ts": "typescript",
|
||||
"tsx": "code",
|
||||
"txt": "document",
|
||||
"wav": "audio",
|
||||
"webm": "video",
|
||||
"xls": "document",
|
||||
"xlsx": "document",
|
||||
"xml": "template",
|
||||
"yaml": "settings",
|
||||
"yml": "settings",
|
||||
"zsh": "terminal"
|
||||
},
|
||||
"types": {
|
||||
"audio": {
|
||||
"icon": "icons/file_icons/audio.svg"
|
||||
},
|
||||
"code": {
|
||||
"icon": "icons/file_icons/code.svg"
|
||||
},
|
||||
"collapsed_chevron": {
|
||||
"icon": "icons/file_icons/chevron_right.svg"
|
||||
},
|
||||
"collapsed_folder": {
|
||||
"icon": "icons/file_icons/folder.svg"
|
||||
},
|
||||
"default": {
|
||||
"icon": "icons/file_icons/file.svg"
|
||||
},
|
||||
"document": {
|
||||
"icon": "icons/file_icons/book.svg"
|
||||
},
|
||||
"eslint": {
|
||||
"icon": "icons/file_icons/eslint.svg"
|
||||
},
|
||||
"expanded_chevron": {
|
||||
"icon": "icons/file_icons/chevron_down.svg"
|
||||
},
|
||||
"expanded_folder": {
|
||||
"icon": "icons/file_icons/folder_open.svg"
|
||||
},
|
||||
"image": {
|
||||
"icon": "icons/file_icons/image.svg"
|
||||
},
|
||||
"lock": {
|
||||
"icon": "icons/file_icons/lock.svg"
|
||||
},
|
||||
"log": {
|
||||
"icon": "icons/file_icons/info.svg"
|
||||
},
|
||||
"prettier": {
|
||||
"icon": "icons/file_icons/prettier.svg"
|
||||
},
|
||||
"rust": {
|
||||
"icon": "icons/file_icons/rust.svg"
|
||||
},
|
||||
"settings": {
|
||||
"icon": "icons/file_icons/settings.svg"
|
||||
},
|
||||
"storage": {
|
||||
"icon": "icons/file_icons/database.svg"
|
||||
},
|
||||
"template": {
|
||||
"icon": "icons/file_icons/html.svg"
|
||||
},
|
||||
"terminal": {
|
||||
"icon": "icons/file_icons/terminal.svg"
|
||||
},
|
||||
"toml": {
|
||||
"icon": "icons/file_icons/toml.svg"
|
||||
},
|
||||
"typescript": {
|
||||
"icon": "icons/file_icons/typescript.svg"
|
||||
},
|
||||
"vcs": {
|
||||
"icon": "icons/file_icons/git.svg"
|
||||
},
|
||||
"video": {
|
||||
"icon": "icons/file_icons/video.svg"
|
||||
}
|
||||
}
|
||||
}
|
5
assets/icons/file_icons/folder.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 5.125C2 4.84886 2.22386 4.625 2.5 4.625H11.5C11.7761 4.625 12 4.84886 12 5.125V11.125C12 11.4011 11.7761 11.625 11.5 11.625H2.5C2.22386 11.625 2 11.4011 2 11.125V5.125Z" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
|
||||
<path d="M6.38197 2.375H2.5C2.22386 2.375 2 2.59886 2 2.875V4.375H8L7.27639 2.92779C7.107 2.589 6.76074 2.375 6.38197 2.375Z" fill="black"/>
|
||||
<path d="M2 8V4.375M2 4.375V2.875C2 2.59886 2.22386 2.375 2.5 2.375H6.38197C6.76074 2.375 7.107 2.589 7.27639 2.92779L8 4.375H2Z" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 687 B |
5
assets/icons/file_icons/folder_open.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 2.53125H2.21875V10.625L4.5 4.59375H7.96875L7 2.53125Z" fill="black"/>
|
||||
<path d="M4.47293 4.94363C4.54554 4.74743 4.73263 4.61719 4.94184 4.61719H12.8755C13.2237 4.61719 13.4653 4.9642 13.3445 5.29074L11.1208 11.2986C11.0482 11.4948 10.8611 11.625 10.6519 11.625H2.71821C2.37002 11.625 2.12844 11.278 2.2493 10.9514L4.47293 4.94363Z" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M8 4.4024L7.27505 2.93264C7.10664 2.59119 6.75894 2.375 6.37821 2.375H2.5C2.22386 2.375 2 2.59886 2 2.875V11.125C2 11.4011 2.22386 11.625 2.5 11.625H4.00781" stroke="black" stroke-width="1.25" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 745 B |
6
assets/icons/file_icons/git.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="4" cy="10" r="2" stroke="black" stroke-width="1.25"/>
|
||||
<circle cx="10" cy="4" r="2" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25"/>
|
||||
<line x1="3.625" y1="2.625" x2="3.625" y2="7.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10 6V6C10 8.20914 8.20914 10 6 10V10" stroke="black" stroke-width="1.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 463 B |
6
assets/icons/file_icons/hash.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<line x1="10.2795" y1="2.63847" x2="7.74785" y2="11.0142" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="6.26624" y1="2.99597" x2="3.7346" y2="11.3717" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="3.15982" y1="5.3799" x2="11.9098" y2="5.3799" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<line x1="2.0983" y1="8.62407" x2="10.8483" y2="8.62407" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 571 B |
5
assets/icons/file_icons/html.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.15732 3.17108L5.84268 10.8289" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M4 5L2 7L4 9" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 9L12 7L10 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 423 B |
7
assets/icons/file_icons/image.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 3C6.91421 3 7.25 2.66421 7.25 2.25C7.25 1.83579 6.91421 1.5 6.5 1.5C6.08579 1.5 5.75 1.83579 5.75 2.25C5.75 2.66421 6.08579 3 6.5 3Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8L9 5L12 8H6Z" fill="black" fill-opacity="0.33"/>
|
||||
<path d="M2 10L5 7L7.375 9.375" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8L7.5 6.5L9 5L10.5 6.5L12 8" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.375 2H2.5C2.22386 2 2 2.22386 2 2.5V11.5C2 11.7761 2.22386 12 2.5 12H7.35938M9.64062 2H11.5C11.7761 2 12 2.22386 12 2.5V11.5C12 11.7761 11.7761 12 11.5 12H10.125" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 866 B |
5
assets/icons/file_icons/info.svg
Normal file
After Width: | Height: | Size: 46 KiB |
6
assets/icons/file_icons/lock.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="5" width="8" height="7" rx="0.5" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M4 4C4 2.89543 4.89543 2 6 2H8C9.10457 2 10 2.89543 10 4V5H4V4Z" stroke="black" stroke-opacity="0.66" stroke-width="1.25"/>
|
||||
<circle cx="7" cy="8" r="1" fill="black"/>
|
||||
<path d="M7 8V9.375" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 445 B |
8
assets/icons/file_icons/notebook.svg
Normal file
|
@ -0,0 +1,8 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.03125 2.96875C2.03125 2.41647 2.47897 1.96875 3.03125 1.96875H5V12H3.03125C2.47897 12 2.03125 11.5523 2.03125 11V2.96875Z" fill="black" fill-opacity="0.33"/>
|
||||
<rect x="2" y="2" width="10" height="10" rx="0.5" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M9.5 5L7.5 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 7H7.5" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 9H7.5" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 2V13" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 823 B |
4
assets/icons/file_icons/package.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.62677 3.88472L6.99983 6.78517M1.62677 3.88472L1.63137 9.90006L7.00442 12.8005M1.62677 3.88472L4.31117 2.54211M6.99983 6.78517L7.00442 12.8005M6.99983 6.78517L9.68414 5.33084M7.00442 12.8005L12.373 9.89186L12.3684 3.87652M4.31117 2.54211L6.99556 1.1995L12.3684 3.87652M4.31117 2.54211L9.68414 5.33084M12.3684 3.87652L9.68414 5.33084" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.03125 12.5625V6.78125L1.5625 3.9375V9.75L7.03125 12.5625Z" fill="black" fill-opacity="0.33"/>
|
||||
</svg>
|
After Width: | Height: | Size: 638 B |
12
assets/icons/file_icons/prettier.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 2.86328H8.51563" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M11 2.86328L12 2.86328" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M9.64062 5.6263L12 5.6263" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M4.79688 5.6263L7.15625 5.6263" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 5.6263L2.35937 5.6263" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M7.15625 8.3737L12 8.3737" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 8.3737L4.64062 8.3737" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M2 11.1094H3.54687" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M5.97656 11.1094H8.35938" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10.8203 11.1094L12 11.1094" stroke="black" stroke-opacity="0.33" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
4
assets/icons/file_icons/rust.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.27935 9.98207C4.32063 9.4038 3.9204 8.89049 3.35998 8.80276L2.60081 8.68387C2.37979 8.64945 2.20167 8.48001 2.15225 8.25614L2.01378 7.63511C1.96382 7.41235 2.05233 7.1807 2.23696 7.05125L2.8631 6.61242C3.33337 6.28297 3.47456 5.6369 3.18621 5.13364L2.79467 4.45092C2.68118 4.25261 2.69801 4.00374 2.83757 3.82321L3.22314 3.32436C3.3627 3.14438 3.59621 3.06994 3.81071 3.13772L4.57531 3.37769C5.11944 3.54879 5.70048 3.26159 5.90683 2.71886L6.1811 1.99782C6.26255 1.78395 6.46345 1.64285 6.68772 1.6423L7.31007 1.64063C7.53434 1.64007 7.73579 1.78006 7.81834 1.99337L8.09965 2.72275C8.30821 3.26214 8.88655 3.54712 9.42903 3.37714L10.1632 3.14716C10.3772 3.07994 10.6096 3.15382 10.7492 3.3327L11.1374 3.83099" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.76988 10.5933C7.76988 10.6595 7.8236 10.7133 7.88988 10.7133H7.97588C8.32602 10.7133 8.60988 10.9971 8.60988 11.3472V11.3472C8.60988 11.6974 8.32602 11.9812 7.97588 11.9812H6.05587C5.70573 11.9812 5.42188 11.6974 5.42188 11.3472V11.3472C5.42188 10.9971 5.70573 10.7133 6.05587 10.7133H6.14188C6.20815 10.7133 6.26188 10.6595 6.26188 10.5933V6.66925C6.26188 6.60298 6.20815 6.54925 6.14188 6.54925H6.05588C5.70573 6.54925 5.42188 6.2654 5.42188 5.91525V5.91525C5.42188 5.5651 5.70573 5.28125 6.05588 5.28125H8.89988C10.0518 5.28125 11.8619 5.71487 11.8619 7.15185C11.8619 7.67078 11.7284 8.10362 11.4642 8.45348C11.1981 8.79765 10.8458 9.05637 10.4056 9.22931V9.22931C10.3782 9.24007 10.3673 9.27304 10.3829 9.29801L11.2163 10.6342C11.247 10.6834 11.3008 10.7133 11.3588 10.7133H11.7319C12.082 10.7133 12.3659 10.9971 12.3659 11.3472V11.3472C12.3659 11.6974 12.082 11.9812 11.7319 11.9812H10.5637C10.4955 11.9812 10.432 11.9465 10.3952 11.889L8.96523 9.65406C8.92847 9.59661 8.86496 9.56185 8.79676 9.56185H7.96988C7.85942 9.56185 7.76988 9.65139 7.76988 9.76185V10.5933ZM8.61188 6.54925C9.02963 6.54925 10.125 6.54925 10.2339 7.18785C10.2975 7.56123 10.1181 7.86557 9.88118 8.07715C9.64227 8.29046 9.20527 8.38985 8.58788 8.38985H7.86988C7.81465 8.38985 7.76988 8.34508 7.76988 8.28985V6.64925C7.76988 6.59402 7.81465 6.54925 7.86988 6.54925H8.61188Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
4
assets/icons/file_icons/settings.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.60081 8.94324L3.35998 9.06214C3.9204 9.14986 4.32063 9.66317 4.27935 10.2414L4.22342 11.0252C4.20713 11.2536 4.32877 11.4686 4.53024 11.568L5.09174 11.8446C5.29321 11.9441 5.53379 11.9068 5.69834 11.7519L6.26255 11.2186C6.67855 10.8253 7.32041 10.8253 7.7369 11.2186L8.3011 11.7519C8.46565 11.9074 8.70572 11.9441 8.90772 11.8446L9.47027 11.5674C9.67124 11.4686 9.79234 11.2541 9.77607 11.0264L9.72007 10.2414C9.67883 9.66317 10.079 9.14986 10.6394 9.06214L11.3986 8.94324C11.6197 8.90883 11.7978 8.73938 11.8477 8.51607L11.9862 7.89504C12.0362 7.67172 11.9477 7.44007 11.763 7.31117L11.1293 6.86731C10.6617 6.53959 10.5189 5.89966 10.8013 5.3969L11.1841 4.71586C11.2954 4.51754 11.277 4.26923 11.1374 4.09036L10.7492 3.59207C10.6096 3.41319 10.3772 3.33932 10.1632 3.40653L9.42903 3.63651C8.88655 3.80649 8.30821 3.52152 8.09965 2.98213L7.81834 2.25275C7.73579 2.03944 7.53434 1.89945 7.31007 1.9L6.68772 1.90167C6.46345 1.90222 6.26255 2.04333 6.1811 2.25719L5.90683 2.97824C5.70048 3.52097 5.11944 3.80816 4.57531 3.63706L3.81071 3.39709C3.59621 3.32932 3.3627 3.40375 3.22314 3.58374L2.83757 4.08258C2.69801 4.26312 2.68118 4.51199 2.79467 4.7103L3.18621 5.39302C3.47456 5.89628 3.33337 6.54235 2.8631 6.87179L2.23696 7.31062C2.05233 7.44007 1.96382 7.67173 2.01378 7.89448L2.15225 8.51552C2.20167 8.73938 2.37979 8.90883 2.60081 8.94324Z" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.14913 5.85093L8.14909 5.85089C7.51453 5.21637 6.48549 5.21637 5.85092 5.85089L5.85089 5.85092C5.21637 6.48549 5.21637 7.51453 5.85089 8.14909L5.85093 8.14913C6.48549 8.78362 7.51452 8.78362 8.14908 8.14913L8.14913 8.14908C8.78362 7.51452 8.78362 6.48549 8.14913 5.85093Z" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
5
assets/icons/file_icons/terminal.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.65625 2.5C1.65625 2.22386 1.88011 2 2.15625 2H11.8437C12.1199 2 12.3438 2.22386 12.3438 2.5V11.5C12.3438 11.7761 12.1199 12 11.8437 12H2.15625C1.88011 12 1.65625 11.7761 1.65625 11.5V2.5Z" stroke="black" stroke-width="1.25"/>
|
||||
<path d="M4.375 9L6.375 7L4.375 5" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7.625 9L9.90625 9" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 549 B |
5
assets/icons/file_icons/toml.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 5H9" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M7 5L7 10" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M4 2H2.5C2.22386 2 2 2.22386 2 2.5V11.5C2 11.7761 2.22386 12 2.5 12H4M10 2H11.5C11.7761 2 12 2.22386 12 2.5V11.5C12 11.7761 11.7761 12 11.5 12H10" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 498 B |
5
assets/icons/file_icons/typescript.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 4.375V2.5C12 2.22386 11.7761 2 11.5 2H2.5C2.22386 2 2 2.22386 2 2.5V11.5C2 11.7761 2.22386 12 2.5 12H3.375" stroke="black" stroke-opacity="0.66" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M10.6836 7.82805C10.7933 7.65392 10.9823 7.57377 11.174 7.57377C11.2904 7.57377 11.4019 7.59384 11.5092 7.62792C11.8324 7.73069 12.2148 7.63925 12.3392 7.32368L12.3773 7.22707C12.4703 6.99131 12.3823 6.71761 12.1522 6.61154C11.8328 6.46436 11.4984 6.375 11.1262 6.375C9.87708 6.375 8.91935 7.60671 9.4239 8.84869C9.54205 9.13951 9.74219 9.36166 9.9515 9.54337C10.1061 9.6776 10.2858 9.80516 10.4475 9.92002C10.4972 9.95529 10.5452 9.98936 10.5903 10.0221C11.0283 10.34 11.2526 10.5876 11.2526 10.9466C11.2526 11.1518 11.1622 11.3133 11.016 11.4128C10.8777 11.5071 10.7055 11.5357 10.5454 11.5222C10.3931 11.5093 10.2529 11.4717 10.1214 11.4196C9.81633 11.2989 9.45533 11.4015 9.33641 11.7073L9.2814 11.8487C9.19162 12.0796 9.2749 12.3463 9.49799 12.4539C10.0894 12.7391 10.7377 12.8279 11.3915 12.5872C12.0569 12.3423 12.595 11.7708 12.595 10.9068C12.595 10.1301 12.1336 9.69583 11.6966 9.36109C11.606 9.29163 11.5259 9.23292 11.4493 9.17682C11.3259 9.08638 11.1964 8.99109 11.0734 8.88536C10.8937 8.73082 10.7518 8.57274 10.6595 8.38613C10.5746 8.21464 10.5815 7.99013 10.6836 7.82805Z" fill="black"/>
|
||||
<path d="M6.98644 7.70936H7.69396C7.98162 7.70936 8.21481 7.47617 8.21481 7.18851V7.02346C8.21481 6.73581 7.98162 6.50261 7.69396 6.50261H4.96848C4.68082 6.50261 4.44763 6.73581 4.44763 7.02346V7.18851C4.44763 7.47617 4.68082 7.70936 4.96848 7.70936H5.676V12.102C5.676 12.3896 5.90919 12.6228 6.19685 12.6228H6.46559C6.75325 12.6228 6.98644 12.3896 6.98644 12.102V7.70936Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
4
assets/icons/file_icons/video.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.65625 2H11.8437C12.1199 2 12.3438 2.22386 12.3438 2.5V9.34375M12.3438 12H2.15625C1.88011 12 1.65625 11.7761 1.65625 11.5V4.65625" stroke="black" stroke-width="1.25" stroke-linecap="round"/>
|
||||
<path d="M9 7.01562L5.65624 9.3125L5.65624 4.6875L9 7.01562Z" fill="black" fill-opacity="0.33" stroke="black" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
|
@ -9,6 +9,7 @@
|
|||
"context": "Editor",
|
||||
"bindings": {
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"alt-cmd-b": "editor::GoToDefinitionSplit",
|
||||
"cmd-<": "editor::ScrollCursorCenter",
|
||||
"cmd-g": [
|
||||
"editor::SelectNext",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"cmd-up": "menu::SelectFirst",
|
||||
"cmd-down": "menu::SelectLast",
|
||||
"enter": "menu::Confirm",
|
||||
"cmd-enter": "menu::SecondaryConfirm",
|
||||
"escape": "menu::Cancel",
|
||||
"ctrl-c": "menu::Cancel",
|
||||
"cmd-{": "pane::ActivatePrevItem",
|
||||
|
@ -194,8 +195,8 @@
|
|||
{
|
||||
"context": "Editor && mode == auto_height",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::Newline",
|
||||
"cmd-alt-enter": "editor::NewlineBelow"
|
||||
"ctrl-enter": "editor::Newline",
|
||||
"ctrl-shift-enter": "editor::NewlineBelow"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -221,7 +222,8 @@
|
|||
"escape": "buffer_search::Dismiss",
|
||||
"tab": "buffer_search::FocusEditor",
|
||||
"enter": "search::SelectNextMatch",
|
||||
"shift-enter": "search::SelectPrevMatch"
|
||||
"shift-enter": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -242,6 +244,7 @@
|
|||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
"alt-enter": "search::SelectAllMatches",
|
||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||
"alt-cmd-w": "search::ToggleWholeWord",
|
||||
"alt-cmd-r": "search::ToggleRegex"
|
||||
|
@ -296,7 +299,9 @@
|
|||
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||
"f2": "editor::Rename",
|
||||
"f12": "editor::GoToDefinition",
|
||||
"alt-f12": "editor::GoToDefinitionSplit",
|
||||
"cmd-f12": "editor::GoToTypeDefinition",
|
||||
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-shift-f12": "editor::FindAllReferences",
|
||||
"ctrl-m": "editor::MoveToEnclosingBracket",
|
||||
"alt-cmd-[": "editor::Fold",
|
||||
|
@ -401,6 +406,7 @@
|
|||
"cmd-b": "workspace::ToggleLeftDock",
|
||||
"cmd-r": "workspace::ToggleRightDock",
|
||||
"cmd-j": "workspace::ToggleBottomDock",
|
||||
"alt-cmd-y": "workspace::CloseAllDocks",
|
||||
"cmd-shift-f": "workspace::NewSearch",
|
||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||
|
@ -441,8 +447,22 @@
|
|||
},
|
||||
{
|
||||
"bindings": {
|
||||
"cmd-k cmd-left": "workspace::ActivatePreviousPane",
|
||||
"cmd-k cmd-right": "workspace::ActivateNextPane"
|
||||
"cmd-k cmd-left": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Left"
|
||||
],
|
||||
"cmd-k cmd-right": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Right"
|
||||
],
|
||||
"cmd-k cmd-up": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Up"
|
||||
],
|
||||
"cmd-k cmd-down": [
|
||||
"workspace::ActivatePaneInDirection",
|
||||
"Down"
|
||||
]
|
||||
}
|
||||
},
|
||||
// Bindings from Atom
|
||||
|
@ -508,8 +528,11 @@
|
|||
"cmd-alt-c": "project_panel::CopyPath",
|
||||
"alt-cmd-shift-c": "project_panel::CopyRelativePath",
|
||||
"f2": "project_panel::Rename",
|
||||
"enter": "project_panel::Rename",
|
||||
"space": "project_panel::Open",
|
||||
"backspace": "project_panel::Delete",
|
||||
"alt-cmd-r": "project_panel::RevealInFinder"
|
||||
"alt-cmd-r": "project_panel::RevealInFinder",
|
||||
"alt-shift-f": "project_panel::NewSearchInDirectory"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -46,8 +46,9 @@
|
|||
"alt-f7": "editor::FindAllReferences",
|
||||
"cmd-alt-f7": "editor::FindAllReferences",
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"cmd-alt-b": "editor::GoToDefinition",
|
||||
"cmd-alt-b": "editor::GoToDefinitionSplit",
|
||||
"cmd-shift-b": "editor::GoToTypeDefinition",
|
||||
"cmd-alt-shift-b": "editor::GoToTypeDefinitionSplit",
|
||||
"alt-enter": "editor::ToggleCodeActions",
|
||||
"f2": "editor::GoToDiagnostic",
|
||||
"cmd-f2": "editor::GoToPrevDiagnostic",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"cmd-shift-a": "editor::SelectLargerSyntaxNode",
|
||||
"shift-f12": "editor::FindAllReferences",
|
||||
"alt-cmd-down": "editor::GoToDefinition",
|
||||
"ctrl-alt-cmd-down": "editor::GoToDefinitionSplit",
|
||||
"alt-shift-cmd-down": "editor::FindAllReferences",
|
||||
"ctrl-.": "editor::GoToHunk",
|
||||
"ctrl-,": "editor::GoToPrevHunk",
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
"cmd-l": "go_to_line::Toggle",
|
||||
"ctrl-shift-d": "editor::DuplicateLine",
|
||||
"cmd-b": "editor::GoToDefinition",
|
||||
"alt-cmd-b": "editor::GoToDefinition",
|
||||
"cmd-j": "editor::ScrollCursorCenter",
|
||||
"cmd-shift-l": "editor::SelectLine",
|
||||
"cmd-shift-t": "outline::Toggle",
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
{
|
||||
"context": "Editor && VimControl && !VimWaiting && !menu",
|
||||
"bindings": {
|
||||
"g": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "G"
|
||||
}
|
||||
],
|
||||
"i": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
|
@ -30,6 +24,8 @@
|
|||
"j": "vim::Down",
|
||||
"down": "vim::Down",
|
||||
"enter": "vim::NextLineStart",
|
||||
"tab": "vim::Tab",
|
||||
"shift-tab": "vim::Tab",
|
||||
"k": "vim::Up",
|
||||
"up": "vim::Up",
|
||||
"l": "vim::Right",
|
||||
|
@ -60,6 +56,8 @@
|
|||
"ignorePunctuation": true
|
||||
}
|
||||
],
|
||||
"n": "search::SelectNextMatch",
|
||||
"shift-n": "search::SelectPrevMatch",
|
||||
"%": "vim::Matching",
|
||||
"f": [
|
||||
"vim::PushOperator",
|
||||
|
@ -99,7 +97,39 @@
|
|||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl+[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"*": "vim::MoveToNext",
|
||||
"#": "vim::MoveToPrev",
|
||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||
// "g" commands
|
||||
"g g": "vim::StartOfDocument",
|
||||
"g h": "editor::Hover",
|
||||
"g t": "pane::ActivateNextItem",
|
||||
"g shift-t": "pane::ActivatePrevItem",
|
||||
"g d": "editor::GoToDefinition",
|
||||
"g shift-d": "editor::GoToTypeDefinition",
|
||||
"g .": "editor::ToggleCodeActions", // zed specific
|
||||
"g shift-a": "editor::FindAllReferences", // zed specific
|
||||
"g *": [
|
||||
"vim::MoveToNext",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
],
|
||||
"g #": [
|
||||
"vim::MoveToPrev",
|
||||
{
|
||||
"partialWord": true
|
||||
}
|
||||
],
|
||||
// z commands
|
||||
"z t": "editor::ScrollCursorTop",
|
||||
"z z": "editor::ScrollCursorCenter",
|
||||
"z b": "editor::ScrollCursorBottom",
|
||||
// Count support
|
||||
"1": [
|
||||
"vim::Number",
|
||||
1
|
||||
|
@ -135,7 +165,75 @@
|
|||
"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 g t": "pane::ActivateNextItem",
|
||||
"ctrl-w ctrl-g t": "pane::ActivateNextItem",
|
||||
"ctrl-w g shift-t": "pane::ActivatePrevItem",
|
||||
"ctrl-w ctrl-g shift-t": "pane::ActivatePrevItem",
|
||||
"ctrl-w w": "workspace::ActivateNextPane",
|
||||
"ctrl-w ctrl-w": "workspace::ActivateNextPane",
|
||||
"ctrl-w p": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w ctrl-p": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w ctrl-shift-w": "workspace::ActivatePreviousPane",
|
||||
"ctrl-w v": "pane::SplitLeft",
|
||||
"ctrl-w ctrl-v": "pane::SplitLeft",
|
||||
"ctrl-w s": "pane::SplitUp",
|
||||
"ctrl-w shift-s": "pane::SplitUp",
|
||||
"ctrl-w ctrl-s": "pane::SplitUp",
|
||||
"ctrl-w c": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-c": "pane::CloseAllItems",
|
||||
"ctrl-w q": "pane::CloseAllItems",
|
||||
"ctrl-w ctrl-q": "pane::CloseAllItems"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -156,12 +254,6 @@
|
|||
"vim::PushOperator",
|
||||
"Yank"
|
||||
],
|
||||
"z": [
|
||||
"vim::PushOperator",
|
||||
{
|
||||
"Namespace": "Z"
|
||||
}
|
||||
],
|
||||
"i": [
|
||||
"vim::SwitchMode",
|
||||
"Insert"
|
||||
|
@ -193,10 +285,18 @@
|
|||
"p": "vim::Paste",
|
||||
"u": "editor::Undo",
|
||||
"ctrl-r": "editor::Redo",
|
||||
"/": [
|
||||
"buffer_search::Deploy",
|
||||
"/": "vim::Search",
|
||||
"?": [
|
||||
"vim::Search",
|
||||
{
|
||||
"focus": true
|
||||
"backwards": true
|
||||
}
|
||||
],
|
||||
";": "vim::RepeatFind",
|
||||
",": [
|
||||
"vim::RepeatFind",
|
||||
{
|
||||
"backwards": true
|
||||
}
|
||||
],
|
||||
"ctrl-f": "vim::PageDown",
|
||||
|
@ -227,24 +327,11 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == g",
|
||||
"bindings": {
|
||||
"g": "vim::StartOfDocument",
|
||||
"h": "editor::Hover",
|
||||
"t": "pane::ActivateNextItem",
|
||||
"shift-t": "pane::ActivatePrevItem",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"d": "editor::GoToDefinition"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == c",
|
||||
"bindings": {
|
||||
"c": "vim::CurrentLine"
|
||||
"c": "vim::CurrentLine",
|
||||
"d": "editor::Rename" // zed specific
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -259,18 +346,6 @@
|
|||
"y": "vim::CurrentLine"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_operator == z",
|
||||
"bindings": {
|
||||
"t": "editor::ScrollCursorTop",
|
||||
"z": "editor::ScrollCursorCenter",
|
||||
"b": "editor::ScrollCursorBottom",
|
||||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && VimObject",
|
||||
"bindings": {
|
||||
|
@ -314,15 +389,16 @@
|
|||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"> >": "editor::Indent",
|
||||
"< <": "editor::Outdent"
|
||||
">": "editor::Indent",
|
||||
"<": "editor::Outdent"
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && vim_mode == insert",
|
||||
"bindings": {
|
||||
"escape": "vim::NormalBefore",
|
||||
"ctrl-c": "vim::NormalBefore"
|
||||
"ctrl-c": "vim::NormalBefore",
|
||||
"ctrl-[": "vim::NormalBefore"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -333,7 +409,18 @@
|
|||
"escape": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
],
|
||||
"ctrl+[": [
|
||||
"vim::SwitchMode",
|
||||
"Normal"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "BufferSearchBar > VimEnabled",
|
||||
"bindings": {
|
||||
"enter": "vim::SearchSubmit",
|
||||
"escape": "buffer_search::Dismiss"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -50,6 +50,13 @@
|
|||
// Whether to pop the completions menu while typing in an editor without
|
||||
// explicitly requesting it.
|
||||
"show_completions_on_input": true,
|
||||
// Whether to show wrap guides in the editor. Setting this to true will
|
||||
// show a guide at the 'preferred_line_length' value if softwrap is set to
|
||||
// 'preferred_line_length', and will show any additional guides as specified
|
||||
// by the 'wrap_guides' setting.
|
||||
"show_wrap_guides": true,
|
||||
// Character counts at which to show wrap guides in the editor.
|
||||
"wrap_guides": [],
|
||||
// Whether to use additional LSP queries to format (and amend) the code after
|
||||
// every "trigger" symbol input, defined by LSP server capabilities.
|
||||
"use_on_type_format": true,
|
||||
|
@ -66,6 +73,11 @@
|
|||
// 3. Draw all invisible symbols:
|
||||
// "all"
|
||||
"show_whitespaces": "selection",
|
||||
// Settings related to calls in Zed
|
||||
"calls": {
|
||||
// Join calls with the microphone muted by default
|
||||
"mute_on_join": true
|
||||
},
|
||||
// Scrollbar related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the editor.
|
||||
|
@ -97,12 +109,18 @@
|
|||
"show_other_hints": true
|
||||
},
|
||||
"project_panel": {
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Default width of the project panel.
|
||||
"default_width": 240,
|
||||
// Where to dock project panel. Can be 'left' or 'right'.
|
||||
"dock": "left",
|
||||
// Default width of the project panel.
|
||||
"default_width": 240
|
||||
// Whether to show file icons in the project panel.
|
||||
"file_icons": true,
|
||||
// Whether to show folder icons or chevrons for directories in the project panel.
|
||||
"folder_icons": true,
|
||||
// Whether to show the git status in the project panel.
|
||||
"git_status": true,
|
||||
// Amount of indentation for nested items.
|
||||
"indent_size": 20
|
||||
},
|
||||
"assistant": {
|
||||
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
|
||||
|
@ -128,6 +146,13 @@
|
|||
// 4. Save when idle for a certain amount of time:
|
||||
// "autosave": { "after_delay": {"milliseconds": 500} },
|
||||
"autosave": "off",
|
||||
// Settings related to the editor's tabs
|
||||
"tabs": {
|
||||
// Show git status colors in the editor tabs.
|
||||
"git_status": false,
|
||||
// Position of the close button on the editor tabs.
|
||||
"close_position": "right"
|
||||
},
|
||||
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||
// before saving it.
|
||||
"remove_trailing_whitespace_on_save": true,
|
||||
|
@ -189,9 +214,7 @@
|
|||
"copilot": {
|
||||
// The set of glob patterns for which copilot should be disabled
|
||||
// in any matching file.
|
||||
"disabled_globs": [
|
||||
".env"
|
||||
]
|
||||
"disabled_globs": [".env"]
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
|
@ -340,12 +363,6 @@
|
|||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust-analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
|
|
|
@ -298,12 +298,22 @@ impl AssistantPanel {
|
|||
}
|
||||
|
||||
fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
|
||||
let mut propagate_action = true;
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
if search_bar.update(cx, |search_bar, cx| search_bar.show(action.focus, true, cx)) {
|
||||
return;
|
||||
}
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
if search_bar.show(cx) {
|
||||
search_bar.search_suggested(cx);
|
||||
if action.focus {
|
||||
search_bar.select_query(cx);
|
||||
cx.focus_self();
|
||||
}
|
||||
propagate_action = false
|
||||
}
|
||||
});
|
||||
}
|
||||
if propagate_action {
|
||||
cx.propagate_action();
|
||||
}
|
||||
cx.propagate_action();
|
||||
}
|
||||
|
||||
fn handle_editor_cancel(&mut self, _: &editor::Cancel, cx: &mut ViewContext<Self>) {
|
||||
|
@ -320,13 +330,13 @@ impl AssistantPanel {
|
|||
|
||||
fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, cx));
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
|
||||
}
|
||||
}
|
||||
|
||||
fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, cx));
|
||||
search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ anyhow.workspace = true
|
|||
async-broadcast = "0.4"
|
||||
futures.workspace = true
|
||||
postage.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
pub mod call_settings;
|
||||
pub mod participant;
|
||||
pub mod room;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use call_settings::CallSettings;
|
||||
use client::{proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore};
|
||||
use collections::HashSet;
|
||||
use futures::{future::Shared, FutureExt};
|
||||
|
@ -19,6 +21,8 @@ pub use participant::ParticipantLocation;
|
|||
pub use room::Room;
|
||||
|
||||
pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut AppContext) {
|
||||
settings::register::<CallSettings>(cx);
|
||||
|
||||
let active_call = cx.add_model(|cx| ActiveCall::new(client, user_store, cx));
|
||||
cx.set_global(active_call);
|
||||
}
|
||||
|
@ -280,21 +284,6 @@ impl ActiveCall {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(&self, cx: &mut AppContext) {
|
||||
if let Some(room) = self.room().cloned() {
|
||||
let toggle_screen_sharing = room.update(cx, |room, cx| {
|
||||
if room.is_screen_sharing() {
|
||||
self.report_call_event("disable screen share", cx);
|
||||
Task::ready(room.unshare_screen(cx))
|
||||
} else {
|
||||
self.report_call_event("enable screen share", cx);
|
||||
room.share_screen(cx)
|
||||
}
|
||||
});
|
||||
toggle_screen_sharing.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_project(
|
||||
&mut self,
|
||||
project: ModelHandle<Project>,
|
||||
|
|
27
crates/call/src/call_settings.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::Setting;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct CallSettings {
|
||||
pub mute_on_join: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||
pub struct CallSettingsContent {
|
||||
pub mute_on_join: Option<bool>,
|
||||
}
|
||||
|
||||
impl Setting for CallSettings {
|
||||
const KEY: Option<&'static str> = Some("calls");
|
||||
|
||||
type FileContent = CallSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &gpui::AppContext,
|
||||
) -> anyhow::Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{
|
||||
call_settings::CallSettings,
|
||||
participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack},
|
||||
IncomingCall,
|
||||
};
|
||||
|
@ -153,8 +154,10 @@ impl Room {
|
|||
cx.spawn(|this, mut cx| async move {
|
||||
connect.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))
|
||||
.await?;
|
||||
if !cx.read(|cx| settings::get::<CallSettings>(cx).mute_on_join) {
|
||||
this.update(&mut cx, |this, cx| this.share_microphone(cx))
|
||||
.await?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
|
@ -656,7 +659,7 @@ impl Room {
|
|||
peer_id,
|
||||
projects: participant.projects,
|
||||
location,
|
||||
muted: false,
|
||||
muted: true,
|
||||
speaking: false,
|
||||
video_tracks: Default::default(),
|
||||
audio_tracks: Default::default(),
|
||||
|
@ -670,6 +673,10 @@ impl Room {
|
|||
live_kit.room.remote_video_tracks(&user.id.to_string());
|
||||
let audio_tracks =
|
||||
live_kit.room.remote_audio_tracks(&user.id.to_string());
|
||||
let publications = live_kit
|
||||
.room
|
||||
.remote_audio_track_publications(&user.id.to_string());
|
||||
|
||||
for track in video_tracks {
|
||||
this.remote_video_track_updated(
|
||||
RemoteVideoTrackUpdate::Subscribed(track),
|
||||
|
@ -677,9 +684,15 @@ impl Room {
|
|||
)
|
||||
.log_err();
|
||||
}
|
||||
for track in audio_tracks {
|
||||
|
||||
for (track, publication) in
|
||||
audio_tracks.iter().zip(publications.iter())
|
||||
{
|
||||
this.remote_audio_track_updated(
|
||||
RemoteAudioTrackUpdate::Subscribed(track),
|
||||
RemoteAudioTrackUpdate::Subscribed(
|
||||
track.clone(),
|
||||
publication.clone(),
|
||||
),
|
||||
cx,
|
||||
)
|
||||
.log_err();
|
||||
|
@ -819,8 +832,8 @@ impl Room {
|
|||
cx.notify();
|
||||
}
|
||||
RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => {
|
||||
let mut found = false;
|
||||
for participant in &mut self.remote_participants.values_mut() {
|
||||
let mut found = false;
|
||||
for track in participant.audio_tracks.values() {
|
||||
if track.sid() == track_id {
|
||||
found = true;
|
||||
|
@ -832,16 +845,20 @@ impl Room {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
RemoteAudioTrackUpdate::Subscribed(track) => {
|
||||
RemoteAudioTrackUpdate::Subscribed(track, publication) => {
|
||||
let user_id = track.publisher_id().parse()?;
|
||||
let track_id = track.sid().to_string();
|
||||
let participant = self
|
||||
.remote_participants
|
||||
.get_mut(&user_id)
|
||||
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
|
||||
|
||||
participant.audio_tracks.insert(track_id.clone(), track);
|
||||
participant.muted = publication.is_muted();
|
||||
|
||||
cx.emit(Event::RemoteAudioTracksChanged {
|
||||
participant_id: participant.peer_id,
|
||||
});
|
||||
|
@ -1053,7 +1070,7 @@ impl Room {
|
|||
self.live_kit
|
||||
.as_ref()
|
||||
.and_then(|live_kit| match &live_kit.microphone_track {
|
||||
LocalTrack::None => None,
|
||||
LocalTrack::None => Some(true),
|
||||
LocalTrack::Pending { muted, .. } => Some(*muted),
|
||||
LocalTrack::Published { muted, .. } => Some(*muted),
|
||||
})
|
||||
|
@ -1070,6 +1087,7 @@ impl Room {
|
|||
self.live_kit.as_ref().map(|live_kit| live_kit.deafened)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn share_microphone(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
if self.status.is_offline() {
|
||||
return Task::ready(Err(anyhow!("room is offline")));
|
||||
|
@ -1244,6 +1262,10 @@ impl Room {
|
|||
pub fn toggle_mute(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
|
||||
let should_mute = !self.is_muted();
|
||||
if let Some(live_kit) = self.live_kit.as_mut() {
|
||||
if matches!(live_kit.microphone_track, LocalTrack::None) {
|
||||
return Ok(self.share_microphone(cx));
|
||||
}
|
||||
|
||||
let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?;
|
||||
live_kit.muted_by_user = should_mute;
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ lazy_static! {
|
|||
struct ClickhouseEventRequestBody {
|
||||
token: &'static str,
|
||||
installation_id: Option<Arc<str>>,
|
||||
is_staff: Option<bool>,
|
||||
app_version: Option<Arc<str>>,
|
||||
os_name: &'static str,
|
||||
os_version: Option<Arc<str>>,
|
||||
|
@ -224,6 +225,7 @@ impl Telemetry {
|
|||
&ClickhouseEventRequestBody {
|
||||
token: ZED_SECRET_CLIENT_TOKEN,
|
||||
installation_id: state.installation_id.clone(),
|
||||
is_staff: state.is_staff.clone(),
|
||||
app_version: state.app_version.clone(),
|
||||
os_name: state.os_name,
|
||||
os_version: state.os_version.clone(),
|
||||
|
|
|
@ -7217,7 +7217,7 @@ async fn test_peers_following_each_other(
|
|||
|
||||
// Clients A and B follow each other in split panes
|
||||
workspace_a.update(cx_a, |workspace, cx| {
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
});
|
||||
workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
|
@ -7228,7 +7228,7 @@ async fn test_peers_following_each_other(
|
|||
.await
|
||||
.unwrap();
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
|
||||
});
|
||||
workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
|
@ -7455,7 +7455,7 @@ async fn test_auto_unfollowing(
|
|||
|
||||
// When client B activates a different pane, it continues following client A in the original pane.
|
||||
workspace_b.update(cx_b, |workspace, cx| {
|
||||
workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
|
||||
workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
|
||||
});
|
||||
assert_eq!(
|
||||
workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
|
||||
|
|
|
@ -15,8 +15,8 @@ use gpui::{
|
|||
geometry::{rect::RectF, vector::vec2f, PathBuilder},
|
||||
json::{self, ToJson},
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
|
||||
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use picker::PickerEvent;
|
||||
use project::{Project, RepositoryEntry};
|
||||
|
@ -652,10 +652,10 @@ impl CollabTitlebarItem {
|
|||
let is_muted = room.read(cx).is_muted();
|
||||
if is_muted {
|
||||
icon = "icons/radix/mic-mute.svg";
|
||||
tooltip = "Unmute microphone\nRight click for options";
|
||||
tooltip = "Unmute microphone";
|
||||
} else {
|
||||
icon = "icons/radix/mic.svg";
|
||||
tooltip = "Mute microphone\nRight click for options";
|
||||
tooltip = "Mute microphone";
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
|
@ -705,10 +705,10 @@ impl CollabTitlebarItem {
|
|||
let is_deafened = room.read(cx).is_deafened().unwrap_or(false);
|
||||
if is_deafened {
|
||||
icon = "icons/radix/speaker-off.svg";
|
||||
tooltip = "Unmute speakers\nRight click for options";
|
||||
tooltip = "Unmute speakers";
|
||||
} else {
|
||||
icon = "icons/radix/speaker-loud.svg";
|
||||
tooltip = "Mute speakers\nRight click for options";
|
||||
tooltip = "Mute speakers";
|
||||
}
|
||||
|
||||
let titlebar = &theme.titlebar;
|
||||
|
@ -1312,7 +1312,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
|
|||
_: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
_: &mut CollabTitlebarItem,
|
||||
_: &mut ViewContext<CollabTitlebarItem>,
|
||||
_: &mut PaintContext<CollabTitlebarItem>,
|
||||
) -> Self::PaintState {
|
||||
let mut path = PathBuilder::new();
|
||||
path.reset(bounds.lower_left());
|
||||
|
|
|
@ -18,13 +18,7 @@ use workspace::AppState;
|
|||
|
||||
actions!(
|
||||
collab,
|
||||
[
|
||||
ToggleScreenSharing,
|
||||
ToggleMute,
|
||||
ToggleDeafen,
|
||||
LeaveCall,
|
||||
ShareMicrophone
|
||||
]
|
||||
[ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
|
||||
);
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
|
@ -40,7 +34,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
cx.add_global_action(toggle_screen_sharing);
|
||||
cx.add_global_action(toggle_mute);
|
||||
cx.add_global_action(toggle_deafen);
|
||||
cx.add_global_action(share_microphone);
|
||||
}
|
||||
|
||||
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
||||
|
@ -71,10 +64,24 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
room.update(cx, Room::toggle_mute)
|
||||
.map(|task| task.detach_and_log_err(cx))
|
||||
.log_err();
|
||||
let call = ActiveCall::global(cx).read(cx);
|
||||
if let Some(room) = call.room().cloned() {
|
||||
let client = call.client();
|
||||
room.update(cx, |room, cx| {
|
||||
if room.is_muted() {
|
||||
ActiveCall::report_call_event_for_room("enable microphone", room.id(), &client, cx);
|
||||
} else {
|
||||
ActiveCall::report_call_event_for_room(
|
||||
"disable microphone",
|
||||
room.id(),
|
||||
&client,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
room.toggle_mute(cx)
|
||||
})
|
||||
.map(|task| task.detach_and_log_err(cx))
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,10 +92,3 @@ pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
|
|||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn share_microphone(_: &ShareMicrophone, cx: &mut AppContext) {
|
||||
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
|
||||
room.update(cx, Room::share_microphone)
|
||||
.detach_and_log_err(cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||
let user_store = self.user_store.read(cx);
|
||||
match user_store.contact_request_status(user) {
|
||||
|
|
|
@ -7,7 +7,7 @@ use gpui::{
|
|||
},
|
||||
json::ToJson,
|
||||
serde_json::{self, json},
|
||||
AnyElement, Axis, Element, LayoutContext, SceneBuilder, ViewContext,
|
||||
AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, ViewContext,
|
||||
};
|
||||
|
||||
use crate::CollabTitlebarItem;
|
||||
|
@ -54,7 +54,7 @@ impl Element<CollabTitlebarItem> for FacePile {
|
|||
visible_bounds: RectF,
|
||||
_layout: &mut Self::LayoutState,
|
||||
view: &mut CollabTitlebarItem,
|
||||
cx: &mut ViewContext<CollabTitlebarItem>,
|
||||
cx: &mut PaintContext<CollabTitlebarItem>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if !self.matches.is_empty() {
|
||||
let window_id = cx.window_id();
|
||||
let focused_view_id = self.focused_view_id;
|
||||
|
|
|
@ -7,7 +7,6 @@ use anyhow::Context;
|
|||
use gpui::AppContext;
|
||||
pub use indoc::indoc;
|
||||
pub use lazy_static;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
pub use smol;
|
||||
pub use sqlez;
|
||||
pub use sqlez_macros;
|
||||
|
@ -17,11 +16,9 @@ pub use util::paths::DB_DIR;
|
|||
use sqlez::domain::Migrator;
|
||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||
use sqlez_macros::sql;
|
||||
use std::fs::create_dir_all;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use util::channel::ReleaseChannel;
|
||||
use util::{async_iife, ResultExt};
|
||||
|
||||
|
@ -42,10 +39,8 @@ const DB_FILE_NAME: &'static str = "db.sqlite";
|
|||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
||||
pub static ref BACKUP_DB_PATH: RwLock<Option<PathBuf>> = RwLock::new(None);
|
||||
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
|
||||
}
|
||||
static DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Open or create a database at the given directory path.
|
||||
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
||||
|
@ -63,66 +58,14 @@ pub async fn open_db<M: Migrator + 'static>(
|
|||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
||||
|
||||
let connection = async_iife!({
|
||||
// Note: This still has a race condition where 1 set of migrations succeeds
|
||||
// (e.g. (Workspace, Editor)) and another fails (e.g. (Workspace, Terminal))
|
||||
// This will cause the first connection to have the database taken out
|
||||
// from under it. This *should* be fine though. The second dabatase failure will
|
||||
// cause errors in the log and so should be observed by developers while writing
|
||||
// soon-to-be good migrations. If user databases are corrupted, we toss them out
|
||||
// and try again from a blank. As long as running all migrations from start to end
|
||||
// on a blank database is ok, this race condition will never be triggered.
|
||||
//
|
||||
// Basically: Don't ever push invalid migrations to stable or everyone will have
|
||||
// a bad time.
|
||||
|
||||
// If no db folder, create one at 0-{channel}
|
||||
create_dir_all(&main_db_dir).context("Could not create db directory")?;
|
||||
smol::fs::create_dir_all(&main_db_dir)
|
||||
.await
|
||||
.context("Could not create db directory")
|
||||
.log_err()?;
|
||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
||||
|
||||
// Optimistically open databases in parallel
|
||||
if !DB_FILE_OPERATIONS.is_locked() {
|
||||
// Try building a connection
|
||||
if let Some(connection) = open_main_db(&db_path).await {
|
||||
return Ok(connection)
|
||||
};
|
||||
}
|
||||
|
||||
// Take a lock in the failure case so that we move the db once per process instead
|
||||
// of potentially multiple times from different threads. This shouldn't happen in the
|
||||
// normal path
|
||||
let _lock = DB_FILE_OPERATIONS.lock();
|
||||
if let Some(connection) = open_main_db(&db_path).await {
|
||||
return Ok(connection)
|
||||
};
|
||||
|
||||
let backup_timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime")
|
||||
.as_millis();
|
||||
|
||||
// If failed, move 0-{channel} to {current unix timestamp}-{channel}
|
||||
let backup_db_dir = db_dir.join(Path::new(&format!(
|
||||
"{}-{}",
|
||||
backup_timestamp,
|
||||
release_channel_name,
|
||||
)));
|
||||
|
||||
std::fs::rename(&main_db_dir, &backup_db_dir)
|
||||
.context("Failed clean up corrupted database, panicking.")?;
|
||||
|
||||
// Set a static ref with the failed timestamp and error so we can notify the user
|
||||
{
|
||||
let mut guard = BACKUP_DB_PATH.write();
|
||||
*guard = Some(backup_db_dir);
|
||||
}
|
||||
|
||||
// Create a new 0-{channel}
|
||||
create_dir_all(&main_db_dir).context("Should be able to create the database directory")?;
|
||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
||||
|
||||
// Try again
|
||||
open_main_db(&db_path).await.context("Could not newly created db")
|
||||
}).await.log_err();
|
||||
open_main_db(&db_path).await
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(connection) = connection {
|
||||
return connection;
|
||||
|
@ -249,13 +192,13 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, thread};
|
||||
use std::thread;
|
||||
|
||||
use sqlez::{connection::Connection, domain::Domain};
|
||||
use sqlez::domain::Domain;
|
||||
use sqlez_macros::sql;
|
||||
use tempdir::TempDir;
|
||||
|
||||
use crate::{open_db, DB_FILE_NAME};
|
||||
use crate::open_db;
|
||||
|
||||
// Test bad migration panics
|
||||
#[gpui::test]
|
||||
|
@ -321,31 +264,10 @@ mod tests {
|
|||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
||||
let mut corrupted_backup_dir = fs::read_dir(tempdir.path())
|
||||
.unwrap()
|
||||
.find(|entry| {
|
||||
!entry
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with("0")
|
||||
})
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.path();
|
||||
corrupted_backup_dir.push(DB_FILE_NAME);
|
||||
|
||||
let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy());
|
||||
assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()()
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
/// Test that DB exists but corrupted (causing recreate)
|
||||
#[gpui::test]
|
||||
#[gpui::test(iterations = 30)]
|
||||
async fn test_simultaneous_db_corruption() {
|
||||
enum CorruptedDB {}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ doctest = false
|
|||
|
||||
[features]
|
||||
test-support = [
|
||||
"rand",
|
||||
"copilot/test-support",
|
||||
"text/test-support",
|
||||
"language/test-support",
|
||||
|
@ -57,16 +56,16 @@ ordered-float.workspace = true
|
|||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false }
|
||||
rand = { workspace = true, optional = true }
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
tree-sitter-rust = { version = "*", optional = true }
|
||||
tree-sitter-html = { version = "*", optional = true }
|
||||
tree-sitter-javascript = { version = "*", optional = true }
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", optional = true }
|
||||
rand.workspace = true
|
||||
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-html = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
copilot = { path = "../copilot", features = ["test-support"] }
|
||||
|
@ -84,7 +83,6 @@ env_logger.workspace = true
|
|||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-rust = "0.20"
|
||||
tree-sitter-html = "0.19"
|
||||
tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
|
||||
tree-sitter-javascript = "0.20"
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
|
|
|
@ -74,6 +74,7 @@ pub use multi_buffer::{
|
|||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use scroll::{
|
||||
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
|
||||
};
|
||||
|
@ -226,6 +227,10 @@ actions!(
|
|||
MoveLineUp,
|
||||
MoveLineDown,
|
||||
JoinLines,
|
||||
SortLinesCaseSensitive,
|
||||
SortLinesCaseInsensitive,
|
||||
ReverseLines,
|
||||
ShuffleLines,
|
||||
Transpose,
|
||||
Cut,
|
||||
Copy,
|
||||
|
@ -271,7 +276,9 @@ actions!(
|
|||
SelectLargerSyntaxNode,
|
||||
SelectSmallerSyntaxNode,
|
||||
GoToDefinition,
|
||||
GoToDefinitionSplit,
|
||||
GoToTypeDefinition,
|
||||
GoToTypeDefinitionSplit,
|
||||
MoveToEnclosingBracket,
|
||||
UndoSelection,
|
||||
RedoSelection,
|
||||
|
@ -342,6 +349,10 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.add_action(Editor::outdent);
|
||||
cx.add_action(Editor::delete_line);
|
||||
cx.add_action(Editor::join_lines);
|
||||
cx.add_action(Editor::sort_lines_case_sensitive);
|
||||
cx.add_action(Editor::sort_lines_case_insensitive);
|
||||
cx.add_action(Editor::reverse_lines);
|
||||
cx.add_action(Editor::shuffle_lines);
|
||||
cx.add_action(Editor::delete_to_previous_word_start);
|
||||
cx.add_action(Editor::delete_to_previous_subword_start);
|
||||
cx.add_action(Editor::delete_to_next_word_end);
|
||||
|
@ -407,7 +418,9 @@ pub fn init(cx: &mut AppContext) {
|
|||
cx.add_action(Editor::go_to_hunk);
|
||||
cx.add_action(Editor::go_to_prev_hunk);
|
||||
cx.add_action(Editor::go_to_definition);
|
||||
cx.add_action(Editor::go_to_definition_split);
|
||||
cx.add_action(Editor::go_to_type_definition);
|
||||
cx.add_action(Editor::go_to_type_definition_split);
|
||||
cx.add_action(Editor::fold);
|
||||
cx.add_action(Editor::fold_at);
|
||||
cx.add_action(Editor::unfold_lines);
|
||||
|
@ -545,6 +558,7 @@ pub struct Editor {
|
|||
pending_rename: Option<RenameState>,
|
||||
searchable: bool,
|
||||
cursor_shape: CursorShape,
|
||||
collapse_matches: bool,
|
||||
workspace: Option<(WeakViewHandle<Workspace>, i64)>,
|
||||
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
|
||||
input_enabled: bool,
|
||||
|
@ -558,6 +572,7 @@ pub struct Editor {
|
|||
inlay_hint_cache: InlayHintCache,
|
||||
next_inlay_id: usize,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pixel_position_of_newest_cursor: Option<Vector2F>,
|
||||
}
|
||||
|
||||
pub struct EditorSnapshot {
|
||||
|
@ -1377,6 +1392,7 @@ impl Editor {
|
|||
searchable: true,
|
||||
override_text_style: None,
|
||||
cursor_shape: Default::default(),
|
||||
collapse_matches: false,
|
||||
workspace: None,
|
||||
keymap_context_layers: Default::default(),
|
||||
input_enabled: true,
|
||||
|
@ -1388,6 +1404,7 @@ impl Editor {
|
|||
copilot_state: Default::default(),
|
||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||
gutter_hovered: false,
|
||||
pixel_position_of_newest_cursor: None,
|
||||
_subscriptions: vec![
|
||||
cx.observe(&buffer, Self::on_buffer_changed),
|
||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||
|
@ -1516,6 +1533,17 @@ impl Editor {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
|
||||
self.collapse_matches = collapse_matches;
|
||||
}
|
||||
|
||||
fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
|
||||
if self.collapse_matches {
|
||||
return range.start..range.start;
|
||||
}
|
||||
range.clone()
|
||||
}
|
||||
|
||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
if self.display_map.read(cx).clip_at_line_ends != clip {
|
||||
self.display_map
|
||||
|
@ -2655,11 +2683,16 @@ impl Editor {
|
|||
InlayRefreshReason::RefreshRequested => (InvalidationStrategy::RefreshRequested, None),
|
||||
};
|
||||
|
||||
self.inlay_hint_cache.refresh_inlay_hints(
|
||||
if let Some(InlaySplice {
|
||||
to_remove,
|
||||
to_insert,
|
||||
}) = self.inlay_hint_cache.spawn_hint_refresh(
|
||||
self.excerpt_visible_offsets(required_languages.as_ref(), cx),
|
||||
invalidate_cache,
|
||||
cx,
|
||||
)
|
||||
) {
|
||||
self.splice_inlay_hints(to_remove, to_insert, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec<Inlay> {
|
||||
|
@ -4181,6 +4214,96 @@ impl Editor {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn sort_lines_case_sensitive(
|
||||
&mut self,
|
||||
_: &SortLinesCaseSensitive,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.manipulate_lines(cx, |text| text.sort())
|
||||
}
|
||||
|
||||
pub fn sort_lines_case_insensitive(
|
||||
&mut self,
|
||||
_: &SortLinesCaseInsensitive,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.manipulate_lines(cx, |text| text.sort_by_key(|line| line.to_lowercase()))
|
||||
}
|
||||
|
||||
pub fn reverse_lines(&mut self, _: &ReverseLines, cx: &mut ViewContext<Self>) {
|
||||
self.manipulate_lines(cx, |lines| lines.reverse())
|
||||
}
|
||||
|
||||
pub fn shuffle_lines(&mut self, _: &ShuffleLines, cx: &mut ViewContext<Self>) {
|
||||
self.manipulate_lines(cx, |lines| lines.shuffle(&mut thread_rng()))
|
||||
}
|
||||
|
||||
fn manipulate_lines<Fn>(&mut self, cx: &mut ViewContext<Self>, mut callback: Fn)
|
||||
where
|
||||
Fn: FnMut(&mut [&str]),
|
||||
{
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
|
||||
let selections = self.selections.all::<Point>(cx);
|
||||
let mut selections = selections.iter().peekable();
|
||||
let mut contiguous_row_selections = Vec::new();
|
||||
let mut new_selections = Vec::new();
|
||||
|
||||
while let Some(selection) = selections.next() {
|
||||
let (start_row, end_row) = consume_contiguous_rows(
|
||||
&mut contiguous_row_selections,
|
||||
selection,
|
||||
&display_map,
|
||||
&mut selections,
|
||||
);
|
||||
|
||||
let start_point = Point::new(start_row, 0);
|
||||
let end_point = Point::new(end_row - 1, buffer.line_len(end_row - 1));
|
||||
let text = buffer
|
||||
.text_for_range(start_point..end_point)
|
||||
.collect::<String>();
|
||||
let mut text = text.split("\n").collect_vec();
|
||||
|
||||
let text_len = text.len();
|
||||
callback(&mut text);
|
||||
|
||||
// This is a current limitation with selections.
|
||||
// If we wanted to support removing or adding lines, we'd need to fix the logic associated with selections.
|
||||
debug_assert!(
|
||||
text.len() == text_len,
|
||||
"callback should not change the number of lines"
|
||||
);
|
||||
|
||||
edits.push((start_point..end_point, text.join("\n")));
|
||||
let start_anchor = buffer.anchor_after(start_point);
|
||||
let end_anchor = buffer.anchor_before(end_point);
|
||||
|
||||
// Make selection and push
|
||||
new_selections.push(Selection {
|
||||
id: selection.id,
|
||||
start: start_anchor.to_offset(&buffer),
|
||||
end: end_anchor.to_offset(&buffer),
|
||||
goal: SelectionGoal::None,
|
||||
reversed: selection.reversed,
|
||||
});
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
});
|
||||
|
||||
this.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select(new_selections);
|
||||
});
|
||||
|
||||
this.request_autoscroll(Autoscroll::fit(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn duplicate_line(&mut self, _: &DuplicateLine, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = &display_map.buffer_snapshot;
|
||||
|
@ -5274,7 +5397,7 @@ impl Editor {
|
|||
|
||||
pub fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
let end = self.buffer.read(cx).read(cx).len();
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
self.change_selections(None, cx, |s| {
|
||||
s.select_ranges(vec![0..end]);
|
||||
});
|
||||
}
|
||||
|
@ -6185,14 +6308,31 @@ impl Editor {
|
|||
}
|
||||
|
||||
pub fn go_to_definition(&mut self, _: &GoToDefinition, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, cx);
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition(&mut self, _: &GoToTypeDefinition, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, cx);
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, cx);
|
||||
}
|
||||
|
||||
fn go_to_definition_of_kind(&mut self, kind: GotoDefinitionKind, cx: &mut ViewContext<Self>) {
|
||||
pub fn go_to_definition_split(&mut self, _: &GoToDefinitionSplit, cx: &mut ViewContext<Self>) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_type_definition_split(
|
||||
&mut self,
|
||||
_: &GoToTypeDefinitionSplit,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, cx);
|
||||
}
|
||||
|
||||
fn go_to_definition_of_kind(
|
||||
&mut self,
|
||||
kind: GotoDefinitionKind,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(cx) else { return };
|
||||
let buffer = self.buffer.read(cx);
|
||||
let head = self.selections.newest::<usize>(cx).head();
|
||||
|
@ -6211,7 +6351,7 @@ impl Editor {
|
|||
cx.spawn_labeled("Fetching Definition...", |editor, mut cx| async move {
|
||||
let definitions = definitions.await?;
|
||||
editor.update(&mut cx, |editor, cx| {
|
||||
editor.navigate_to_definitions(definitions, cx);
|
||||
editor.navigate_to_definitions(definitions, split, cx);
|
||||
})?;
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})
|
||||
|
@ -6221,6 +6361,7 @@ impl Editor {
|
|||
pub fn navigate_to_definitions(
|
||||
&mut self,
|
||||
mut definitions: Vec<LocationLink>,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let Some(workspace) = self.workspace(cx) else { return };
|
||||
|
@ -6234,18 +6375,24 @@ impl Editor {
|
|||
.to_offset(definition.target.buffer.read(cx));
|
||||
|
||||
if Some(&definition.target.buffer) == self.buffer.read(cx).as_singleton().as_ref() {
|
||||
let range = self.range_for_match(&range);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
} else {
|
||||
cx.window_context().defer(move |cx| {
|
||||
let target_editor: ViewHandle<Self> = workspace.update(cx, |workspace, cx| {
|
||||
workspace.open_project_item(definition.target.buffer.clone(), cx)
|
||||
if split {
|
||||
workspace.split_project_item(definition.target.buffer.clone(), cx)
|
||||
} else {
|
||||
workspace.open_project_item(definition.target.buffer.clone(), cx)
|
||||
}
|
||||
});
|
||||
target_editor.update(cx, |target_editor, cx| {
|
||||
// When selecting a definition in a different buffer, disable the nav history
|
||||
// to avoid creating a history entry at the previous cursor location.
|
||||
pane.update(cx, |pane, _| pane.disable_history());
|
||||
let range = target_editor.range_for_match(&range);
|
||||
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range]);
|
||||
});
|
||||
|
@ -6276,7 +6423,9 @@ impl Editor {
|
|||
.map(|definition| definition.target)
|
||||
.collect();
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx)
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace, locations, replica_id, title, split, cx,
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -6321,7 +6470,7 @@ impl Editor {
|
|||
})
|
||||
.unwrap();
|
||||
Self::open_locations_in_multibuffer(
|
||||
workspace, locations, replica_id, title, cx,
|
||||
workspace, locations, replica_id, title, false, cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
|
@ -6336,6 +6485,7 @@ impl Editor {
|
|||
mut locations: Vec<Location>,
|
||||
replica_id: ReplicaId,
|
||||
title: String,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
// If there are multiple definitions, open them in a multibuffer
|
||||
|
@ -6382,7 +6532,11 @@ impl Editor {
|
|||
cx,
|
||||
);
|
||||
});
|
||||
workspace.add_item(Box::new(editor), cx);
|
||||
if split {
|
||||
workspace.split_item(Box::new(editor), cx);
|
||||
} else {
|
||||
workspace.add_item(Box::new(editor), cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename(&mut self, _: &Rename, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||
|
@ -7031,6 +7185,20 @@ impl Editor {
|
|||
.text()
|
||||
}
|
||||
|
||||
pub fn wrap_guides(&self, cx: &AppContext) -> SmallVec<[(usize, bool); 2]> {
|
||||
let mut wrap_guides = smallvec::smallvec![];
|
||||
|
||||
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||
if settings.show_wrap_guides {
|
||||
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) {
|
||||
wrap_guides.push((soft_wrap as usize, true));
|
||||
}
|
||||
wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
|
||||
}
|
||||
|
||||
wrap_guides
|
||||
}
|
||||
|
||||
pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
|
||||
let settings = self.buffer.read(cx).settings_at(0, cx);
|
||||
let mode = self
|
||||
|
|
|
@ -2500,6 +2500,156 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// Test sort_lines_case_insensitive()
|
||||
cx.set_state(indoc! {"
|
||||
«z
|
||||
y
|
||||
x
|
||||
Z
|
||||
Y
|
||||
Xˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«x
|
||||
X
|
||||
y
|
||||
Y
|
||||
z
|
||||
Zˇ»
|
||||
"});
|
||||
|
||||
// Test reverse_lines()
|
||||
cx.set_state(indoc! {"
|
||||
«5
|
||||
4
|
||||
3
|
||||
2
|
||||
1ˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«1
|
||||
2
|
||||
3
|
||||
4
|
||||
5ˇ»
|
||||
"});
|
||||
|
||||
// Skip testing shuffle_line()
|
||||
|
||||
// From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
|
||||
// Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
|
||||
|
||||
// Don't manipulate when cursor is on single line, but expand the selection
|
||||
cx.set_state(indoc! {"
|
||||
ddˇdd
|
||||
ccc
|
||||
bb
|
||||
a
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«ddddˇ»
|
||||
ccc
|
||||
bb
|
||||
a
|
||||
"});
|
||||
|
||||
// Basic manipulate case
|
||||
// Start selection moves to column 0
|
||||
// End of selection shrinks to fit shorter line
|
||||
cx.set_state(indoc! {"
|
||||
dd«d
|
||||
ccc
|
||||
bb
|
||||
aaaaaˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«aaaaa
|
||||
bb
|
||||
ccc
|
||||
dddˇ»
|
||||
"});
|
||||
|
||||
// Manipulate case with newlines
|
||||
cx.set_state(indoc! {"
|
||||
dd«d
|
||||
ccc
|
||||
|
||||
bb
|
||||
aaaaa
|
||||
|
||||
ˇ»
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«
|
||||
|
||||
aaaaa
|
||||
bb
|
||||
ccc
|
||||
dddˇ»
|
||||
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
// Manipulate with multiple selections on a single line
|
||||
cx.set_state(indoc! {"
|
||||
dd«dd
|
||||
cˇ»c«c
|
||||
bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«aaaaa
|
||||
bb
|
||||
ccc
|
||||
ddddˇ»
|
||||
"});
|
||||
|
||||
// Manipulate with multiple disjoin selections
|
||||
cx.set_state(indoc! {"
|
||||
5«
|
||||
4
|
||||
3
|
||||
2
|
||||
1ˇ»
|
||||
|
||||
dd«dd
|
||||
ccc
|
||||
bb
|
||||
aaaˇ»aa
|
||||
"});
|
||||
cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
|
||||
cx.assert_editor_state(indoc! {"
|
||||
«1
|
||||
2
|
||||
3
|
||||
4
|
||||
5ˇ»
|
||||
|
||||
«aaaaa
|
||||
bb
|
||||
ccc
|
||||
ddddˇ»
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_duplicate_line(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
@ -3836,7 +3986,7 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
|
|||
autoclose_before: "})]>".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
));
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
|
@ -5383,7 +5533,7 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
|
|||
line_comment: Some("// ".into()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
));
|
||||
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
|
|
|
@ -32,7 +32,7 @@ use gpui::{
|
|||
platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
|
||||
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
||||
AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
|
||||
MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
|
||||
MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use json::json;
|
||||
|
@ -61,6 +61,7 @@ enum FoldMarkers {}
|
|||
struct SelectionLayout {
|
||||
head: DisplayPoint,
|
||||
cursor_shape: CursorShape,
|
||||
is_newest: bool,
|
||||
range: Range<DisplayPoint>,
|
||||
}
|
||||
|
||||
|
@ -70,6 +71,7 @@ impl SelectionLayout {
|
|||
line_mode: bool,
|
||||
cursor_shape: CursorShape,
|
||||
map: &DisplaySnapshot,
|
||||
is_newest: bool,
|
||||
) -> Self {
|
||||
if line_mode {
|
||||
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||
|
@ -77,6 +79,7 @@ impl SelectionLayout {
|
|||
Self {
|
||||
head: selection.head().to_display_point(map),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: point_range.start.to_display_point(map)
|
||||
..point_range.end.to_display_point(map),
|
||||
}
|
||||
|
@ -85,6 +88,7 @@ impl SelectionLayout {
|
|||
Self {
|
||||
head: selection.head(),
|
||||
cursor_shape,
|
||||
is_newest,
|
||||
range: selection.range(),
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +160,7 @@ impl EditorElement {
|
|||
event.position,
|
||||
event.cmd,
|
||||
event.shift,
|
||||
event.alt,
|
||||
position_map.as_ref(),
|
||||
text_bounds,
|
||||
cx,
|
||||
|
@ -308,6 +313,7 @@ impl EditorElement {
|
|||
position: Vector2F,
|
||||
cmd: bool,
|
||||
shift: bool,
|
||||
alt: bool,
|
||||
position_map: &PositionMap,
|
||||
text_bounds: RectF,
|
||||
cx: &mut EventContext<Editor>,
|
||||
|
@ -324,9 +330,9 @@ impl EditorElement {
|
|||
|
||||
if point == target_point {
|
||||
if shift {
|
||||
go_to_fetched_type_definition(editor, point, cx);
|
||||
go_to_fetched_type_definition(editor, point, alt, cx);
|
||||
} else {
|
||||
go_to_fetched_definition(editor, point, cx);
|
||||
go_to_fetched_definition(editor, point, alt, cx);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -535,6 +541,24 @@ impl EditorElement {
|
|||
corner_radius: 0.,
|
||||
});
|
||||
}
|
||||
|
||||
for (wrap_position, active) in layout.wrap_guides.iter() {
|
||||
let x = text_bounds.origin_x() + wrap_position + layout.position_map.em_width / 2.;
|
||||
let color = if *active {
|
||||
self.style.active_wrap_guide
|
||||
} else {
|
||||
self.style.wrap_guide
|
||||
};
|
||||
scene.push_quad(Quad {
|
||||
bounds: RectF::new(
|
||||
vec2f(x, text_bounds.origin_y()),
|
||||
vec2f(1., text_bounds.height()),
|
||||
),
|
||||
background: Some(color),
|
||||
border: Border::new(0., Color::transparent_black()),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -862,6 +886,12 @@ impl EditorElement {
|
|||
let x = cursor_character_x - scroll_left;
|
||||
let y = cursor_position.row() as f32 * layout.position_map.line_height
|
||||
- scroll_top;
|
||||
if selection.is_newest {
|
||||
editor.pixel_position_of_newest_cursor = Some(vec2f(
|
||||
bounds.origin_x() + x + block_width / 2.,
|
||||
bounds.origin_y() + y + layout.position_map.line_height / 2.,
|
||||
));
|
||||
}
|
||||
cursors.push(Cursor {
|
||||
color: selection_style.cursor,
|
||||
block_width,
|
||||
|
@ -1308,16 +1338,15 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
|
||||
let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
|
||||
fn column_pixels(&self, column: usize, cx: &ViewContext<Editor>) -> f32 {
|
||||
let style = &self.style;
|
||||
|
||||
cx.text_layout_cache()
|
||||
.layout_str(
|
||||
"1".repeat(digit_count).as_str(),
|
||||
" ".repeat(column).as_str(),
|
||||
style.text.font_size,
|
||||
&[(
|
||||
digit_count,
|
||||
column,
|
||||
RunStyle {
|
||||
font_id: style.text.font_id,
|
||||
color: Color::black(),
|
||||
|
@ -1328,6 +1357,11 @@ impl EditorElement {
|
|||
.width()
|
||||
}
|
||||
|
||||
fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
|
||||
let digit_count = (snapshot.max_buffer_row() as f32 + 1.).log10().floor() as usize + 1;
|
||||
self.column_pixels(digit_count, cx)
|
||||
}
|
||||
|
||||
//Folds contained in a hunk are ignored apart from shrinking visual size
|
||||
//If a fold contains any hunks then that fold line is marked as modified
|
||||
fn layout_git_gutters(
|
||||
|
@ -1975,6 +2009,7 @@ impl Element<Editor> for EditorElement {
|
|||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let style = self.style.clone();
|
||||
|
||||
let line_height = (style.text.font_size * style.line_height_scalar).round();
|
||||
|
||||
let gutter_padding;
|
||||
|
@ -2012,6 +2047,12 @@ impl Element<Editor> for EditorElement {
|
|||
}
|
||||
};
|
||||
|
||||
let wrap_guides = editor
|
||||
.wrap_guides(cx)
|
||||
.iter()
|
||||
.map(|(guide, active)| (self.column_pixels(*guide, cx), *active))
|
||||
.collect();
|
||||
|
||||
let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
|
||||
if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
|
||||
size.set_y(
|
||||
|
@ -2106,6 +2147,7 @@ impl Element<Editor> for EditorElement {
|
|||
line_mode,
|
||||
cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
false,
|
||||
));
|
||||
}
|
||||
selections.extend(remote_selections);
|
||||
|
@ -2115,6 +2157,7 @@ impl Element<Editor> for EditorElement {
|
|||
.selections
|
||||
.disjoint_in_range(start_anchor..end_anchor, cx);
|
||||
local_selections.extend(editor.selections.pending(cx));
|
||||
let newest = editor.selections.newest(cx);
|
||||
for selection in &local_selections {
|
||||
let is_empty = selection.start == selection.end;
|
||||
let selection_start = snapshot.prev_line_boundary(selection.start).1;
|
||||
|
@ -2137,11 +2180,13 @@ impl Element<Editor> for EditorElement {
|
|||
local_selections
|
||||
.into_iter()
|
||||
.map(|selection| {
|
||||
let is_newest = selection == newest;
|
||||
SelectionLayout::new(
|
||||
selection,
|
||||
editor.selections.line_mode,
|
||||
editor.cursor_shape,
|
||||
&snapshot.display_snapshot,
|
||||
is_newest,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
|
@ -2368,6 +2413,7 @@ impl Element<Editor> for EditorElement {
|
|||
snapshot,
|
||||
}),
|
||||
visible_display_row_range: start_row..end_row,
|
||||
wrap_guides,
|
||||
gutter_size,
|
||||
gutter_padding,
|
||||
text_size,
|
||||
|
@ -2409,7 +2455,7 @@ impl Element<Editor> for EditorElement {
|
|||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
editor: &mut Editor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
cx: &mut PaintContext<Editor>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
|
@ -2518,6 +2564,7 @@ pub struct LayoutState {
|
|||
gutter_margin: f32,
|
||||
text_size: Vector2F,
|
||||
mode: EditorMode,
|
||||
wrap_guides: SmallVec<[(f32, bool); 2]>,
|
||||
visible_display_row_range: Range<u32>,
|
||||
active_rows: BTreeMap<u32, bool>,
|
||||
highlighted_rows: Option<Range<u32>>,
|
||||
|
@ -3004,7 +3051,14 @@ mod tests {
|
|||
let mut scene = SceneBuilder::new(1.0);
|
||||
let bounds = RectF::new(Default::default(), size);
|
||||
editor.update(cx, |editor, cx| {
|
||||
element.paint(&mut scene, bounds, bounds, &mut state, editor, cx);
|
||||
element.paint(
|
||||
&mut scene,
|
||||
bounds,
|
||||
bounds,
|
||||
&mut state,
|
||||
editor,
|
||||
&mut PaintContext::new(cx),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ fn show_hover(
|
|||
|
||||
// Construct new hover popover from hover request
|
||||
let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
|
||||
if hover_result.contents.is_empty() {
|
||||
if hover_result.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -420,7 +420,7 @@ fn render_blocks(
|
|||
|
||||
RenderedInfo {
|
||||
theme_id,
|
||||
text,
|
||||
text: text.trim().to_string(),
|
||||
highlights,
|
||||
region_ranges,
|
||||
regions,
|
||||
|
@ -816,6 +816,118 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_empty_hovers_filtered(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Hover with keyboard has no delay
|
||||
cx.set_state(indoc! {"
|
||||
fˇn test() { println!(); }
|
||||
"});
|
||||
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
«fn» test() { println!(); }
|
||||
"});
|
||||
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Array(vec![
|
||||
lsp::MarkedString::String("regular text for hover to show".to_string()),
|
||||
lsp::MarkedString::String("".to_string()),
|
||||
lsp::MarkedString::LanguageString(lsp::LanguageString {
|
||||
language: "Rust".to_string(),
|
||||
value: "".to_string(),
|
||||
}),
|
||||
]),
|
||||
range: Some(symbol_range),
|
||||
}))
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||
cx.editor(|editor, _| {
|
||||
assert_eq!(
|
||||
editor.hover_state.info_popover.clone().unwrap().blocks,
|
||||
vec![HoverBlock {
|
||||
text: "regular text for hover to show".to_string(),
|
||||
kind: HoverBlockKind::Markdown,
|
||||
}],
|
||||
"No empty string hovers should be shown"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_line_ends_trimmed(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Hover with keyboard has no delay
|
||||
cx.set_state(indoc! {"
|
||||
fˇn test() { println!(); }
|
||||
"});
|
||||
cx.update_editor(|editor, cx| hover(editor, &Hover, cx));
|
||||
let symbol_range = cx.lsp_range(indoc! {"
|
||||
«fn» test() { println!(); }
|
||||
"});
|
||||
|
||||
let code_str = "\nlet hovered_point: Vector2F // size = 8, align = 0x4\n";
|
||||
let markdown_string = format!("\n```rust\n{code_str}```");
|
||||
|
||||
let closure_markdown_string = markdown_string.clone();
|
||||
cx.handle_request::<lsp::request::HoverRequest, _, _>(move |_, _, _| {
|
||||
let future_markdown_string = closure_markdown_string.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::Hover {
|
||||
contents: lsp::HoverContents::Markup(lsp::MarkupContent {
|
||||
kind: lsp::MarkupKind::Markdown,
|
||||
value: future_markdown_string,
|
||||
}),
|
||||
range: Some(symbol_range),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
|
||||
cx.condition(|editor, _| editor.hover_state.visible()).await;
|
||||
cx.editor(|editor, cx| {
|
||||
let blocks = editor.hover_state.info_popover.clone().unwrap().blocks;
|
||||
assert_eq!(
|
||||
blocks,
|
||||
vec![HoverBlock {
|
||||
text: markdown_string,
|
||||
kind: HoverBlockKind::Markdown,
|
||||
}],
|
||||
);
|
||||
|
||||
let style = editor.style(cx);
|
||||
let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
|
||||
assert_eq!(
|
||||
rendered.text,
|
||||
code_str.trim(),
|
||||
"Should not have extra line breaks at end of rendered hover"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
|
|
@ -195,20 +195,41 @@ impl InlayHintCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn refresh_inlay_hints(
|
||||
pub fn spawn_hint_refresh(
|
||||
&mut self,
|
||||
mut excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
|
||||
invalidate: InvalidationStrategy,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if !self.enabled || excerpts_to_query.is_empty() {
|
||||
return;
|
||||
) -> Option<InlaySplice> {
|
||||
if !self.enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let update_tasks = &mut self.update_tasks;
|
||||
let mut invalidated_hints = Vec::new();
|
||||
if invalidate.should_invalidate() {
|
||||
update_tasks
|
||||
.retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id));
|
||||
let mut changed = false;
|
||||
update_tasks.retain(|task_excerpt_id, _| {
|
||||
let retain = excerpts_to_query.contains_key(task_excerpt_id);
|
||||
changed |= !retain;
|
||||
retain
|
||||
});
|
||||
self.hints.retain(|cached_excerpt, cached_hints| {
|
||||
let retain = excerpts_to_query.contains_key(cached_excerpt);
|
||||
changed |= !retain;
|
||||
if !retain {
|
||||
invalidated_hints.extend(cached_hints.read().hints.iter().map(|&(id, _)| id));
|
||||
}
|
||||
retain
|
||||
});
|
||||
if changed {
|
||||
self.version += 1;
|
||||
}
|
||||
}
|
||||
if excerpts_to_query.is_empty() && invalidated_hints.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cache_version = self.version;
|
||||
excerpts_to_query.retain(|visible_excerpt_id, _| {
|
||||
match update_tasks.entry(*visible_excerpt_id) {
|
||||
|
@ -229,6 +250,15 @@ impl InlayHintCache {
|
|||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
if invalidated_hints.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(InlaySplice {
|
||||
to_remove: invalidated_hints,
|
||||
to_insert: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn new_allowed_hint_kinds_splice(
|
||||
|
@ -684,7 +714,7 @@ async fn fetch_and_update_hints(
|
|||
|
||||
if query.invalidate.should_invalidate() {
|
||||
let mut outdated_excerpt_caches = HashSet::default();
|
||||
for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() {
|
||||
for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
|
||||
let excerpt_hints = excerpt_hints.read();
|
||||
if excerpt_hints.buffer_id == query.buffer_id
|
||||
&& excerpt_id != &query.excerpt_id
|
||||
|
@ -1022,9 +1052,9 @@ mod tests {
|
|||
"Should get its first hints when opening the editor"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
editor.inlay_hint_cache().version,
|
||||
edits_made,
|
||||
"The editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
|
@ -1053,9 +1083,9 @@ mod tests {
|
|||
"Should not update hints while the work task is running"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
editor.inlay_hint_cache().version,
|
||||
edits_made,
|
||||
"Should not update the cache while the work task is running"
|
||||
);
|
||||
});
|
||||
|
@ -1077,9 +1107,9 @@ mod tests {
|
|||
"New hints should be queried after the work task is done"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
editor.inlay_hint_cache().version,
|
||||
edits_made,
|
||||
"Cache version should udpate once after the work task is done"
|
||||
);
|
||||
});
|
||||
|
@ -1194,9 +1224,9 @@ mod tests {
|
|||
"Should get its first hints when opening the editor"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 1,
|
||||
editor.inlay_hint_cache().version,
|
||||
1,
|
||||
"Rust editor update the cache version after every cache/view change"
|
||||
);
|
||||
});
|
||||
|
@ -1252,8 +1282,7 @@ mod tests {
|
|||
"Markdown editor should have a separate verison, repeating Rust editor rules"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 1);
|
||||
assert_eq!(editor.inlay_hint_cache().version, 1);
|
||||
});
|
||||
|
||||
rs_editor.update(cx, |editor, cx| {
|
||||
|
@ -1269,9 +1298,9 @@ mod tests {
|
|||
"Rust inlay cache should change after the edit"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 2,
|
||||
editor.inlay_hint_cache().version,
|
||||
2,
|
||||
"Every time hint cache changes, cache version should be incremented"
|
||||
);
|
||||
});
|
||||
|
@ -1283,8 +1312,7 @@ mod tests {
|
|||
"Markdown editor should not be affected by Rust editor changes"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 1);
|
||||
assert_eq!(editor.inlay_hint_cache().version, 1);
|
||||
});
|
||||
|
||||
md_editor.update(cx, |editor, cx| {
|
||||
|
@ -1300,8 +1328,7 @@ mod tests {
|
|||
"Rust editor should not be affected by Markdown editor changes"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 2);
|
||||
assert_eq!(editor.inlay_hint_cache().version, 2);
|
||||
});
|
||||
rs_editor.update(cx, |editor, cx| {
|
||||
let expected_layers = vec!["1".to_string()];
|
||||
|
@ -1311,8 +1338,7 @@ mod tests {
|
|||
"Markdown editor should also change independently"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 2);
|
||||
assert_eq!(editor.inlay_hint_cache().version, 2);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1433,9 +1459,9 @@ mod tests {
|
|||
vec!["other hint".to_string(), "type hint".to_string()],
|
||||
visible_hint_labels(editor, cx)
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
editor.inlay_hint_cache().version,
|
||||
edits_made,
|
||||
"Should not update cache version due to new loaded hints being the same"
|
||||
);
|
||||
});
|
||||
|
@ -1568,9 +1594,8 @@ mod tests {
|
|||
);
|
||||
assert!(cached_hint_labels(editor).is_empty());
|
||||
assert!(visible_hint_labels(editor, cx).is_empty());
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, edits_made,
|
||||
editor.inlay_hint_cache().version, edits_made,
|
||||
"The editor should not update the cache version after /refresh query without updates"
|
||||
);
|
||||
});
|
||||
|
@ -1641,8 +1666,7 @@ mod tests {
|
|||
vec!["parameter hint".to_string()],
|
||||
visible_hint_labels(editor, cx),
|
||||
);
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, edits_made);
|
||||
assert_eq!(editor.inlay_hint_cache().version, edits_made);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1720,9 +1744,8 @@ mod tests {
|
|||
"Should get hints from the last edit landed only"
|
||||
);
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 1,
|
||||
editor.inlay_hint_cache().version, 1,
|
||||
"Only one update should be registered in the cache after all cancellations"
|
||||
);
|
||||
});
|
||||
|
@ -1766,9 +1789,9 @@ mod tests {
|
|||
"Should get hints from the last edit landed only"
|
||||
);
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 2,
|
||||
editor.inlay_hint_cache().version,
|
||||
2,
|
||||
"Should update the cache version once more, for the new change"
|
||||
);
|
||||
});
|
||||
|
@ -1886,9 +1909,8 @@ mod tests {
|
|||
"Should have hints from both LSP requests made for a big file"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(
|
||||
inlay_cache.version, 2,
|
||||
editor.inlay_hint_cache().version, 2,
|
||||
"Both LSP queries should've bumped the cache version"
|
||||
);
|
||||
});
|
||||
|
@ -1918,8 +1940,7 @@ mod tests {
|
|||
assert_eq!(expected_layers, cached_hint_labels(editor),
|
||||
"Should have hints from the new LSP response after edit");
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added");
|
||||
assert_eq!(editor.inlay_hint_cache().version, 5, "Should update the cache for every LSP response with hints added");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2075,6 +2096,7 @@ mod tests {
|
|||
panic!("unexpected uri: {:?}", params.text_document.uri);
|
||||
};
|
||||
|
||||
// one hint per excerpt
|
||||
let positions = [
|
||||
lsp::Position::new(0, 2),
|
||||
lsp::Position::new(4, 2),
|
||||
|
@ -2138,8 +2160,7 @@ mod tests {
|
|||
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison");
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(), "Every visible excerpt hints should bump the verison");
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
|
@ -2169,8 +2190,8 @@ mod tests {
|
|||
assert_eq!(expected_layers, cached_hint_labels(editor),
|
||||
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 9);
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len(),
|
||||
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
|
@ -2179,7 +2200,7 @@ mod tests {
|
|||
});
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let last_scroll_update_version = editor.update(cx, |editor, cx| {
|
||||
let expected_layers = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
|
@ -2197,8 +2218,8 @@ mod tests {
|
|||
assert_eq!(expected_layers, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 12);
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_layers.len());
|
||||
expected_layers.len()
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
|
@ -2225,12 +2246,14 @@ mod tests {
|
|||
assert_eq!(expected_layers, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer");
|
||||
assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
|
||||
});
|
||||
|
||||
editor_edited.store(true, Ordering::Release);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
|
||||
});
|
||||
editor.handle_input("++++more text++++", cx);
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
|
@ -2240,19 +2263,253 @@ mod tests {
|
|||
"main hint(edited) #1".to_string(),
|
||||
"main hint(edited) #2".to_string(),
|
||||
"main hint(edited) #3".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
"other hint #3".to_string(),
|
||||
"other hint #4".to_string(),
|
||||
"other hint #5".to_string(),
|
||||
"main hint(edited) #4".to_string(),
|
||||
"main hint(edited) #5".to_string(),
|
||||
"other hint(edited) #0".to_string(),
|
||||
"other hint(edited) #1".to_string(),
|
||||
];
|
||||
assert_eq!(expected_layers, cached_hint_labels(editor),
|
||||
"After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \
|
||||
unedited (2nd) buffer should have the same hint");
|
||||
assert_eq!(
|
||||
expected_layers,
|
||||
cached_hint_labels(editor),
|
||||
"After multibuffer edit, editor gets scolled back to the last selection; \
|
||||
all hints should be invalidated and requeried for all of its visible excerpts"
|
||||
);
|
||||
assert_eq!(expected_layers, visible_hint_labels(editor, cx));
|
||||
let inlay_cache = editor.inlay_hint_cache();
|
||||
assert_eq!(inlay_cache.version, 16);
|
||||
assert_eq!(
|
||||
editor.inlay_hint_cache().version,
|
||||
last_scroll_update_version + expected_layers.len() + 1,
|
||||
"Due to every excerpt having one hint, cache should update per new excerpt received + 1 for outdated hints removal"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_excerpts_removed(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_type_hints: false,
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
})
|
||||
});
|
||||
|
||||
let mut language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
);
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities: lsp::ServerCapabilities {
|
||||
inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
let language = Arc::new(language);
|
||||
let fs = FakeFs::new(cx.background());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
|
||||
"other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::clone(&language))
|
||||
});
|
||||
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let worktree_id = workspace.update(cx, |workspace, cx| {
|
||||
workspace.project().read_with(cx, |project, cx| {
|
||||
project.worktrees(cx).next().unwrap().read(cx).id()
|
||||
})
|
||||
});
|
||||
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "main.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, "other.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
|
||||
let (buffer_1_excerpts, buffer_2_excerpts) = multibuffer.update(cx, |multibuffer, cx| {
|
||||
let buffer_1_excerpts = multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(2, 0),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
let buffer_2_excerpts = multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[ExcerptRange {
|
||||
context: Point::new(0, 1)..Point::new(2, 1),
|
||||
primary: None,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
(buffer_1_excerpts, buffer_2_excerpts)
|
||||
});
|
||||
|
||||
assert!(!buffer_1_excerpts.is_empty());
|
||||
assert!(!buffer_2_excerpts.is_empty());
|
||||
|
||||
deterministic.run_until_parked();
|
||||
cx.foreground().run_until_parked();
|
||||
let (_, editor) =
|
||||
cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
|
||||
let editor_edited = Arc::new(AtomicBool::new(false));
|
||||
let fake_server = fake_servers.next().await.unwrap();
|
||||
let closure_editor_edited = Arc::clone(&editor_edited);
|
||||
fake_server
|
||||
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
let task_editor_edited = Arc::clone(&closure_editor_edited);
|
||||
async move {
|
||||
let hint_text = if params.text_document.uri
|
||||
== lsp::Url::from_file_path("/a/main.rs").unwrap()
|
||||
{
|
||||
"main hint"
|
||||
} else if params.text_document.uri
|
||||
== lsp::Url::from_file_path("/a/other.rs").unwrap()
|
||||
{
|
||||
"other hint"
|
||||
} else {
|
||||
panic!("unexpected uri: {:?}", params.text_document.uri);
|
||||
};
|
||||
|
||||
let positions = [
|
||||
lsp::Position::new(0, 2),
|
||||
lsp::Position::new(4, 2),
|
||||
lsp::Position::new(22, 2),
|
||||
lsp::Position::new(44, 2),
|
||||
lsp::Position::new(56, 2),
|
||||
lsp::Position::new(67, 2),
|
||||
];
|
||||
let out_of_range_hint = lsp::InlayHint {
|
||||
position: lsp::Position::new(
|
||||
params.range.start.line + 99,
|
||||
params.range.start.character + 99,
|
||||
),
|
||||
label: lsp::InlayHintLabel::String(
|
||||
"out of excerpt range, should be ignored".to_string(),
|
||||
),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
};
|
||||
|
||||
let edited = task_editor_edited.load(Ordering::Acquire);
|
||||
Ok(Some(
|
||||
std::iter::once(out_of_range_hint)
|
||||
.chain(positions.into_iter().enumerate().map(|(i, position)| {
|
||||
lsp::InlayHint {
|
||||
position,
|
||||
label: lsp::InlayHintLabel::String(format!(
|
||||
"{hint_text}{} #{i}",
|
||||
if edited { "(edited)" } else { "" },
|
||||
)),
|
||||
kind: None,
|
||||
text_edits: None,
|
||||
tooltip: None,
|
||||
padding_left: None,
|
||||
padding_right: None,
|
||||
data: None,
|
||||
}
|
||||
}))
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec!["main hint #0".to_string(), "other hint #0".to_string()],
|
||||
cached_hint_labels(editor),
|
||||
"Cache should update for both excerpts despite hints display was disabled"
|
||||
);
|
||||
assert!(
|
||||
visible_hint_labels(editor, cx).is_empty(),
|
||||
"All hints are disabled and should not be shown despite being present in the cache"
|
||||
);
|
||||
assert_eq!(
|
||||
editor.inlay_hint_cache().version,
|
||||
2,
|
||||
"Cache should update once per excerpt query"
|
||||
);
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts(buffer_2_excerpts, cx)
|
||||
})
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
vec!["main hint #0".to_string()],
|
||||
cached_hint_labels(editor),
|
||||
"For the removed excerpt, should clean corresponding cached hints"
|
||||
);
|
||||
assert!(
|
||||
visible_hint_labels(editor, cx).is_empty(),
|
||||
"All hints are disabled and should not be shown despite being present in the cache"
|
||||
);
|
||||
assert_eq!(
|
||||
editor.inlay_hint_cache().version,
|
||||
3,
|
||||
"Excerpt removal should trigger cache update"
|
||||
);
|
||||
});
|
||||
|
||||
update_test_language_settings(cx, |settings| {
|
||||
settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
enabled: true,
|
||||
show_type_hints: true,
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
})
|
||||
});
|
||||
cx.foreground().run_until_parked();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let expected_hints = vec!["main hint #0".to_string()];
|
||||
assert_eq!(
|
||||
expected_hints,
|
||||
cached_hint_labels(editor),
|
||||
"Hint display settings change should not change the cache"
|
||||
);
|
||||
assert_eq!(
|
||||
expected_hints,
|
||||
visible_hint_labels(editor, cx),
|
||||
"Settings change should make cached hints visible"
|
||||
);
|
||||
assert_eq!(
|
||||
editor.inlay_hint_cache().version,
|
||||
4,
|
||||
"Settings change should trigger cache update"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ use anyhow::{Context, Result};
|
|||
use collections::HashSet;
|
||||
use futures::future::try_join_all;
|
||||
use gpui::{
|
||||
elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
|
||||
Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
||||
elements::*,
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use language::{
|
||||
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
|
||||
|
@ -750,6 +752,10 @@ impl Item for Editor {
|
|||
Some(Box::new(handle.clone()))
|
||||
}
|
||||
|
||||
fn pixel_position_of_cursor(&self) -> Option<Vector2F> {
|
||||
self.pixel_position_of_newest_cursor
|
||||
}
|
||||
|
||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
||||
ToolbarItemLocation::PrimaryLeft { flex: None }
|
||||
}
|
||||
|
@ -887,10 +893,20 @@ pub(crate) enum BufferSearchHighlights {}
|
|||
impl SearchableItem for Editor {
|
||||
type Match = Range<Anchor>;
|
||||
|
||||
fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
|
||||
fn to_search_event(
|
||||
&mut self,
|
||||
event: &Self::Event,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> Option<SearchEvent> {
|
||||
match event {
|
||||
Event::BufferEdited => Some(SearchEvent::MatchesInvalidated),
|
||||
Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged),
|
||||
Event::SelectionsChanged { .. } => {
|
||||
if self.selections.disjoint_anchors().len() == 1 {
|
||||
Some(SearchEvent::ActiveMatchChanged)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -936,46 +952,68 @@ impl SearchableItem for Editor {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.unfold_ranges([matches[index].clone()], false, true, cx);
|
||||
let range = self.range_for_match(&matches[index]);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([matches[index].clone()])
|
||||
});
|
||||
s.select_ranges([range]);
|
||||
})
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
|
||||
self.unfold_ranges(matches.clone(), false, false, cx);
|
||||
let mut ranges = Vec::new();
|
||||
for m in &matches {
|
||||
ranges.push(self.range_for_match(&m))
|
||||
}
|
||||
self.change_selections(None, cx, |s| s.select_ranges(ranges));
|
||||
}
|
||||
|
||||
fn match_index_for_direction(
|
||||
&mut self,
|
||||
matches: &Vec<Range<Anchor>>,
|
||||
mut current_index: usize,
|
||||
current_index: usize,
|
||||
direction: Direction,
|
||||
count: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> usize {
|
||||
let buffer = self.buffer().read(cx).snapshot(cx);
|
||||
let cursor = self.selections.newest_anchor().head();
|
||||
if matches[current_index].start.cmp(&cursor, &buffer).is_gt() {
|
||||
if direction == Direction::Prev {
|
||||
if current_index == 0 {
|
||||
current_index = matches.len() - 1;
|
||||
let current_index_position = if self.selections.disjoint_anchors().len() == 1 {
|
||||
self.selections.newest_anchor().head()
|
||||
} else {
|
||||
matches[current_index].start
|
||||
};
|
||||
|
||||
let mut count = count % matches.len();
|
||||
if count == 0 {
|
||||
return current_index;
|
||||
}
|
||||
match direction {
|
||||
Direction::Next => {
|
||||
if matches[current_index]
|
||||
.start
|
||||
.cmp(¤t_index_position, &buffer)
|
||||
.is_gt()
|
||||
{
|
||||
count = count - 1
|
||||
}
|
||||
|
||||
(current_index + count) % matches.len()
|
||||
}
|
||||
Direction::Prev => {
|
||||
if matches[current_index]
|
||||
.end
|
||||
.cmp(¤t_index_position, &buffer)
|
||||
.is_lt()
|
||||
{
|
||||
count = count - 1;
|
||||
}
|
||||
|
||||
if current_index >= count {
|
||||
current_index - count
|
||||
} else {
|
||||
current_index -= 1;
|
||||
matches.len() - (count - current_index)
|
||||
}
|
||||
}
|
||||
} else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() {
|
||||
if direction == Direction::Next {
|
||||
current_index = 0;
|
||||
}
|
||||
} else if direction == Direction::Prev {
|
||||
if current_index == 0 {
|
||||
current_index = matches.len() - 1;
|
||||
} else {
|
||||
current_index -= 1;
|
||||
}
|
||||
} else if direction == Direction::Next {
|
||||
if current_index == matches.len() - 1 {
|
||||
current_index = 0
|
||||
} else {
|
||||
current_index += 1;
|
||||
}
|
||||
};
|
||||
current_index
|
||||
}
|
||||
}
|
||||
|
||||
fn find_matches(
|
||||
|
|
|
@ -246,23 +246,26 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
|||
pub fn go_to_fetched_definition(
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, cx);
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, editor, point, split, cx);
|
||||
}
|
||||
|
||||
pub fn go_to_fetched_type_definition(
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, cx);
|
||||
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, editor, point, split, cx);
|
||||
}
|
||||
|
||||
fn go_to_fetched_definition_of_kind(
|
||||
kind: LinkDefinitionKind,
|
||||
editor: &mut Editor,
|
||||
point: DisplayPoint,
|
||||
split: bool,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let cached_definitions = editor.link_go_to_definition_state.definitions.clone();
|
||||
|
@ -275,7 +278,7 @@ fn go_to_fetched_definition_of_kind(
|
|||
cx.focus_self();
|
||||
}
|
||||
|
||||
editor.navigate_to_definitions(cached_definitions, cx);
|
||||
editor.navigate_to_definitions(cached_definitions, split, cx);
|
||||
} else {
|
||||
editor.select(
|
||||
SelectPhase::Begin {
|
||||
|
@ -403,7 +406,7 @@ mod tests {
|
|||
});
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_type_definition(editor, hover_point, cx);
|
||||
go_to_fetched_type_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
@ -614,7 +617,7 @@ mod tests {
|
|||
|
||||
// Cmd click with existing definition doesn't re-request and dismisses highlight
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_definition(editor, hover_point, cx);
|
||||
go_to_fetched_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
// Assert selection moved to to definition
|
||||
cx.lsp
|
||||
|
@ -655,7 +658,7 @@ mod tests {
|
|||
])))
|
||||
});
|
||||
cx.update_editor(|editor, cx| {
|
||||
go_to_fetched_definition(editor, hover_point, cx);
|
||||
go_to_fetched_definition(editor, hover_point, false, cx);
|
||||
});
|
||||
requests.next().await;
|
||||
cx.foreground().run_until_parked();
|
||||
|
|
|
@ -16,13 +16,13 @@ use crate::{
|
|||
Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingSelection {
|
||||
pub selection: Selection<Anchor>,
|
||||
pub mode: SelectMode,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelectionsCollection {
|
||||
display_map: ModelHandle<DisplayMap>,
|
||||
buffer: ModelHandle<MultiBuffer>,
|
||||
|
@ -138,7 +138,7 @@ impl SelectionsCollection {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// Returns all of the selections, adjusted to take into account the selection line_mode
|
||||
/// Returns all of the selections, adjusted to take into account the selection line_mode
|
||||
pub fn all_adjusted(&self, cx: &mut AppContext) -> Vec<Selection<Point>> {
|
||||
let mut selections = self.all::<Point>(cx);
|
||||
if self.line_mode {
|
||||
|
|
|
@ -60,6 +60,7 @@ pub(crate) struct FeedbackEditor {
|
|||
system_specs: SystemSpecs,
|
||||
editor: ViewHandle<Editor>,
|
||||
project: ModelHandle<Project>,
|
||||
pub allow_submission: bool,
|
||||
}
|
||||
|
||||
impl FeedbackEditor {
|
||||
|
@ -82,10 +83,15 @@ impl FeedbackEditor {
|
|||
system_specs: system_specs.clone(),
|
||||
editor,
|
||||
project,
|
||||
allow_submission: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submit(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
|
||||
if !self.allow_submission {
|
||||
return Task::ready(Ok(()));
|
||||
}
|
||||
|
||||
let feedback_text = self.editor.read(cx).text(cx);
|
||||
let feedback_char_count = feedback_text.chars().count();
|
||||
let feedback_text = feedback_text.trim().to_string();
|
||||
|
@ -122,19 +128,26 @@ impl FeedbackEditor {
|
|||
let answer = answer.recv().await;
|
||||
|
||||
if answer == Some(0) {
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
feedback_editor.set_allow_submission(false, cx);
|
||||
})
|
||||
.log_err();
|
||||
|
||||
match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
|
||||
Ok(_) => {
|
||||
this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed))
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Err(error) => {
|
||||
log::error!("{}", error);
|
||||
this.update(&mut cx, |_, cx| {
|
||||
this.update(&mut cx, |feedback_editor, cx| {
|
||||
cx.prompt(
|
||||
PromptLevel::Critical,
|
||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||
&["OK"],
|
||||
);
|
||||
feedback_editor.set_allow_submission(true, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -146,6 +159,11 @@ impl FeedbackEditor {
|
|||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn set_allow_submission(&mut self, allow_submission: bool, cx: &mut ViewContext<Self>) {
|
||||
self.allow_submission = allow_submission;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
async fn submit_feedback(
|
||||
feedback_text: &str,
|
||||
zed_client: Arc<Client>,
|
||||
|
@ -362,8 +380,13 @@ impl Item for FeedbackEditor {
|
|||
impl SearchableItem for FeedbackEditor {
|
||||
type Match = Range<Anchor>;
|
||||
|
||||
fn to_search_event(event: &Self::Event) -> Option<workspace::searchable::SearchEvent> {
|
||||
Editor::to_search_event(event)
|
||||
fn to_search_event(
|
||||
&mut self,
|
||||
event: &Self::Event,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<workspace::searchable::SearchEvent> {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.to_search_event(event, cx))
|
||||
}
|
||||
|
||||
fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
|
||||
|
@ -391,6 +414,11 @@ impl SearchableItem for FeedbackEditor {
|
|||
.update(cx, |editor, cx| editor.activate_match(index, matches, cx))
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
|
||||
self.editor
|
||||
.update(cx, |e, cx| e.select_matches(matches, cx))
|
||||
}
|
||||
|
||||
fn find_matches(
|
||||
&mut self,
|
||||
query: project::search::SearchQuery,
|
||||
|
|
|
@ -46,10 +46,28 @@ impl View for SubmitFeedbackButton {
|
|||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||
let theme = theme::current(cx).clone();
|
||||
let allow_submission = self
|
||||
.active_item
|
||||
.as_ref()
|
||||
.map_or(true, |i| i.read(cx).allow_submission);
|
||||
|
||||
enum SubmitFeedbackButton {}
|
||||
MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
|
||||
let style = theme.feedback.submit_button.style_for(state);
|
||||
Label::new("Submit as Markdown", style.text.clone())
|
||||
let text;
|
||||
let style = if allow_submission {
|
||||
text = "Submit as Markdown";
|
||||
theme.feedback.submit_button.style_for(state)
|
||||
} else {
|
||||
text = "Submitting...";
|
||||
theme
|
||||
.feedback
|
||||
.submit_button
|
||||
.disabled
|
||||
.as_ref()
|
||||
.unwrap_or(&theme.feedback.submit_button.default)
|
||||
};
|
||||
|
||||
Label::new(text, style.text.clone())
|
||||
.contained()
|
||||
.with_style(style.container)
|
||||
})
|
||||
|
|
|
@ -442,53 +442,71 @@ impl PickerDelegate for FileFinderDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
|
||||
fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<FileFinder>) {
|
||||
if let Some(m) = self.matches.get(self.selected_index()) {
|
||||
if let Some(workspace) = self.workspace.upgrade(cx) {
|
||||
let open_task = workspace.update(cx, |workspace, cx| match m {
|
||||
Match::History(history_match) => {
|
||||
let worktree_id = history_match.project.worktree_id;
|
||||
if workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some()
|
||||
{
|
||||
workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
let open_task = workspace.update(cx, move |workspace, cx| {
|
||||
let split_or_open = |workspace: &mut Workspace, project_path, cx| {
|
||||
if secondary {
|
||||
workspace.split_path(project_path, cx)
|
||||
} else {
|
||||
match history_match.absolute.as_ref() {
|
||||
Some(abs_path) => {
|
||||
workspace.open_abs_path(abs_path.to_path_buf(), false, cx)
|
||||
}
|
||||
None => workspace.open_path(
|
||||
workspace.open_path(project_path, None, true, cx)
|
||||
}
|
||||
};
|
||||
match m {
|
||||
Match::History(history_match) => {
|
||||
let worktree_id = history_match.project.worktree_id;
|
||||
if workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.is_some()
|
||||
{
|
||||
split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
match history_match.absolute.as_ref() {
|
||||
Some(abs_path) => {
|
||||
if secondary {
|
||||
workspace.split_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
workspace.open_abs_path(
|
||||
abs_path.to_path_buf(),
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
None => split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id,
|
||||
path: Arc::clone(&history_match.project.path),
|
||||
},
|
||||
cx,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
Match::Search(m) => split_or_open(
|
||||
workspace,
|
||||
ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
},
|
||||
cx,
|
||||
),
|
||||
}
|
||||
Match::Search(m) => workspace.open_path(
|
||||
ProjectPath {
|
||||
worktree_id: WorktreeId::from_usize(m.worktree_id),
|
||||
path: m.path.clone(),
|
||||
},
|
||||
None,
|
||||
true,
|
||||
cx,
|
||||
),
|
||||
});
|
||||
|
||||
let row = self
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use git2::{BranchType, ErrorCode};
|
||||
use git2::{BranchType, StatusShow};
|
||||
use parking_lot::Mutex;
|
||||
use rpc::proto;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
@ -10,6 +10,7 @@ use std::{
|
|||
os::unix::prelude::OsStrExt,
|
||||
path::{Component, Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::SystemTime,
|
||||
};
|
||||
use sum_tree::{MapSeekTarget, TreeMap};
|
||||
use util::ResultExt;
|
||||
|
@ -25,23 +26,30 @@ pub struct Branch {
|
|||
#[async_trait::async_trait]
|
||||
pub trait GitRepository: Send {
|
||||
fn reload_index(&self);
|
||||
|
||||
fn load_index_text(&self, relative_file_path: &Path) -> Option<String>;
|
||||
|
||||
fn branch_name(&self) -> Option<String>;
|
||||
|
||||
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>>;
|
||||
/// Get the statuses of all of the files in the index that start with the given
|
||||
/// path and have changes with resepect to the HEAD commit. This is fast because
|
||||
/// the index stores hashes of trees, so that unchanged directories can be skipped.
|
||||
fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus>;
|
||||
|
||||
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>>;
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn change_branch(&self, _: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn create_branch(&self, _: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
/// Get the status of a given file in the working directory with respect to
|
||||
/// the index. In the common case, when there are no changes, this only requires
|
||||
/// an index lookup. The index stores the mtime of each file when it was added,
|
||||
/// so there's no work to do if the mtime matches.
|
||||
fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus>;
|
||||
|
||||
/// Get the status of a given file in the working directory with respect to
|
||||
/// the HEAD commit. In the common case, when there are no changes, this only
|
||||
/// requires an index lookup and blob comparison between the index and the HEAD
|
||||
/// commit. The index stores the mtime of each file when it was added, so there's
|
||||
/// no need to consider the working directory file if the mtime matches.
|
||||
fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus>;
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>>;
|
||||
fn change_branch(&self, _: &str) -> Result<()>;
|
||||
fn create_branch(&self, _: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn GitRepository {
|
||||
|
@ -50,7 +58,6 @@ impl std::fmt::Debug for dyn GitRepository {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl GitRepository for LibGitRepository {
|
||||
fn reload_index(&self) {
|
||||
if let Ok(mut index) = self.index() {
|
||||
|
@ -88,39 +95,67 @@ impl GitRepository for LibGitRepository {
|
|||
Some(branch.to_string())
|
||||
}
|
||||
|
||||
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>> {
|
||||
let statuses = self.statuses(None).log_err()?;
|
||||
|
||||
fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus> {
|
||||
let mut map = TreeMap::default();
|
||||
|
||||
for status in statuses
|
||||
.iter()
|
||||
.filter(|status| !status.status().contains(git2::Status::IGNORED))
|
||||
{
|
||||
let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes())));
|
||||
let Some(status) = read_status(status.status()) else {
|
||||
continue
|
||||
};
|
||||
let mut options = git2::StatusOptions::new();
|
||||
options.pathspec(path_prefix);
|
||||
options.show(StatusShow::Index);
|
||||
|
||||
map.insert(path, status)
|
||||
}
|
||||
|
||||
Some(map)
|
||||
}
|
||||
|
||||
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
|
||||
let status = self.status_file(path);
|
||||
match status {
|
||||
Ok(status) => Ok(read_status(status)),
|
||||
Err(e) => {
|
||||
if e.code() == ErrorCode::NotFound {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(e.into())
|
||||
if let Some(statuses) = self.statuses(Some(&mut options)).log_err() {
|
||||
for status in statuses.iter() {
|
||||
let path = RepoPath(PathBuf::from(OsStr::from_bytes(status.path_bytes())));
|
||||
let status = status.status();
|
||||
if !status.contains(git2::Status::IGNORED) {
|
||||
if let Some(status) = read_status(status) {
|
||||
map.insert(path, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
fn unstaged_status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus> {
|
||||
// If the file has not changed since it was added to the index, then
|
||||
// there can't be any changes.
|
||||
if matches_index(self, path, mtime) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut options = git2::StatusOptions::new();
|
||||
options.pathspec(&path.0);
|
||||
options.disable_pathspec_match(true);
|
||||
options.include_untracked(true);
|
||||
options.recurse_untracked_dirs(true);
|
||||
options.include_unmodified(true);
|
||||
options.show(StatusShow::Workdir);
|
||||
|
||||
let statuses = self.statuses(Some(&mut options)).log_err()?;
|
||||
let status = statuses.get(0).and_then(|s| read_status(s.status()));
|
||||
status
|
||||
}
|
||||
|
||||
fn status(&self, path: &RepoPath, mtime: SystemTime) -> Option<GitFileStatus> {
|
||||
let mut options = git2::StatusOptions::new();
|
||||
options.pathspec(&path.0);
|
||||
options.disable_pathspec_match(true);
|
||||
options.include_untracked(true);
|
||||
options.recurse_untracked_dirs(true);
|
||||
options.include_unmodified(true);
|
||||
|
||||
// If the file has not changed since it was added to the index, then
|
||||
// there's no need to examine the working directory file: just compare
|
||||
// the blob in the index to the one in the HEAD commit.
|
||||
if matches_index(self, path, mtime) {
|
||||
options.show(StatusShow::Index);
|
||||
}
|
||||
|
||||
let statuses = self.statuses(Some(&mut options)).log_err()?;
|
||||
let status = statuses.get(0).and_then(|s| read_status(s.status()));
|
||||
status
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
let local_branches = self.branches(Some(BranchType::Local))?;
|
||||
let valid_branches = local_branches
|
||||
|
@ -163,6 +198,21 @@ impl GitRepository for LibGitRepository {
|
|||
}
|
||||
}
|
||||
|
||||
fn matches_index(repo: &LibGitRepository, path: &RepoPath, mtime: SystemTime) -> bool {
|
||||
if let Some(index) = repo.index().log_err() {
|
||||
if let Some(entry) = index.get_path(&path, 0) {
|
||||
if let Some(mtime) = mtime.duration_since(SystemTime::UNIX_EPOCH).log_err() {
|
||||
if entry.mtime.seconds() == mtime.as_secs() as i32
|
||||
&& entry.mtime.nanoseconds() == mtime.subsec_nanos()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn read_status(status: git2::Status) -> Option<GitFileStatus> {
|
||||
if status.contains(git2::Status::CONFLICTED) {
|
||||
Some(GitFileStatus::Conflict)
|
||||
|
@ -212,18 +262,40 @@ impl GitRepository for FakeGitRepository {
|
|||
state.branch_name.clone()
|
||||
}
|
||||
|
||||
fn statuses(&self) -> Option<TreeMap<RepoPath, GitFileStatus>> {
|
||||
let state = self.state.lock();
|
||||
fn staged_statuses(&self, path_prefix: &Path) -> TreeMap<RepoPath, GitFileStatus> {
|
||||
let mut map = TreeMap::default();
|
||||
let state = self.state.lock();
|
||||
for (repo_path, status) in state.worktree_statuses.iter() {
|
||||
map.insert(repo_path.to_owned(), status.to_owned());
|
||||
if repo_path.0.starts_with(path_prefix) {
|
||||
map.insert(repo_path.to_owned(), status.to_owned());
|
||||
}
|
||||
}
|
||||
Some(map)
|
||||
map
|
||||
}
|
||||
|
||||
fn status(&self, path: &RepoPath) -> Result<Option<GitFileStatus>> {
|
||||
fn unstaged_status(&self, _path: &RepoPath, _mtime: SystemTime) -> Option<GitFileStatus> {
|
||||
None
|
||||
}
|
||||
|
||||
fn status(&self, path: &RepoPath, _mtime: SystemTime) -> Option<GitFileStatus> {
|
||||
let state = self.state.lock();
|
||||
Ok(state.worktree_statuses.get(path).cloned())
|
||||
state.worktree_statuses.get(path).cloned()
|
||||
}
|
||||
|
||||
fn branches(&self) -> Result<Vec<Branch>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn change_branch(&self, name: &str) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
state.branch_name = Some(name.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_branch(&self, name: &str) -> Result<()> {
|
||||
let mut state = self.state.lock();
|
||||
state.branch_name = Some(name.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ use window_input_handler::WindowInputHandler;
|
|||
use crate::{
|
||||
elements::{AnyElement, AnyRootElement, RootElement},
|
||||
executor::{self, Task},
|
||||
fonts::TextStyle,
|
||||
json,
|
||||
keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
|
||||
platform::{
|
||||
|
@ -1073,7 +1074,7 @@ impl AppContext {
|
|||
|
||||
pub fn is_action_available(&self, action: &dyn Action) -> bool {
|
||||
let mut available_in_window = false;
|
||||
let action_type = action.as_any().type_id();
|
||||
let action_id = action.id();
|
||||
if let Some(window_id) = self.platform.main_window_id() {
|
||||
available_in_window = self
|
||||
.read_window(window_id, |cx| {
|
||||
|
@ -1083,7 +1084,7 @@ impl AppContext {
|
|||
cx.views_metadata.get(&(window_id, view_id))
|
||||
{
|
||||
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action_type) {
|
||||
if actions.contains_key(&action_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1094,7 +1095,7 @@ impl AppContext {
|
|||
})
|
||||
.unwrap_or(false);
|
||||
}
|
||||
available_in_window || self.global_actions.contains_key(&action_type)
|
||||
available_in_window || self.global_actions.contains_key(&action_id)
|
||||
}
|
||||
|
||||
fn actions_mut(
|
||||
|
@ -3363,6 +3364,7 @@ pub struct LayoutContext<'a, 'b, 'c, V: View> {
|
|||
view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
new_parents: &'c mut HashMap<usize, usize>,
|
||||
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
|
||||
text_style_stack: Vec<Arc<TextStyle>>,
|
||||
pub refreshing: bool,
|
||||
}
|
||||
|
||||
|
@ -3377,6 +3379,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||
view_context,
|
||||
new_parents,
|
||||
views_to_notify_if_ancestors_change,
|
||||
text_style_stack: Vec::new(),
|
||||
refreshing,
|
||||
}
|
||||
}
|
||||
|
@ -3399,7 +3402,7 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
if actions.contains_key(&action.as_any().type_id()) {
|
||||
if actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(i);
|
||||
}
|
||||
}
|
||||
|
@ -3407,22 +3410,18 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||
}
|
||||
}
|
||||
|
||||
if self.global_actions.contains_key(&action.as_any().type_id()) {
|
||||
if self.global_actions.contains_key(&action.id()) {
|
||||
handler_depth = Some(contexts.len())
|
||||
}
|
||||
|
||||
let action_contexts = if let Some(depth) = handler_depth {
|
||||
&contexts[depth..]
|
||||
} else {
|
||||
&contexts
|
||||
};
|
||||
|
||||
self.keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.find_map(|b| {
|
||||
let highest_handler = handler_depth?;
|
||||
if action.eq(b.action())
|
||||
&& (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..]))
|
||||
{
|
||||
Some(b.keystrokes().into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.keystrokes_for_action(action, action_contexts)
|
||||
}
|
||||
|
||||
fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
|
||||
|
@ -3432,6 +3431,24 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||
.or_default()
|
||||
.push(self_view_id);
|
||||
}
|
||||
|
||||
pub fn text_style(&self) -> Arc<TextStyle> {
|
||||
self.text_style_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(Default::default())
|
||||
}
|
||||
|
||||
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
|
||||
where
|
||||
S: Into<Arc<TextStyle>>,
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
{
|
||||
self.text_style_stack.push(style.into());
|
||||
let result = f(self);
|
||||
self.text_style_stack.pop();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V: View> Deref for LayoutContext<'a, 'b, 'c, V> {
|
||||
|
@ -3468,6 +3485,72 @@ impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct PaintContext<'a, 'b, 'c, V: View> {
|
||||
view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
text_style_stack: Vec<Arc<TextStyle>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V: View> PaintContext<'a, 'b, 'c, V> {
|
||||
pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
|
||||
Self {
|
||||
view_context,
|
||||
text_style_stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_style(&self) -> Arc<TextStyle> {
|
||||
self.text_style_stack
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(Default::default())
|
||||
}
|
||||
|
||||
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
|
||||
where
|
||||
S: Into<Arc<TextStyle>>,
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
{
|
||||
self.text_style_stack.push(style.into());
|
||||
let result = f(self);
|
||||
self.text_style_stack.pop();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, V: View> Deref for PaintContext<'a, 'b, 'c, V> {
|
||||
type Target = ViewContext<'a, 'b, V>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> DerefMut for PaintContext<'_, '_, '_, V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view_context
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> BorrowAppContext for PaintContext<'_, '_, '_, V> {
|
||||
fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
|
||||
BorrowAppContext::read_with(&*self.view_context, f)
|
||||
}
|
||||
|
||||
fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
|
||||
BorrowAppContext::update(&mut *self.view_context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
|
||||
fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
|
||||
BorrowWindowContext::read_with(&*self.view_context, window_id, f)
|
||||
}
|
||||
|
||||
fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
|
||||
BorrowWindowContext::update(&mut *self.view_context, window_id, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventContext<'a, 'b, 'c, V: View> {
|
||||
view_context: &'c mut ViewContext<'a, 'b, V>,
|
||||
pub(crate) handled: bool,
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
|||
text_layout::TextLayoutCache,
|
||||
util::post_inc,
|
||||
Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
|
||||
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, NoAction, SceneBuilder,
|
||||
Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, PaintContext, SceneBuilder,
|
||||
Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
|
||||
};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
@ -363,17 +363,13 @@ impl<'a> WindowContext<'a> {
|
|||
) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
|
||||
let window_id = self.window_id;
|
||||
let mut contexts = Vec::new();
|
||||
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
|
||||
let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
|
||||
for (depth, view_id) in self.ancestors(view_id).enumerate() {
|
||||
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||
contexts.push(view_metadata.keymap_context.clone());
|
||||
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||
handler_depths_by_action_type.extend(
|
||||
actions
|
||||
.keys()
|
||||
.copied()
|
||||
.map(|action_type| (action_type, depth)),
|
||||
);
|
||||
handler_depths_by_action_id
|
||||
.extend(actions.keys().copied().map(|action_id| (action_id, depth)));
|
||||
}
|
||||
} else {
|
||||
log::error!(
|
||||
|
@ -383,21 +379,21 @@ impl<'a> WindowContext<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
handler_depths_by_action_type.extend(
|
||||
handler_depths_by_action_id.extend(
|
||||
self.global_actions
|
||||
.keys()
|
||||
.copied()
|
||||
.map(|action_type| (action_type, contexts.len())),
|
||||
.map(|action_id| (action_id, contexts.len())),
|
||||
);
|
||||
|
||||
self.action_deserializers
|
||||
.iter()
|
||||
.filter_map(move |(name, (type_id, deserialize))| {
|
||||
if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
|
||||
.filter_map(move |(name, (action_id, deserialize))| {
|
||||
if let Some(action_depth) = handler_depths_by_action_id.get(action_id).copied() {
|
||||
let action = deserialize(serde_json::Value::Object(Default::default())).ok()?;
|
||||
let bindings = self
|
||||
.keystroke_matcher
|
||||
.bindings_for_action_type(*type_id)
|
||||
.bindings_for_action(*action_id)
|
||||
.filter(|b| {
|
||||
action.eq(b.action())
|
||||
&& (0..=action_depth)
|
||||
|
@ -434,11 +430,7 @@ impl<'a> WindowContext<'a> {
|
|||
MatchResult::None => false,
|
||||
MatchResult::Pending => true,
|
||||
MatchResult::Matches(matches) => {
|
||||
let no_action_id = (NoAction {}).id();
|
||||
for (view_id, action) in matches {
|
||||
if action.id() == no_action_id {
|
||||
return false;
|
||||
}
|
||||
if self.dispatch_action(Some(*view_id), action.as_ref()) {
|
||||
self.keystroke_matcher.clear_pending();
|
||||
handled_by = Some(action.boxed_clone());
|
||||
|
@ -1408,7 +1400,7 @@ impl<V: View> Element<V> for ChildView {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
|
||||
rendered_view
|
||||
|
|
|
@ -34,8 +34,8 @@ use crate::{
|
|||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
|
||||
WindowContext,
|
||||
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
WeakViewHandle, WindowContext,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::HashMap;
|
||||
|
@ -62,7 +62,7 @@ pub trait Element<V: View>: 'static {
|
|||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState;
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
@ -299,7 +299,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
|
|||
mut layout,
|
||||
} => {
|
||||
let bounds = RectF::new(origin, size);
|
||||
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
|
||||
let paint = element.paint(
|
||||
scene,
|
||||
bounds,
|
||||
visible_bounds,
|
||||
&mut layout,
|
||||
view,
|
||||
&mut PaintContext::new(cx),
|
||||
);
|
||||
ElementState::PostPaint {
|
||||
element,
|
||||
constraint,
|
||||
|
@ -317,7 +324,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
|
|||
..
|
||||
} => {
|
||||
let bounds = RectF::new(origin, bounds.size());
|
||||
let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
|
||||
let paint = element.paint(
|
||||
scene,
|
||||
bounds,
|
||||
visible_bounds,
|
||||
&mut layout,
|
||||
view,
|
||||
&mut PaintContext::new(cx),
|
||||
);
|
||||
ElementState::PostPaint {
|
||||
element,
|
||||
constraint,
|
||||
|
@ -514,7 +528,7 @@ impl<V: View> Element<V> for AnyElement<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
|
||||
|
@ -69,7 +70,7 @@ impl<V: View> Element<V> for Align<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let my_center = bounds.size() / 2.;
|
||||
let my_target = my_center + my_center * self.alignment;
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::marker::PhantomData;
|
|||
use super::Element;
|
||||
use crate::{
|
||||
json::{self, json},
|
||||
SceneBuilder, View, ViewContext,
|
||||
PaintContext, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
use pathfinder_geometry::{
|
||||
|
@ -56,7 +56,7 @@ where
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.0(scene, bounds, visible_bounds, view, cx)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
|||
use serde_json::json;
|
||||
|
||||
use crate::{
|
||||
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
pub struct Clipped<V: View> {
|
||||
|
@ -37,7 +38,7 @@ impl<V: View> Element<V> for Clipped<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
scene.paint_layer(Some(bounds), |scene| {
|
||||
self.child
|
||||
|
|
|
@ -5,7 +5,8 @@ use serde_json::json;
|
|||
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
pub struct ConstrainedBox<V: View> {
|
||||
|
@ -156,7 +157,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
scene.paint_layer(Some(visible_bounds), |scene| {
|
||||
self.child
|
||||
|
|
|
@ -10,7 +10,8 @@ use crate::{
|
|||
json::ToJson,
|
||||
platform::CursorStyle,
|
||||
scene::{self, Border, CursorRegion, Quad},
|
||||
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
@ -214,7 +215,7 @@ impl<V: View> Element<V> for Container<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let quad_bounds = RectF::from_points(
|
||||
bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{json, ToJson},
|
||||
LayoutContext, SceneBuilder, View, ViewContext,
|
||||
LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use crate::{Element, SizeConstraint};
|
||||
|
||||
|
@ -57,7 +57,7 @@ impl<V: View> Element<V> for Empty {
|
|||
_: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
_: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ use std::ops::Range;
|
|||
|
||||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -61,7 +62,7 @@ impl<V: View> Element<V> for Expanded<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
|
|
|
@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
|
|||
|
||||
use crate::{
|
||||
json::{self, ToJson, Value},
|
||||
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
|
||||
Vector2FExt, View, ViewContext,
|
||||
AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
|
||||
SizeConstraint, Vector2FExt, View, ViewContext,
|
||||
};
|
||||
use pathfinder_geometry::{
|
||||
rect::RectF,
|
||||
|
@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
|
|||
visible_bounds: RectF,
|
||||
remaining_space: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
|
||||
|
@ -449,7 +449,7 @@ impl<V: View> Element<V> for FlexItem<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
|
|
|
@ -3,7 +3,8 @@ use std::ops::Range;
|
|||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
pub struct Hook<V: View> {
|
||||
|
@ -52,7 +53,7 @@ impl<V: View> Element<V> for Hook<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
|
|
|
@ -5,8 +5,8 @@ use crate::{
|
|||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::{json, ToJson},
|
||||
scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
|
||||
View, ViewContext,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
@ -97,7 +97,7 @@ impl<V: View> Element<V> for Image {
|
|||
_: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
_: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
if let Some(data) = layout {
|
||||
scene.push_image(scene::Image {
|
||||
|
|
|
@ -66,7 +66,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
|
|||
visible_bounds: RectF,
|
||||
element: &mut AnyElement<V>,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
element.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
},
|
||||
json::{ToJson, Value},
|
||||
text_layout::{Line, RunStyle},
|
||||
Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
@ -163,7 +163,7 @@ impl<V: View> Element<V> for Label {
|
|||
visible_bounds: RectF,
|
||||
line: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
|
||||
line.paint(
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
|||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::json,
|
||||
AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
|
||||
View, ViewContext,
|
||||
};
|
||||
use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, SumTree};
|
||||
|
@ -255,7 +255,7 @@ impl<V: View> Element<V> for List<V> {
|
|||
visible_bounds: RectF,
|
||||
scroll_top: &mut ListOffset,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
scene.push_layer(Some(visible_bounds));
|
||||
|
@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{elements::Empty, geometry::vector::vec2f, Entity};
|
||||
use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
|
||||
use rand::prelude::*;
|
||||
use std::env;
|
||||
|
||||
|
@ -988,7 +988,7 @@ mod tests {
|
|||
_: RectF,
|
||||
_: &mut (),
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
_: &mut PaintContext<V>,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ use crate::{
|
|||
CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
|
||||
MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
|
||||
},
|
||||
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
|
||||
SizeConstraint, View, ViewContext,
|
||||
AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
|
||||
SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{marker::PhantomData, ops::Range};
|
||||
|
@ -256,7 +256,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
if self.above {
|
||||
self.child
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::ops::Range;
|
|||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::ToJson,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
|
||||
SizeConstraint, View, ViewContext,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -143,7 +143,7 @@ impl<V: View> Element<V> for Overlay<V> {
|
|||
_: RectF,
|
||||
size: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
let (anchor_position, mut bounds) = match self.position_mode {
|
||||
OverlayPositionMode::Window => {
|
||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
|||
geometry::rect::RectF,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
scene::MouseDrag,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
|
||||
SizeConstraint, View, ViewContext,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -125,7 +125,7 @@ impl<V: View> Element<V> for Resizable<V> {
|
|||
visible_bounds: pathfinder_geometry::rect::RectF,
|
||||
constraint: &mut SizeConstraint,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
scene.push_stacking_context(None, None);
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ use std::ops::Range;
|
|||
use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::{self, json, ToJson},
|
||||
AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
|
||||
ViewContext,
|
||||
};
|
||||
|
||||
/// Element which renders it's children in a stack on top of each other.
|
||||
|
@ -57,7 +58,7 @@ impl<V: View> Element<V> for Stack<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
for child in &mut self.children {
|
||||
scene.paint_layer(None, |scene| {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::constrain_size_preserving_aspect_ratio;
|
||||
use crate::json::ToJson;
|
||||
use crate::PaintContext;
|
||||
use crate::{
|
||||
color::Color,
|
||||
geometry::{
|
||||
|
@ -73,7 +74,7 @@ impl<V: View> Element<V> for Svg {
|
|||
_visible_bounds: RectF,
|
||||
svg: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
_: &mut ViewContext<V>,
|
||||
_: &mut PaintContext<V>,
|
||||
) {
|
||||
if let Some(svg) = svg.clone() {
|
||||
scene.push_icon(scene::Icon {
|
||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
|||
},
|
||||
json::{ToJson, Value},
|
||||
text_layout::{Line, RunStyle, ShapedBoundary},
|
||||
AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
|
||||
View, ViewContext,
|
||||
AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
|
||||
TextLayoutCache, View, ViewContext,
|
||||
};
|
||||
use log::warn;
|
||||
use serde_json::json;
|
||||
|
@ -171,7 +171,7 @@ impl<V: View> Element<V> for Text {
|
|||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
_: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let mut origin = bounds.origin();
|
||||
let empty = Vec::new();
|
||||
|
|
|
@ -6,8 +6,8 @@ use crate::{
|
|||
fonts::TextStyle,
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
|
||||
ViewContext,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
|
||||
Task, View, ViewContext,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
@ -194,7 +194,7 @@ impl<V: View> Element<V> for Tooltip<V> {
|
|||
visible_bounds: RectF,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) {
|
||||
self.child
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx);
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
},
|
||||
json::{self, json},
|
||||
platform::ScrollWheelEvent,
|
||||
AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
|
||||
AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
|
||||
};
|
||||
use json::ToJson;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
|
@ -278,7 +278,7 @@ impl<V: View> Element<V> for UniformList<V> {
|
|||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut ViewContext<V>,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
|
||||
|
||||
|
|
|
@ -71,6 +71,32 @@ pub struct TextStyle {
|
|||
pub underline: Underline,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
|
||||
TextStyle {
|
||||
color: refinement.color.unwrap_or(self.color),
|
||||
font_family_name: refinement
|
||||
.font_family_name
|
||||
.unwrap_or_else(|| self.font_family_name.clone()),
|
||||
font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
|
||||
font_id: refinement.font_id.unwrap_or(self.font_id),
|
||||
font_size: refinement.font_size.unwrap_or(self.font_size),
|
||||
font_properties: refinement.font_properties.unwrap_or(self.font_properties),
|
||||
underline: refinement.underline.unwrap_or(self.underline),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextStyleRefinement {
|
||||
pub color: Option<Color>,
|
||||
pub font_family_name: Option<Arc<str>>,
|
||||
pub font_family_id: Option<FamilyId>,
|
||||
pub font_id: Option<FontId>,
|
||||
pub font_size: Option<f32>,
|
||||
pub font_properties: Option<Properties>,
|
||||
pub underline: Option<Underline>,
|
||||
}
|
||||
|
||||
#[derive(JsonSchema)]
|
||||
#[serde(remote = "Properties")]
|
||||
pub struct PropertiesDef {
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::{any::TypeId, fmt::Debug};
|
|||
use collections::HashMap;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::Action;
|
||||
use crate::{Action, NoAction};
|
||||
|
||||
pub use binding::{Binding, BindingMatchResult};
|
||||
pub use keymap::Keymap;
|
||||
|
@ -47,8 +47,8 @@ impl KeymapMatcher {
|
|||
self.keymap.clear();
|
||||
}
|
||||
|
||||
pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator<Item = &Binding> {
|
||||
self.keymap.bindings_for_action_type(action_type)
|
||||
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
|
||||
self.keymap.bindings_for_action(action_id)
|
||||
}
|
||||
|
||||
pub fn clear_pending(&mut self) {
|
||||
|
@ -81,6 +81,7 @@ impl KeymapMatcher {
|
|||
// The key is the reverse position of the binding in the bindings list so that later bindings
|
||||
// match before earlier ones in the user's config
|
||||
let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
|
||||
let no_action_id = (NoAction {}).id();
|
||||
|
||||
let first_keystroke = self.pending_keystrokes.is_empty();
|
||||
self.pending_keystrokes.push(keystroke.clone());
|
||||
|
@ -108,7 +109,9 @@ impl KeymapMatcher {
|
|||
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
||||
{
|
||||
BindingMatchResult::Complete(action) => {
|
||||
matched_bindings.push((*view_id, action));
|
||||
if action.id() != no_action_id {
|
||||
matched_bindings.push((*view_id, action));
|
||||
}
|
||||
}
|
||||
BindingMatchResult::Partial => {
|
||||
self.pending_views
|
||||
|
|
|
@ -7,8 +7,8 @@ use super::{KeymapContext, KeymapContextPredicate, Keystroke};
|
|||
|
||||
pub struct Binding {
|
||||
action: Box<dyn Action>,
|
||||
keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
context_predicate: Option<KeymapContextPredicate>,
|
||||
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
pub(super) context_predicate: Option<KeymapContextPredicate>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Binding {
|
||||
|
|
|
@ -1,61 +1,388 @@
|
|||
use collections::HashSet;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
};
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
use super::Binding;
|
||||
use crate::{Action, NoAction};
|
||||
|
||||
use super::{Binding, KeymapContextPredicate, Keystroke};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<Binding>,
|
||||
binding_indices_by_action_type: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn new(bindings: Vec<Binding>) -> Self {
|
||||
let mut binding_indices_by_action_type = HashMap::new();
|
||||
for (ix, binding) in bindings.iter().enumerate() {
|
||||
binding_indices_by_action_type
|
||||
.entry(binding.action().type_id())
|
||||
.or_insert_with(SmallVec::new)
|
||||
.push(ix);
|
||||
}
|
||||
|
||||
Self {
|
||||
binding_indices_by_action_type,
|
||||
bindings,
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(super) fn new(bindings: Vec<Binding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_for_action_type(
|
||||
pub(crate) fn bindings_for_action(
|
||||
&self,
|
||||
action_type: TypeId,
|
||||
action_id: TypeId,
|
||||
) -> impl Iterator<Item = &'_ Binding> {
|
||||
self.binding_indices_by_action_type
|
||||
.get(&action_type)
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map(SmallVec::as_slice)
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
}
|
||||
|
||||
pub(crate) fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
|
||||
let no_action_id = (NoAction {}).id();
|
||||
let mut new_bindings = Vec::new();
|
||||
let mut has_new_disabled_keystrokes = false;
|
||||
for binding in bindings {
|
||||
self.binding_indices_by_action_type
|
||||
.entry(binding.action().as_any().type_id())
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(binding);
|
||||
if binding.action().id() == no_action_id {
|
||||
has_new_disabled_keystrokes |= self
|
||||
.disabled_keystrokes
|
||||
.entry(binding.keystrokes)
|
||||
.or_default()
|
||||
.insert(binding.context_predicate);
|
||||
} else {
|
||||
new_bindings.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
if has_new_disabled_keystrokes {
|
||||
self.binding_indices_by_action_id.retain(|_, indices| {
|
||||
indices.retain(|ix| {
|
||||
let binding = &self.bindings[*ix];
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => {
|
||||
!disabled_predicates.contains(&binding.context_predicate)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
});
|
||||
!indices.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
for new_binding in new_bindings {
|
||||
if !self.binding_disabled(&new_binding) {
|
||||
self.binding_indices_by_action_id
|
||||
.entry(new_binding.action().id())
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(new_binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_type.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
self.disabled_keystrokes.clear();
|
||||
}
|
||||
|
||||
pub fn bindings(&self) -> &Vec<Binding> {
|
||||
&self.bindings
|
||||
pub fn bindings(&self) -> Vec<&Binding> {
|
||||
self.bindings
|
||||
.iter()
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn binding_disabled(&self, binding: &Binding) -> bool {
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::actions;
|
||||
|
||||
use super::*;
|
||||
|
||||
actions!(
|
||||
keymap_test,
|
||||
[Present1, Present2, Present3, Duplicate, Missing]
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn regular_keymap() {
|
||||
let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let missing = Binding::new("ctrl-r", Missing {}, None);
|
||||
let all_bindings = [
|
||||
&present_1,
|
||||
&present_2,
|
||||
&present_3,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
&missing,
|
||||
];
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
assert_absent(&keymap, &all_bindings);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
|
||||
keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
|
||||
assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
|
||||
assert_present(
|
||||
&keymap,
|
||||
&[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
|
||||
);
|
||||
|
||||
keymap.add_bindings([
|
||||
keystroke_duplicate_to_1.clone(),
|
||||
full_duplicate_to_2.clone(),
|
||||
]);
|
||||
assert_absent(&keymap, &[&missing]);
|
||||
assert!(
|
||||
!keymap.binding_disabled(&keystroke_duplicate_to_1),
|
||||
"Duplicate binding 1 was added and should not be disabled"
|
||||
);
|
||||
assert!(
|
||||
!keymap.binding_disabled(&full_duplicate_to_2),
|
||||
"Duplicate binding 2 was added and should not be disabled"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(keystroke_duplicate_to_1.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "q".to_string()
|
||||
}],
|
||||
"{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(full_duplicate_to_2.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
},
|
||||
&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
}
|
||||
],
|
||||
"{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
|
||||
);
|
||||
|
||||
let updated_bindings = keymap.bindings();
|
||||
let expected_updated_bindings = vec![
|
||||
&present_1,
|
||||
&present_2,
|
||||
&present_3,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
];
|
||||
assert_eq!(
|
||||
updated_bindings.len(),
|
||||
expected_updated_bindings.len(),
|
||||
"Unexpected updated keymap bindings {updated_bindings:?}"
|
||||
);
|
||||
for (i, expected) in expected_updated_bindings.iter().enumerate() {
|
||||
let keymap_binding = &updated_bindings[i];
|
||||
assert_eq!(
|
||||
keymap_binding.context_predicate, expected.context_predicate,
|
||||
"Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap_binding.keystrokes, expected.keystrokes,
|
||||
"Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
}
|
||||
|
||||
keymap.clear();
|
||||
assert_absent(&keymap, &all_bindings);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keymap_with_ignored() {
|
||||
let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
|
||||
let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
|
||||
let ignored_3_with_other_context =
|
||||
Binding::new("ctrl-e", NoAction {}, Some("other_context"));
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
|
||||
keymap.add_bindings([
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_absent(&keymap, &[&present_3]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
assert!(keymap.bindings().is_empty());
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_3, "e")]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
ignored_1.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
|
||||
assert_disabled(&keymap, &[&present_1, &ignored_1]);
|
||||
assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
|
||||
keymap.clear();
|
||||
|
||||
keymap.add_bindings([
|
||||
present_1.clone(),
|
||||
present_2.clone(),
|
||||
present_3.clone(),
|
||||
keystroke_duplicate_to_1.clone(),
|
||||
full_duplicate_to_2.clone(),
|
||||
ignored_1.clone(),
|
||||
ignored_2.clone(),
|
||||
ignored_3_with_other_context.clone(),
|
||||
]);
|
||||
assert_present(&keymap, &[(&present_3, "e")]);
|
||||
assert_disabled(
|
||||
&keymap,
|
||||
&[
|
||||
&present_1,
|
||||
&present_2,
|
||||
&keystroke_duplicate_to_1,
|
||||
&full_duplicate_to_2,
|
||||
&ignored_1,
|
||||
&ignored_2,
|
||||
&ignored_3_with_other_context,
|
||||
],
|
||||
);
|
||||
keymap.clear();
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
|
||||
let keymap_bindings = keymap.bindings();
|
||||
assert_eq!(
|
||||
expected_bindings.len(),
|
||||
keymap_bindings.len(),
|
||||
"Unexpected keymap bindings {keymap_bindings:?}"
|
||||
);
|
||||
for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
|
||||
assert!(
|
||||
!keymap.binding_disabled(expected),
|
||||
"{expected:?} should not be disabled as it was added into keymap for element {i}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap
|
||||
.bindings_for_action(expected.action().id())
|
||||
.map(|binding| &binding.keystrokes)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![&Keystroke {
|
||||
ctrl: true,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: expected_key.to_string()
|
||||
}],
|
||||
"{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
|
||||
);
|
||||
|
||||
let keymap_binding = &keymap_bindings[i];
|
||||
assert_eq!(
|
||||
keymap_binding.context_predicate, expected.context_predicate,
|
||||
"Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap_binding.keystrokes, expected.keystrokes,
|
||||
"Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
for binding in bindings.iter() {
|
||||
assert!(
|
||||
!keymap.binding_disabled(binding),
|
||||
"{binding:?} should not be disabled in the keymap where was not added"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap.bindings_for_action(binding.action().id()).count(),
|
||||
0,
|
||||
"{binding:?} should have no actions in the keymap where was not added"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
for binding in bindings.iter() {
|
||||
assert!(
|
||||
keymap.binding_disabled(binding),
|
||||
"{binding:?} should be disabled in the keymap"
|
||||
);
|
||||
assert_eq!(
|
||||
keymap.bindings_for_action(binding.action().id()).count(),
|
||||
0,
|
||||
"{binding:?} should have no actions in the keymap where it was disabled"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ impl KeymapContext {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum KeymapContextPredicate {
|
||||
Identifier(String),
|
||||
Equal(String, String),
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Write;
|
|||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
|
|
|
@ -231,7 +231,7 @@ impl MacForegroundPlatform {
|
|||
} => {
|
||||
// TODO
|
||||
let keystrokes = keystroke_matcher
|
||||
.bindings_for_action_type(action.as_any().type_id())
|
||||
.bindings_for_action(action.id())
|
||||
.find(|binding| binding.action().eq(action.as_ref()))
|
||||
.map(|binding| binding.keystrokes());
|
||||
let selector = match os_action {
|
||||
|
|
|
@ -10,8 +10,8 @@ use crate::{
|
|||
mac::{
|
||||
platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer, screen::Screen,
|
||||
},
|
||||
Event, InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
|
||||
MouseMovedEvent, Scene, WindowBounds, WindowKind,
|
||||
Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
|
||||
},
|
||||
};
|
||||
use block::ConcreteBlock;
|
||||
|
@ -1053,7 +1053,44 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
|||
|
||||
let window_height = window_state_borrow.content_size().y();
|
||||
let event = unsafe { Event::from_native(native_event, Some(window_height)) };
|
||||
if let Some(event) = event {
|
||||
|
||||
if let Some(mut event) = event {
|
||||
let synthesized_second_event = match &mut event {
|
||||
Event::MouseDown(
|
||||
event @ MouseButtonEvent {
|
||||
button: MouseButton::Left,
|
||||
modifiers: Modifiers { ctrl: true, .. },
|
||||
..
|
||||
},
|
||||
) => {
|
||||
*event = MouseButtonEvent {
|
||||
button: MouseButton::Right,
|
||||
modifiers: Modifiers {
|
||||
ctrl: false,
|
||||
..event.modifiers
|
||||
},
|
||||
click_count: 1,
|
||||
..*event
|
||||
};
|
||||
|
||||
Some(Event::MouseUp(MouseButtonEvent {
|
||||
button: MouseButton::Right,
|
||||
..*event
|
||||
}))
|
||||
}
|
||||
|
||||
// Because we map a ctrl-left_down to a right_down -> right_up let's ignore
|
||||
// the ctrl-left_up to avoid having a mismatch in button down/up events if the
|
||||
// user is still holding ctrl when releasing the left mouse button
|
||||
Event::MouseUp(MouseButtonEvent {
|
||||
button: MouseButton::Left,
|
||||
modifiers: Modifiers { ctrl: true, .. },
|
||||
..
|
||||
}) => return,
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match &event {
|
||||
Event::MouseMoved(
|
||||
event @ MouseMovedEvent {
|
||||
|
@ -1105,6 +1142,9 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
|||
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
||||
drop(window_state_borrow);
|
||||
callback(event);
|
||||
if let Some(event) = synthesized_second_event {
|
||||
callback(event);
|
||||
}
|
||||
window_state.borrow_mut().event_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,6 @@ lazy_static.workspace = true
|
|||
log.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
rand = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
@ -56,10 +55,12 @@ similar = "1.3"
|
|||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
tree-sitter-rust = { version = "*", optional = true }
|
||||
tree-sitter-typescript = { version = "*", optional = true }
|
||||
unicase = "2.6"
|
||||
|
||||
rand = { workspace = true, optional = true }
|
||||
tree-sitter-rust = { workspace = true, optional = true }
|
||||
tree-sitter-typescript = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
client = { path = "../client", features = ["test-support"] }
|
||||
collections = { path = "../collections", features = ["test-support"] }
|
||||
|
@ -74,12 +75,13 @@ indoc.workspace = true
|
|||
rand.workspace = true
|
||||
unindent.workspace = true
|
||||
|
||||
tree-sitter-embedded-template = "*"
|
||||
tree-sitter-html = "*"
|
||||
tree-sitter-javascript = "*"
|
||||
tree-sitter-json = "*"
|
||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||
tree-sitter-rust = "*"
|
||||
tree-sitter-python = "*"
|
||||
tree-sitter-typescript = "*"
|
||||
tree-sitter-ruby = "*"
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-ruby.workspace = true
|
||||
tree-sitter-elixir.workspace = true
|
||||
tree-sitter-heex.workspace = true
|
||||
|
|
|
@ -2145,23 +2145,27 @@ impl BufferSnapshot {
|
|||
|
||||
pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
|
||||
let offset = position.to_offset(self);
|
||||
let mut range = 0..self.len();
|
||||
let mut scope = self.language.clone().map(|language| LanguageScope {
|
||||
language,
|
||||
override_id: None,
|
||||
});
|
||||
|
||||
if let Some(layer_info) = self
|
||||
.syntax
|
||||
.layers_for_range(offset..offset, &self.text)
|
||||
.filter(|l| l.node().end_byte() > offset)
|
||||
.last()
|
||||
{
|
||||
Some(LanguageScope {
|
||||
language: layer_info.language.clone(),
|
||||
override_id: layer_info.override_id(offset, &self.text),
|
||||
})
|
||||
} else {
|
||||
self.language.clone().map(|language| LanguageScope {
|
||||
language,
|
||||
override_id: None,
|
||||
})
|
||||
// Use the layer that has the smallest node intersecting the given point.
|
||||
for layer in self.syntax.layers_for_range(offset..offset, &self.text) {
|
||||
let mut cursor = layer.node().walk();
|
||||
while cursor.goto_first_child_for_byte(offset).is_some() {}
|
||||
let node_range = cursor.node().byte_range();
|
||||
if node_range.to_inclusive().contains(&offset) && node_range.len() < range.len() {
|
||||
range = node_range;
|
||||
scope = Some(LanguageScope {
|
||||
language: layer.language.clone(),
|
||||
override_id: layer.override_id(offset, &self.text),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn surrounding_word<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
|
||||
|
|
|
@ -1533,47 +1533,9 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
|
|||
])
|
||||
});
|
||||
|
||||
let html_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
"
|
||||
(element
|
||||
(start_tag) @start
|
||||
(end_tag)? @end) @indent
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let html_language = Arc::new(html_lang());
|
||||
|
||||
let javascript_language = Arc::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "JavaScript".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
r#"
|
||||
(object "}" @end) @indent
|
||||
"#,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let javascript_language = Arc::new(javascript_lang());
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test());
|
||||
language_registry.add(html_language.clone());
|
||||
|
@ -1669,7 +1631,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_language_config_at(cx: &mut AppContext) {
|
||||
fn test_language_scope_at(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
cx.add_model(|cx| {
|
||||
|
@ -1709,7 +1671,7 @@ fn test_language_config_at(cx: &mut AppContext) {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_override_query(
|
||||
r#"
|
||||
|
@ -1756,6 +1718,54 @@ fn test_language_config_at(cx: &mut AppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
|
||||
init_settings(cx, |_| {});
|
||||
|
||||
cx.add_model(|cx| {
|
||||
let text = r#"
|
||||
<ol>
|
||||
<% people.each do |person| %>
|
||||
<li>
|
||||
<%= person.name %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
let language_registry = Arc::new(LanguageRegistry::test());
|
||||
language_registry.add(Arc::new(ruby_lang()));
|
||||
language_registry.add(Arc::new(html_lang()));
|
||||
language_registry.add(Arc::new(erb_lang()));
|
||||
|
||||
let mut buffer = Buffer::new(0, text, cx);
|
||||
buffer.set_language_registry(language_registry.clone());
|
||||
buffer.set_language(
|
||||
language_registry
|
||||
.language_for_name("ERB")
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.ok(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let snapshot = buffer.snapshot();
|
||||
let html_config = snapshot.language_scope_at(Point::new(2, 4)).unwrap();
|
||||
assert_eq!(html_config.line_comment_prefix(), None);
|
||||
assert_eq!(
|
||||
html_config.block_comment_delimiters(),
|
||||
Some((&"<!--".into(), &"-->".into()))
|
||||
);
|
||||
|
||||
let ruby_config = snapshot.language_scope_at(Point::new(3, 12)).unwrap();
|
||||
assert_eq!(ruby_config.line_comment_prefix().unwrap().as_ref(), "# ");
|
||||
assert_eq!(ruby_config.block_comment_delimiters(), None);
|
||||
|
||||
buffer
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_serialization(cx: &mut gpui::AppContext) {
|
||||
let mut now = Instant::now();
|
||||
|
@ -2143,6 +2153,7 @@ fn ruby_lang() -> Language {
|
|||
LanguageConfig {
|
||||
name: "Ruby".into(),
|
||||
path_suffixes: vec!["rb".to_string()],
|
||||
line_comment: Some("# ".into()),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_ruby::language()),
|
||||
|
@ -2158,6 +2169,61 @@ fn ruby_lang() -> Language {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
fn html_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "HTML".into(),
|
||||
block_comment: Some(("<!--".into(), "-->".into())),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_html::language()),
|
||||
)
|
||||
.with_indents_query(
|
||||
"
|
||||
(element
|
||||
(start_tag) @start
|
||||
(end_tag)? @end) @indent
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "javascript"))
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn erb_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "ERB".into(),
|
||||
path_suffixes: vec!["erb".to_string()],
|
||||
block_comment: Some(("<%#".into(), "%>".into())),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_embedded_template::language()),
|
||||
)
|
||||
.with_injection_query(
|
||||
r#"
|
||||
(
|
||||
(code) @content
|
||||
(#set! "language" "ruby")
|
||||
(#set! "combined")
|
||||
)
|
||||
|
||||
(
|
||||
(content) @content
|
||||
(#set! "language" "html")
|
||||
(#set! "combined")
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
|
@ -2227,7 +2293,7 @@ fn javascript_lang() -> Language {
|
|||
name: "JavaScript".into(),
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_javascript::language()),
|
||||
Some(tree_sitter_typescript::language_tsx()),
|
||||
)
|
||||
.with_brackets_query(
|
||||
r#"
|
||||
|
@ -2236,6 +2302,12 @@ fn javascript_lang() -> Language {
|
|||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_indents_query(
|
||||
r#"
|
||||
(object "}" @end) @indent
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn get_tree_sexp(buffer: &ModelHandle<Buffer>, cx: &gpui::TestAppContext) -> String {
|
||||
|
|