Compare commits
5 Commits
copilot_ex
...
look-behin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ea0c9634c | ||
|
|
4bbdf18280 | ||
|
|
83b191cb17 | ||
|
|
6cde94d0bc | ||
|
|
6823ac624a |
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@2e657c127d5b1635d5a8e3fa40e0ac50a5bf6992 # v3
|
||||
uses: astral-sh/setup-uv@f3bcaebff5eace81a1c062af9f9011aae482ca9d # v3
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: true
|
||||
|
||||
8
.github/workflows/deploy_cloudflare.yml
vendored
8
.github/workflows/deploy_cloudflare.yml
vendored
@@ -37,28 +37,28 @@ jobs:
|
||||
mdbook build ./docs --dest-dir=../target/deploy/docs/
|
||||
|
||||
- name: Deploy Docs
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy target/deploy --project-name=docs
|
||||
|
||||
- name: Deploy Install
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: r2 object put -f script/install.sh zed-open-source-website-assets/install.sh
|
||||
|
||||
- name: Deploy Docs Workers
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Deploy Install Workers
|
||||
uses: cloudflare/wrangler-action@05f17c4a695b4d94b57b59997562c6a4624c64e4 # v3
|
||||
uses: cloudflare/wrangler-action@b2a0191ce60d21388e1a8dcc968b4e9966f938e1 # v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
|
||||
638
Cargo.lock
generated
638
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -350,6 +350,7 @@ ec4rs = "1.1"
|
||||
emojis = "0.6.1"
|
||||
env_logger = "0.11"
|
||||
exec = "0.3.1"
|
||||
fancy-regex = "0.14.0"
|
||||
fork = "0.2.0"
|
||||
futures = "0.3"
|
||||
futures-batch = "0.6.1"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-search"><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M4.268 21a2 2 0 0 0 1.727 1H18a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v3"/><path d="m9 18-1.5-1.5"/><circle cx="5" cy="14" r="3"/></svg>
|
||||
|
Before Width: | Height: | Size: 393 B |
@@ -251,10 +251,10 @@
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"ctrl-w": "pane::CloseActiveItem",
|
||||
"ctrl-f4": "pane::CloseActiveItem",
|
||||
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-ctrl-t": "pane::CloseInactiveItems",
|
||||
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"ctrl-k u": "pane::CloseCleanItems",
|
||||
"ctrl-k w": "pane::CloseAllItems",
|
||||
"ctrl-shift-f": "project_search::ToggleFocus",
|
||||
"ctrl-alt-g": "search::SelectNextMatch",
|
||||
"ctrl-alt-shift-g": "search::SelectPrevMatch",
|
||||
|
||||
@@ -291,10 +291,10 @@
|
||||
"ctrl-shift-pageup": "pane::SwapItemLeft",
|
||||
"ctrl-shift-pagedown": "pane::SwapItemRight",
|
||||
"cmd-w": "pane::CloseActiveItem",
|
||||
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
|
||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
||||
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
|
||||
"cmd-k u": ["pane::CloseCleanItems", { "close_pinned": false }],
|
||||
"cmd-k cmd-w": ["pane::CloseAllItems", { "close_pinned": false }],
|
||||
"cmd-k u": "pane::CloseCleanItems",
|
||||
"cmd-k cmd-w": "pane::CloseAllItems",
|
||||
"cmd-f": "project_search::ToggleFocus",
|
||||
"cmd-g": "search::SelectNextMatch",
|
||||
"cmd-shift-g": "search::SelectPrevMatch",
|
||||
|
||||
@@ -27,15 +27,15 @@
|
||||
"ghost_element.active": "#454a56ff",
|
||||
"ghost_element.selected": "#454a56ff",
|
||||
"ghost_element.disabled": "#2e343eff",
|
||||
"text": "#dce0e5ff",
|
||||
"text.muted": "#a9afbcff",
|
||||
"text.placeholder": "#878a98ff",
|
||||
"text.disabled": "#878a98ff",
|
||||
"text": "#c8ccd4ff",
|
||||
"text.muted": "#838994ff",
|
||||
"text.placeholder": "#696B77ff",
|
||||
"text.disabled": "#696B77ff",
|
||||
"text.accent": "#74ade8ff",
|
||||
"icon": "#dce0e5ff",
|
||||
"icon.muted": "#a9afbcff",
|
||||
"icon.disabled": "#878a98ff",
|
||||
"icon.placeholder": "#a9afbcff",
|
||||
"icon": "#c8ccd4ff",
|
||||
"icon.muted": "#838994ff",
|
||||
"icon.disabled": "#696B77ff",
|
||||
"icon.placeholder": "#838994ff",
|
||||
"icon.accent": "#74ade8ff",
|
||||
"status_bar.background": "#3b414dff",
|
||||
"title_bar.background": "#3b414dff",
|
||||
@@ -60,19 +60,19 @@
|
||||
"editor.active_line.background": "#2f343ebf",
|
||||
"editor.highlighted_line.background": "#2f343eff",
|
||||
"editor.line_number": "#c8ccd459",
|
||||
"editor.active_line_number": "#dce0e5ff",
|
||||
"editor.invisible": "#878a98ff",
|
||||
"editor.active_line_number": "#c8ccd4ff",
|
||||
"editor.invisible": "#696B77ff",
|
||||
"editor.wrap_guide": "#c8ccd40d",
|
||||
"editor.active_wrap_guide": "#c8ccd41a",
|
||||
"editor.document_highlight.read_background": "#74ade81a",
|
||||
"editor.document_highlight.write_background": "#555a6366",
|
||||
"terminal.background": "#282c33ff",
|
||||
"terminal.foreground": "#dce0e5ff",
|
||||
"terminal.bright_foreground": "#dce0e5ff",
|
||||
"terminal.foreground": "#c8ccd4ff",
|
||||
"terminal.bright_foreground": "#c8ccd4ff",
|
||||
"terminal.dim_foreground": "#282c33ff",
|
||||
"terminal.ansi.black": "#282c33ff",
|
||||
"terminal.ansi.bright_black": "#525561ff",
|
||||
"terminal.ansi.dim_black": "#dce0e5ff",
|
||||
"terminal.ansi.dim_black": "#c8ccd4ff",
|
||||
"terminal.ansi.red": "#d07277ff",
|
||||
"terminal.ansi.bright_red": "#673a3cff",
|
||||
"terminal.ansi.dim_red": "#eab7b9ff",
|
||||
@@ -91,8 +91,8 @@
|
||||
"terminal.ansi.cyan": "#6eb4bfff",
|
||||
"terminal.ansi.bright_cyan": "#3a565bff",
|
||||
"terminal.ansi.dim_cyan": "#b9d9dfff",
|
||||
"terminal.ansi.white": "#dce0e5ff",
|
||||
"terminal.ansi.bright_white": "#dce0e5ff",
|
||||
"terminal.ansi.white": "#c8ccd4ff",
|
||||
"terminal.ansi.bright_white": "#c8ccd4ff",
|
||||
"terminal.ansi.dim_white": "#575d65ff",
|
||||
"link_text.hover": "#74ade8ff",
|
||||
"conflict": "#dec184ff",
|
||||
@@ -107,14 +107,14 @@
|
||||
"error": "#d07277ff",
|
||||
"error.background": "#d072771a",
|
||||
"error.border": "#4c2b2cff",
|
||||
"hidden": "#878a98ff",
|
||||
"hidden.background": "#696b771a",
|
||||
"hidden": "#696B77ff",
|
||||
"hidden.background": "#696B771a",
|
||||
"hidden.border": "#414754ff",
|
||||
"hint": "#788ca6ff",
|
||||
"hint": "#5a6f89ff",
|
||||
"hint.background": "#5a6f891a",
|
||||
"hint.border": "#293b5bff",
|
||||
"ignored": "#878a98ff",
|
||||
"ignored.background": "#696b771a",
|
||||
"ignored": "#696B77ff",
|
||||
"ignored.background": "#696B771a",
|
||||
"ignored.border": "#464b57ff",
|
||||
"info": "#74ade8ff",
|
||||
"info.background": "#74ade81a",
|
||||
@@ -131,7 +131,7 @@
|
||||
"success": "#a1c181ff",
|
||||
"success.background": "#a1c1811a",
|
||||
"success.border": "#38482fff",
|
||||
"unreachable": "#a9afbcff",
|
||||
"unreachable": "#838994ff",
|
||||
"unreachable.background": "#8389941a",
|
||||
"unreachable.border": "#464b57ff",
|
||||
"warning": "#dec184ff",
|
||||
@@ -211,7 +211,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#dce0e5ff",
|
||||
"color": "#c8ccd4ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -236,7 +236,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"hint": {
|
||||
"color": "#788ca6ff",
|
||||
"color": "#5a6f89ff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
@@ -276,7 +276,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"preproc": {
|
||||
"color": "#dce0e5ff",
|
||||
"color": "#c8ccd4ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -361,7 +361,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#dce0e5ff",
|
||||
"color": "#c8ccd4ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -402,15 +402,15 @@
|
||||
"ghost_element.active": "#cacacaff",
|
||||
"ghost_element.selected": "#cacacaff",
|
||||
"ghost_element.disabled": "#ebebecff",
|
||||
"text": "#242529ff",
|
||||
"text.muted": "#58585aff",
|
||||
"text.placeholder": "#7e8086ff",
|
||||
"text.disabled": "#7e8086ff",
|
||||
"text": "#383a41ff",
|
||||
"text.muted": "#7e8087ff",
|
||||
"text.placeholder": "#a1a1a3ff",
|
||||
"text.disabled": "#a1a1a3ff",
|
||||
"text.accent": "#5c78e2ff",
|
||||
"icon": "#242529ff",
|
||||
"icon.muted": "#58585aff",
|
||||
"icon.disabled": "#7e8086ff",
|
||||
"icon.placeholder": "#58585aff",
|
||||
"icon": "#383a41ff",
|
||||
"icon.muted": "#7e8087ff",
|
||||
"icon.disabled": "#a1a1a3ff",
|
||||
"icon.placeholder": "#7e8087ff",
|
||||
"icon.accent": "#5c78e2ff",
|
||||
"status_bar.background": "#dcdcddff",
|
||||
"title_bar.background": "#dcdcddff",
|
||||
@@ -428,26 +428,26 @@
|
||||
"scrollbar.thumb.border": "#dfdfe0ff",
|
||||
"scrollbar.track.background": "#00000000",
|
||||
"scrollbar.track.border": "#eeeeeeff",
|
||||
"editor.foreground": "#242529ff",
|
||||
"editor.foreground": "#383a41ff",
|
||||
"editor.background": "#fafafaff",
|
||||
"editor.gutter.background": "#fafafaff",
|
||||
"editor.subheader.background": "#ebebecff",
|
||||
"editor.active_line.background": "#ebebecbf",
|
||||
"editor.highlighted_line.background": "#ebebecff",
|
||||
"editor.line_number": "#383a4159",
|
||||
"editor.active_line_number": "#242529ff",
|
||||
"editor.active_line_number": "#383a41ff",
|
||||
"editor.invisible": "#a3a3a4ff",
|
||||
"editor.wrap_guide": "#383a410d",
|
||||
"editor.active_wrap_guide": "#383a411a",
|
||||
"editor.document_highlight.read_background": "#5c78e21a",
|
||||
"editor.document_highlight.write_background": "#a3a3a466",
|
||||
"terminal.background": "#fafafaff",
|
||||
"terminal.foreground": "#242529ff",
|
||||
"terminal.bright_foreground": "#242529ff",
|
||||
"terminal.foreground": "#383a41ff",
|
||||
"terminal.bright_foreground": "#383a41ff",
|
||||
"terminal.dim_foreground": "#fafafaff",
|
||||
"terminal.ansi.black": "#fafafaff",
|
||||
"terminal.ansi.bright_black": "#aaaaaaff",
|
||||
"terminal.ansi.dim_black": "#242529ff",
|
||||
"terminal.ansi.dim_black": "#383a41ff",
|
||||
"terminal.ansi.red": "#d36151ff",
|
||||
"terminal.ansi.bright_red": "#f0b0a4ff",
|
||||
"terminal.ansi.dim_red": "#6f312aff",
|
||||
@@ -466,11 +466,11 @@
|
||||
"terminal.ansi.cyan": "#3a82b7ff",
|
||||
"terminal.ansi.bright_cyan": "#a3bedaff",
|
||||
"terminal.ansi.dim_cyan": "#254058ff",
|
||||
"terminal.ansi.white": "#242529ff",
|
||||
"terminal.ansi.bright_white": "#242529ff",
|
||||
"terminal.ansi.white": "#383a41ff",
|
||||
"terminal.ansi.bright_white": "#383a41ff",
|
||||
"terminal.ansi.dim_white": "#97979aff",
|
||||
"link_text.hover": "#5c78e2ff",
|
||||
"conflict": "#a48819ff",
|
||||
"conflict": "#dec184ff",
|
||||
"conflict.background": "#faf2e6ff",
|
||||
"conflict.border": "#f4e7d1ff",
|
||||
"created": "#669f59ff",
|
||||
@@ -482,19 +482,19 @@
|
||||
"error": "#d36151ff",
|
||||
"error.background": "#fbdfd9ff",
|
||||
"error.border": "#f6c6bdff",
|
||||
"hidden": "#7e8086ff",
|
||||
"hidden": "#a1a1a3ff",
|
||||
"hidden.background": "#dcdcddff",
|
||||
"hidden.border": "#d3d3d4ff",
|
||||
"hint": "#7274a7ff",
|
||||
"hint": "#9294beff",
|
||||
"hint.background": "#e2e2faff",
|
||||
"hint.border": "#cbcdf6ff",
|
||||
"ignored": "#7e8086ff",
|
||||
"ignored": "#a1a1a3ff",
|
||||
"ignored.background": "#dcdcddff",
|
||||
"ignored.border": "#c9c9caff",
|
||||
"info": "#5c78e2ff",
|
||||
"info.background": "#e2e2faff",
|
||||
"info.border": "#cbcdf6ff",
|
||||
"modified": "#a48819ff",
|
||||
"modified": "#a47a23ff",
|
||||
"modified.background": "#faf2e6ff",
|
||||
"modified.border": "#f4e7d1ff",
|
||||
"predictive": "#9b9ec6ff",
|
||||
@@ -506,10 +506,10 @@
|
||||
"success": "#669f59ff",
|
||||
"success.background": "#dfeadbff",
|
||||
"success.border": "#c8dcc1ff",
|
||||
"unreachable": "#58585aff",
|
||||
"unreachable": "#7e8087ff",
|
||||
"unreachable.background": "#dcdcddff",
|
||||
"unreachable.border": "#c9c9caff",
|
||||
"warning": "#a48819ff",
|
||||
"warning": "#dec184ff",
|
||||
"warning.background": "#faf2e6ff",
|
||||
"warning.border": "#f4e7d1ff",
|
||||
"players": [
|
||||
@@ -544,7 +544,7 @@
|
||||
"selection": "#d361513d"
|
||||
},
|
||||
{
|
||||
"cursor": "#a48819ff",
|
||||
"cursor": "#dec184ff",
|
||||
"background": "#dec184ff",
|
||||
"selection": "#dec1843d"
|
||||
},
|
||||
@@ -586,7 +586,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#242529ff",
|
||||
"color": "#383a41ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -611,7 +611,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"hint": {
|
||||
"color": "#7274a7ff",
|
||||
"color": "#9294beff",
|
||||
"font_style": null,
|
||||
"font_weight": 700
|
||||
},
|
||||
@@ -651,12 +651,12 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"preproc": {
|
||||
"color": "#242529ff",
|
||||
"color": "#383a41ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"primary": {
|
||||
"color": "#242529ff",
|
||||
"color": "#383a41ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -666,7 +666,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"punctuation": {
|
||||
"color": "#242529ff",
|
||||
"color": "#383a41ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -736,7 +736,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#242529ff",
|
||||
"color": "#383a41ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -519,8 +519,8 @@
|
||||
"selection": "#d337813d"
|
||||
},
|
||||
{
|
||||
"cursor": "#cb4b16ff",
|
||||
"background": "#cb4b16ff",
|
||||
"cursor": "#cb4b17ff",
|
||||
"background": "#cb4b17ff",
|
||||
"selection": "#cb4b173d"
|
||||
},
|
||||
{
|
||||
@@ -596,7 +596,7 @@
|
||||
"font_weight": 700
|
||||
},
|
||||
"enum": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -621,7 +621,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -636,7 +636,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"operator": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -686,7 +686,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -696,17 +696,17 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
@@ -716,7 +716,7 @@
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#cb4b16ff",
|
||||
"color": "#cb4b17ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::slash_command::file_command::codeblock_fence_for_path;
|
||||
use crate::{
|
||||
assistant_settings::{AssistantDockPosition, AssistantSettings},
|
||||
humanize_token_count,
|
||||
@@ -7,23 +6,24 @@ use crate::{
|
||||
slash_command::{
|
||||
default_command::DefaultSlashCommand,
|
||||
docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
|
||||
file_command, SlashCommandCompletionProvider, SlashCommandRegistry,
|
||||
file_command::{self, codeblock_fence_for_path},
|
||||
SlashCommandCompletionProvider, SlashCommandRegistry,
|
||||
},
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context,
|
||||
ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
|
||||
DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
|
||||
InsertIntoEditor, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, ParsedSlashCommand,
|
||||
PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, RequestType,
|
||||
SavedContextMetadata, SlashCommandId, Split, ToggleFocus, ToggleModelSelector,
|
||||
InsertIntoEditor, Message, MessageId, MessageMetadata, MessageStatus, ModelPickerDelegate,
|
||||
ModelSelector, NewContext, PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection,
|
||||
RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
|
||||
ToggleModelSelector,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||
use assistant_tool::ToolRegistry;
|
||||
use client::{proto, zed_urls, Client, Status};
|
||||
use collections::{hash_map, BTreeSet, HashMap, HashSet};
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
use editor::{
|
||||
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||
display_map::{
|
||||
@@ -38,12 +38,12 @@ use editor::{display_map::CreaseId, FoldPlaceholder};
|
||||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
canvas, div, img, percentage, point, prelude::*, pulsating_between, size, Action, Animation,
|
||||
AnimationExt, AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry,
|
||||
ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle,
|
||||
FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
|
||||
Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription,
|
||||
Task, Transformation, UpdateGlobal, View, WeakModel, WeakView,
|
||||
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
|
||||
CursorStyle, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
|
||||
FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, RenderImage,
|
||||
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||
UpdateGlobal, View, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
@@ -1477,7 +1477,7 @@ pub struct ContextEditor {
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
remote_id: Option<workspace::ViewId>,
|
||||
pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
invoked_slash_command_creases: HashMap<SlashCommandId, CreaseId>,
|
||||
pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
|
||||
pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
patches: HashMap<Range<language::Anchor>, PatchViewState>,
|
||||
@@ -1548,7 +1548,7 @@ impl ContextEditor {
|
||||
workspace,
|
||||
project,
|
||||
pending_slash_command_creases: HashMap::default(),
|
||||
invoked_slash_command_creases: HashMap::default(),
|
||||
pending_slash_command_blocks: HashMap::default(),
|
||||
pending_tool_use_creases: HashMap::default(),
|
||||
_subscriptions,
|
||||
patches: HashMap::default(),
|
||||
@@ -1573,13 +1573,14 @@ impl ContextEditor {
|
||||
});
|
||||
let command = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context.parsed_slash_commands()[0].clone()
|
||||
context.pending_slash_commands()[0].clone()
|
||||
});
|
||||
self.run_command(
|
||||
command.source_range,
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
false,
|
||||
false,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -1752,6 +1753,7 @@ impl ContextEditor {
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
true,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -1767,6 +1769,7 @@ impl ContextEditor {
|
||||
name: &str,
|
||||
arguments: &[String],
|
||||
ensure_trailing_newline: bool,
|
||||
expand_result: bool,
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
@@ -1790,9 +1793,9 @@ impl ContextEditor {
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.insert_command_output(
|
||||
command_range,
|
||||
name,
|
||||
output,
|
||||
ensure_trailing_newline,
|
||||
expand_result,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1862,7 +1865,8 @@ impl ContextEditor {
|
||||
IconName::PocketKnife,
|
||||
tool_use.name.clone().into(),
|
||||
),
|
||||
..Default::default()
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
};
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
@@ -1917,10 +1921,11 @@ impl ContextEditor {
|
||||
ContextEvent::PatchesUpdated { removed, updated } => {
|
||||
self.patches_updated(removed, updated, cx);
|
||||
}
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
|
||||
let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
|
||||
let excerpt_id = *excerpt_id;
|
||||
|
||||
editor.remove_creases(
|
||||
removed
|
||||
@@ -1929,6 +1934,16 @@ impl ContextEditor {
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.remove_blocks(
|
||||
HashSet::from_iter(
|
||||
removed.iter().filter_map(|range| {
|
||||
self.pending_slash_command_blocks.remove(range)
|
||||
}),
|
||||
),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
let crease_ids = editor.insert_creases(
|
||||
updated.iter().map(|command| {
|
||||
let workspace = self.workspace.clone();
|
||||
@@ -1943,6 +1958,7 @@ impl ContextEditor {
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
false,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -1952,7 +1968,8 @@ impl ContextEditor {
|
||||
});
|
||||
let placeholder = FoldPlaceholder {
|
||||
render: Arc::new(move |_, _, _| Empty.into_any()),
|
||||
..Default::default()
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
};
|
||||
let render_toggle = {
|
||||
let confirm_command = confirm_command.clone();
|
||||
@@ -1994,29 +2011,62 @@ impl ContextEditor {
|
||||
cx,
|
||||
);
|
||||
|
||||
let block_ids = editor.insert_blocks(
|
||||
updated
|
||||
.iter()
|
||||
.filter_map(|command| match &command.status {
|
||||
PendingSlashCommandStatus::Error(error) => {
|
||||
Some((command, error.clone()))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.map(|(command, error_message)| BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
height: 1,
|
||||
placement: BlockPlacement::Below(Anchor {
|
||||
buffer_id: Some(buffer_id),
|
||||
excerpt_id,
|
||||
text_anchor: command.source_range.start,
|
||||
}),
|
||||
render: slash_command_error_block_renderer(error_message),
|
||||
priority: 0,
|
||||
}),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
self.pending_slash_command_creases.extend(
|
||||
updated
|
||||
.iter()
|
||||
.map(|command| command.source_range.clone())
|
||||
.zip(crease_ids),
|
||||
);
|
||||
|
||||
self.pending_slash_command_blocks.extend(
|
||||
updated
|
||||
.iter()
|
||||
.map(|command| command.source_range.clone())
|
||||
.zip(block_ids),
|
||||
);
|
||||
})
|
||||
}
|
||||
ContextEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
self.update_invoked_slash_command(*command_id, cx);
|
||||
}
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
self.insert_slash_command_output_sections([section.clone()], false, cx);
|
||||
}
|
||||
ContextEvent::SlashCommandFinished {
|
||||
output_range: _output_range,
|
||||
run_commands_in_ranges,
|
||||
output_range,
|
||||
sections,
|
||||
run_commands_in_output,
|
||||
expand_result,
|
||||
} => {
|
||||
for range in run_commands_in_ranges {
|
||||
self.insert_slash_command_output_sections(
|
||||
sections.iter().cloned(),
|
||||
*expand_result,
|
||||
cx,
|
||||
);
|
||||
|
||||
if *run_commands_in_output {
|
||||
let commands = self.context.update(cx, |context, cx| {
|
||||
context.reparse(cx);
|
||||
context
|
||||
.pending_commands_for_range(range.clone(), cx)
|
||||
.pending_commands_for_range(output_range.clone(), cx)
|
||||
.to_vec()
|
||||
});
|
||||
|
||||
@@ -2026,6 +2076,7 @@ impl ContextEditor {
|
||||
&command.name,
|
||||
&command.arguments,
|
||||
false,
|
||||
false,
|
||||
self.workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -2068,7 +2119,8 @@ impl ContextEditor {
|
||||
IconName::PocketKnife,
|
||||
format!("Tool Result: {tool_use_id}").into(),
|
||||
),
|
||||
..Default::default()
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
};
|
||||
let render_trailer =
|
||||
move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||
@@ -2106,76 +2158,6 @@ impl ContextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_invoked_slash_command(
|
||||
&mut self,
|
||||
command_id: SlashCommandId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if let Some(invoked_slash_command) =
|
||||
self.context.read(cx).invoked_slash_command(&command_id)
|
||||
{
|
||||
if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
|
||||
buffer.as_singleton().unwrap();
|
||||
|
||||
let start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
|
||||
.unwrap();
|
||||
let end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
|
||||
.unwrap();
|
||||
editor.remove_folds_with_type(
|
||||
&[start..end],
|
||||
TypeId::of::<PendingSlashCommand>(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
editor.remove_creases(
|
||||
HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
|
||||
cx,
|
||||
);
|
||||
} else if let hash_map::Entry::Vacant(entry) =
|
||||
self.invoked_slash_command_creases.entry(command_id)
|
||||
{
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let (&excerpt_id, _buffer_id, _buffer_snapshot) =
|
||||
buffer.as_singleton().unwrap();
|
||||
let context = self.context.downgrade();
|
||||
let crease_start = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.start)
|
||||
.unwrap();
|
||||
let crease_end = buffer
|
||||
.anchor_in_excerpt(excerpt_id, invoked_slash_command.range.end)
|
||||
.unwrap();
|
||||
let fold_placeholder =
|
||||
invoked_slash_command_fold_placeholder(command_id, context);
|
||||
let crease_ids = editor.insert_creases(
|
||||
[Crease::new(
|
||||
crease_start..crease_end,
|
||||
fold_placeholder.clone(),
|
||||
fold_toggle("invoked-slash-command"),
|
||||
|_row, _folded, _cx| Empty.into_any(),
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
editor.fold_ranges([(crease_start..crease_end, fold_placeholder)], false, cx);
|
||||
entry.insert(crease_ids[0]);
|
||||
} else {
|
||||
cx.notify()
|
||||
}
|
||||
} else {
|
||||
editor.remove_creases(
|
||||
HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
|
||||
cx,
|
||||
);
|
||||
cx.notify();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn patches_updated(
|
||||
&mut self,
|
||||
removed: &Vec<Range<text::Anchor>>,
|
||||
@@ -2247,7 +2229,8 @@ impl ContextEditor {
|
||||
.unwrap_or_else(|| Empty.into_any())
|
||||
})
|
||||
},
|
||||
..Default::default()
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
};
|
||||
|
||||
let should_refold;
|
||||
@@ -2305,7 +2288,7 @@ impl ContextEditor {
|
||||
}
|
||||
|
||||
if should_refold {
|
||||
editor.unfold_ranges(&[patch_start..patch_end], true, false, cx);
|
||||
editor.unfold_ranges([patch_start..patch_end], true, false, cx);
|
||||
editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
|
||||
}
|
||||
}
|
||||
@@ -2351,8 +2334,8 @@ impl ContextEditor {
|
||||
section.icon,
|
||||
section.label.clone(),
|
||||
),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
..Default::default()
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any_element(),
|
||||
@@ -2596,29 +2579,6 @@ impl ContextEditor {
|
||||
})
|
||||
}
|
||||
|
||||
fn esc_kbd(cx: &WindowContext) -> Div {
|
||||
let colors = cx.theme().colors().clone();
|
||||
|
||||
h_flex()
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||
.text_size(TextSize::XSmall.rems(cx))
|
||||
.text_color(colors.text_muted)
|
||||
.child("Press")
|
||||
.child(
|
||||
h_flex()
|
||||
.rounded_md()
|
||||
.px_1()
|
||||
.mr_0p5()
|
||||
.border_1()
|
||||
.border_color(theme::color_alpha(colors.border_variant, 0.6))
|
||||
.bg(theme::color_alpha(colors.element_background, 0.6))
|
||||
.child("esc"),
|
||||
)
|
||||
.child("to cancel")
|
||||
}
|
||||
|
||||
fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
@@ -2634,7 +2594,6 @@ impl ContextEditor {
|
||||
let render_block = |message: MessageMetadata| -> RenderBlock {
|
||||
Box::new({
|
||||
let context = self.context.clone();
|
||||
|
||||
move |cx| {
|
||||
let message_id = MessageId(message.timestamp);
|
||||
let llm_loading = message.role == Role::Assistant
|
||||
@@ -2656,7 +2615,7 @@ impl ContextEditor {
|
||||
"pulsating-label",
|
||||
Animation::new(Duration::from_secs(2))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.4, 0.8)),
|
||||
.with_easing(pulsating_between(0.3, 0.9)),
|
||||
|label, delta| label.alpha(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
@@ -2667,7 +2626,7 @@ impl ContextEditor {
|
||||
spinner = Some(
|
||||
Icon::new(IconName::ArrowCircle)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Info)
|
||||
.color(Color::Muted)
|
||||
.with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
@@ -2679,7 +2638,20 @@ impl ContextEditor {
|
||||
)
|
||||
.into_any_element(),
|
||||
);
|
||||
note = Some(Self::esc_kbd(cx).into_any_element());
|
||||
note = Some(
|
||||
div()
|
||||
.font(
|
||||
theme::ThemeSettings::get_global(cx)
|
||||
.buffer_font
|
||||
.clone(),
|
||||
)
|
||||
.child(
|
||||
Label::new("Press 'esc' to cancel")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
(animated_label, spinner, note)
|
||||
}
|
||||
@@ -2694,7 +2666,7 @@ impl ContextEditor {
|
||||
|
||||
let sender = h_flex()
|
||||
.items_center()
|
||||
.gap_2p5()
|
||||
.gap_2()
|
||||
.child(
|
||||
ButtonLike::new("role")
|
||||
.style(ButtonStyle::Filled)
|
||||
@@ -3292,12 +3264,13 @@ impl ContextEditor {
|
||||
Crease::new(
|
||||
start..end,
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
render: render_fold_icon_button(
|
||||
weak_editor.clone(),
|
||||
metadata.crease.icon,
|
||||
metadata.crease.label.clone(),
|
||||
),
|
||||
..Default::default()
|
||||
merge_adjacent: false,
|
||||
},
|
||||
render_slash_command_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
@@ -4963,8 +4936,8 @@ fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) ->
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5008,7 +4981,7 @@ fn render_pending_slash_command_gutter_decoration(
|
||||
|
||||
fn render_docs_slash_command_trailer(
|
||||
row: MultiBufferRow,
|
||||
command: ParsedSlashCommand,
|
||||
command: PendingSlashCommand,
|
||||
cx: &mut WindowContext,
|
||||
) -> AnyElement {
|
||||
if command.arguments.is_empty() {
|
||||
@@ -5092,51 +5065,17 @@ fn make_lsp_adapter_delegate(
|
||||
})
|
||||
}
|
||||
|
||||
enum PendingSlashCommand {}
|
||||
|
||||
fn invoked_slash_command_fold_placeholder(
|
||||
command_id: SlashCommandId,
|
||||
context: WeakModel<Context>,
|
||||
) -> FoldPlaceholder {
|
||||
FoldPlaceholder {
|
||||
constrain_width: false,
|
||||
merge_adjacent: false,
|
||||
render: Arc::new(move |fold_id, _, cx| {
|
||||
let Some(context) = context.upgrade() else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
let Some(command) = context.read(cx).invoked_slash_command(&command_id) else {
|
||||
return Empty.into_any();
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id(fold_id)
|
||||
.px_1()
|
||||
.ml_6()
|
||||
.gap_2()
|
||||
.bg(cx.theme().colors().surface_background)
|
||||
.rounded_md()
|
||||
.child(Label::new(format!("/{}", command.name.clone())))
|
||||
.map(|parent| match &command.status {
|
||||
InvokedSlashCommandStatus::Running(_) => {
|
||||
parent.child(Icon::new(IconName::ArrowCircle).with_animation(
|
||||
"arrow-circle",
|
||||
Animation::new(Duration::from_secs(4)).repeat(),
|
||||
|icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
|
||||
))
|
||||
}
|
||||
InvokedSlashCommandStatus::Error(message) => parent.child(
|
||||
Label::new(format!("error: {message}"))
|
||||
.single_line()
|
||||
.color(Color::Error),
|
||||
),
|
||||
InvokedSlashCommandStatus::Finished => parent,
|
||||
})
|
||||
.into_any_element()
|
||||
}),
|
||||
type_tag: Some(TypeId::of::<PendingSlashCommand>()),
|
||||
}
|
||||
fn slash_command_error_block_renderer(message: String) -> RenderBlock {
|
||||
Box::new(move |_| {
|
||||
div()
|
||||
.pl_6()
|
||||
.child(
|
||||
Label::new(format!("error: {}", message))
|
||||
.single_line()
|
||||
.color(Color::Error),
|
||||
)
|
||||
.into_any()
|
||||
})
|
||||
}
|
||||
|
||||
enum TokenState {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,19 +2,14 @@ use super::{AssistantEdit, MessageCacheMetadata};
|
||||
use crate::{
|
||||
assistant_panel, prompt_library, slash_command::file_command, AssistantEditKind, CacheStatus,
|
||||
Context, ContextEvent, ContextId, ContextOperation, MessageId, MessageStatus, PromptBuilder,
|
||||
SlashCommandId,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
|
||||
SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult,
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandRegistry, SlashCommandResult,
|
||||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashSet;
|
||||
use fs::FakeFs;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
stream::{self, StreamExt},
|
||||
};
|
||||
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
|
||||
use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
|
||||
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
|
||||
@@ -32,8 +27,8 @@ use std::{
|
||||
rc::Rc,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId, ToOffset};
|
||||
use ui::{Context as _, IconName, WindowContext};
|
||||
use text::{network::Network, OffsetRangeExt as _, ReplicaId};
|
||||
use ui::{Context as _, WindowContext};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
@@ -386,41 +381,20 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
let context =
|
||||
cx.new_model(|cx| Context::local(registry.clone(), None, None, prompt_builder.clone(), cx));
|
||||
|
||||
#[derive(Default)]
|
||||
struct ContextRanges {
|
||||
parsed_commands: HashSet<Range<language::Anchor>>,
|
||||
command_outputs: HashMap<SlashCommandId, Range<language::Anchor>>,
|
||||
output_sections: HashSet<Range<language::Anchor>>,
|
||||
}
|
||||
|
||||
let context_ranges = Rc::new(RefCell::new(ContextRanges::default()));
|
||||
let output_ranges = Rc::new(RefCell::new(HashSet::default()));
|
||||
context.update(cx, |_, cx| {
|
||||
cx.subscribe(&context, {
|
||||
let context_ranges = context_ranges.clone();
|
||||
move |context, _, event, _| {
|
||||
let mut context_ranges = context_ranges.borrow_mut();
|
||||
match event {
|
||||
ContextEvent::InvokedSlashCommandChanged { command_id } => {
|
||||
let command = context.invoked_slash_command(command_id).unwrap();
|
||||
context_ranges
|
||||
.command_outputs
|
||||
.insert(*command_id, command.range.clone());
|
||||
let ranges = output_ranges.clone();
|
||||
move |_, _, event, _| match event {
|
||||
ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
|
||||
for range in removed {
|
||||
ranges.borrow_mut().remove(range);
|
||||
}
|
||||
ContextEvent::ParsedSlashCommandsUpdated { removed, updated } => {
|
||||
for range in removed {
|
||||
context_ranges.parsed_commands.remove(range);
|
||||
}
|
||||
for command in updated {
|
||||
context_ranges
|
||||
.parsed_commands
|
||||
.insert(command.source_range.clone());
|
||||
}
|
||||
for command in updated {
|
||||
ranges.borrow_mut().insert(command.source_range.clone());
|
||||
}
|
||||
ContextEvent::SlashCommandOutputSectionAdded { section } => {
|
||||
context_ranges.output_sections.insert(section.range.clone());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
@@ -432,12 +406,14 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
assert_text_and_output_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/lib.rs»"
|
||||
.unindent(),
|
||||
&output_ranges.borrow(),
|
||||
"
|
||||
«/file src/lib.rs»
|
||||
"
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -446,12 +422,14 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
let edit_offset = buffer.text().find("lib.rs").unwrap();
|
||||
buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
assert_text_and_output_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/main.rs»"
|
||||
.unindent(),
|
||||
&output_ranges.borrow(),
|
||||
"
|
||||
«/file src/main.rs»
|
||||
"
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -464,180 +442,36 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
assert_text_and_output_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
/unknown src/main.rs"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Undoing the insertion of an non-existent slash command resorts the previous one.
|
||||
buffer.update(cx, |buffer, cx| buffer.undo(cx));
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
«/file src/main.rs»"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
let (command_output_tx, command_output_rx) = mpsc::unbounded();
|
||||
context.update(cx, |context, cx| {
|
||||
let command_source_range = context.parsed_slash_commands[0].source_range.clone();
|
||||
context.insert_command_output(
|
||||
command_source_range,
|
||||
"file",
|
||||
Task::ready(Ok(command_output_rx.boxed())),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
…⟧
|
||||
&output_ranges.borrow(),
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::Ai,
|
||||
label: "src/main.rs".into(),
|
||||
metadata: None,
|
||||
}))
|
||||
.unwrap();
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::Content("src/main.rs".into())))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
src/main.rs…⟧
|
||||
/unknown src/main.rs
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::Content("\nfn main() {}".into())))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
src/main.rs
|
||||
fn main() {}…⟧
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
command_output_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦«/file src/main.rs»
|
||||
⟪src/main.rs
|
||||
fn main() {}⟫…⟧
|
||||
"
|
||||
.unindent(),
|
||||
cx,
|
||||
);
|
||||
|
||||
drop(command_output_tx);
|
||||
cx.run_until_parked();
|
||||
assert_text_and_context_ranges(
|
||||
&buffer,
|
||||
&context_ranges,
|
||||
&"
|
||||
⟦⟪src/main.rs
|
||||
fn main() {}⟫⟧
|
||||
"
|
||||
.unindent(),
|
||||
.unindent()
|
||||
.trim_end(),
|
||||
cx,
|
||||
);
|
||||
|
||||
#[track_caller]
|
||||
fn assert_text_and_context_ranges(
|
||||
fn assert_text_and_output_ranges(
|
||||
buffer: &Model<Buffer>,
|
||||
ranges: &RefCell<ContextRanges>,
|
||||
ranges: &HashSet<Range<language::Anchor>>,
|
||||
expected_marked_text: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let mut actual_marked_text = String::new();
|
||||
buffer.update(cx, |buffer, _| {
|
||||
struct Endpoint {
|
||||
offset: usize,
|
||||
marker: char,
|
||||
}
|
||||
|
||||
let ranges = ranges.borrow();
|
||||
let mut endpoints = Vec::new();
|
||||
for range in ranges.command_outputs.values() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '⟦',
|
||||
});
|
||||
}
|
||||
for range in ranges.parsed_commands.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '«',
|
||||
});
|
||||
}
|
||||
for range in ranges.output_sections.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.start.to_offset(buffer),
|
||||
marker: '⟪',
|
||||
});
|
||||
}
|
||||
|
||||
for range in ranges.output_sections.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '⟫',
|
||||
});
|
||||
}
|
||||
for range in ranges.parsed_commands.iter() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '»',
|
||||
});
|
||||
}
|
||||
for range in ranges.command_outputs.values() {
|
||||
endpoints.push(Endpoint {
|
||||
offset: range.end.to_offset(buffer),
|
||||
marker: '⟧',
|
||||
});
|
||||
}
|
||||
|
||||
endpoints.sort_by_key(|endpoint| endpoint.offset);
|
||||
let mut offset = 0;
|
||||
for endpoint in endpoints {
|
||||
actual_marked_text.extend(buffer.text_for_range(offset..endpoint.offset));
|
||||
actual_marked_text.push(endpoint.marker);
|
||||
offset = endpoint.offset;
|
||||
}
|
||||
actual_marked_text.extend(buffer.text_for_range(offset..buffer.len()));
|
||||
let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
|
||||
let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
|
||||
let mut ranges = ranges
|
||||
.iter()
|
||||
.map(|range| range.to_offset(buffer))
|
||||
.collect::<Vec<_>>();
|
||||
ranges.sort_by_key(|a| a.start);
|
||||
(buffer.text(), ranges)
|
||||
});
|
||||
|
||||
assert_eq!(actual_marked_text, expected_marked_text);
|
||||
assert_eq!(actual_text, expected_text);
|
||||
assert_eq!(actual_ranges, expected_ranges);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,57 +1063,44 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
offset + 1..offset + 1 + command_text.len()
|
||||
});
|
||||
|
||||
let output_len = rng.gen_range(1..=10);
|
||||
let output_text = RandomCharIter::new(&mut rng)
|
||||
.filter(|c| *c != '\r')
|
||||
.take(10)
|
||||
.take(output_len)
|
||||
.collect::<String>();
|
||||
|
||||
let mut events = vec![Ok(SlashCommandEvent::StartMessage {
|
||||
role: Role::User,
|
||||
merge_same_roles: true,
|
||||
})];
|
||||
|
||||
let num_sections = rng.gen_range(0..=3);
|
||||
let mut section_start = 0;
|
||||
let mut sections = Vec::with_capacity(num_sections);
|
||||
for _ in 0..num_sections {
|
||||
let mut section_end = rng.gen_range(section_start..=output_text.len());
|
||||
while !output_text.is_char_boundary(section_end) {
|
||||
section_end += 1;
|
||||
}
|
||||
events.push(Ok(SlashCommandEvent::StartSection {
|
||||
icon: IconName::Ai,
|
||||
let section_start = rng.gen_range(0..output_len);
|
||||
let section_end = rng.gen_range(section_start..=output_len);
|
||||
sections.push(SlashCommandOutputSection {
|
||||
range: section_start..section_end,
|
||||
icon: ui::IconName::Ai,
|
||||
label: "section".into(),
|
||||
metadata: None,
|
||||
}));
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: output_text[section_start..section_end].to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
events.push(Ok(SlashCommandEvent::EndSection { metadata: None }));
|
||||
section_start = section_end;
|
||||
}
|
||||
|
||||
if section_start < output_text.len() {
|
||||
events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
|
||||
text: output_text[section_start..].to_string(),
|
||||
run_commands_in_text: false,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Context {}: insert slash command output at {:?} with {:?} events",
|
||||
"Context {}: insert slash command output at {:?} with {:?}",
|
||||
context_index,
|
||||
command_range,
|
||||
events.len()
|
||||
sections
|
||||
);
|
||||
|
||||
let command_range = context.buffer.read(cx).anchor_after(command_range.start)
|
||||
..context.buffer.read(cx).anchor_after(command_range.end);
|
||||
context.insert_command_output(
|
||||
command_range,
|
||||
"/command",
|
||||
Task::ready(Ok(stream::iter(events).boxed())),
|
||||
Task::ready(Ok(SlashCommandOutput {
|
||||
text: output_text,
|
||||
sections,
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
.to_event_stream())),
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -1357,7 +1178,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
|
||||
let first_context = contexts[0].read(cx);
|
||||
for context in &contexts[1..] {
|
||||
let context = context.read(cx);
|
||||
assert!(context.pending_ops.is_empty(), "pending ops: {:?}", context.pending_ops);
|
||||
assert!(context.pending_ops.is_empty());
|
||||
assert_eq!(
|
||||
context.buffer.read(cx).text(),
|
||||
first_context.buffer.read(cx).text(),
|
||||
|
||||
@@ -53,9 +53,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use text::{OffsetRangeExt, ToPoint as _};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip,
|
||||
};
|
||||
use ui::{prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
|
||||
use util::{RangeExt, ResultExt};
|
||||
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
|
||||
|
||||
@@ -1901,58 +1899,21 @@ impl PromptEditor {
|
||||
let codegen = self.codegen.read(cx);
|
||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
let default_model = model_registry.active_model();
|
||||
let alternative_models = model_registry.inline_alternative_models();
|
||||
|
||||
let get_model_name = |index: usize| -> String {
|
||||
let name = |model: &Arc<dyn LanguageModel>| model.name().0.to_string();
|
||||
|
||||
match index {
|
||||
0 => default_model.as_ref().map_or_else(String::new, name),
|
||||
index if index <= alternative_models.len() => alternative_models
|
||||
.get(index - 1)
|
||||
.map_or_else(String::new, name),
|
||||
_ => String::new(),
|
||||
}
|
||||
};
|
||||
|
||||
let total_models = alternative_models.len() + 1;
|
||||
|
||||
if total_models <= 1 {
|
||||
return div().into_any_element();
|
||||
}
|
||||
|
||||
let current_index = codegen.active_alternative;
|
||||
let prev_index = (current_index + total_models - 1) % total_models;
|
||||
let next_index = (current_index + 1) % total_models;
|
||||
|
||||
let prev_model_name = get_model_name(prev_index);
|
||||
let next_model_name = get_model_name(next_index);
|
||||
|
||||
h_flex()
|
||||
.child(
|
||||
IconButton::new("previous", IconName::ChevronLeft)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(disabled || current_index == 0)
|
||||
.disabled(disabled)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
cx.new_view(|cx| {
|
||||
let mut tooltip = Tooltip::new("Previous Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != 0 {
|
||||
tooltip = tooltip.meta(prev_model_name.clone());
|
||||
}
|
||||
tooltip
|
||||
})
|
||||
.into()
|
||||
Tooltip::for_action_in(
|
||||
"Previous Alternative",
|
||||
&CyclePreviousInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
@@ -1976,25 +1937,17 @@ impl PromptEditor {
|
||||
.child(
|
||||
IconButton::new("next", IconName::ChevronRight)
|
||||
.icon_color(Color::Muted)
|
||||
.disabled(disabled || current_index == total_models - 1)
|
||||
.disabled(disabled)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip({
|
||||
let focus_handle = self.editor.focus_handle(cx);
|
||||
move |cx| {
|
||||
cx.new_view(|cx| {
|
||||
let mut tooltip = Tooltip::new("Next Alternative").key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
),
|
||||
);
|
||||
if !disabled && current_index != total_models - 1 {
|
||||
tooltip = tooltip.meta(next_model_name.clone());
|
||||
}
|
||||
tooltip
|
||||
})
|
||||
.into()
|
||||
Tooltip::for_action_in(
|
||||
"Next Alternative",
|
||||
&CycleNextInlineAssist,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
|
||||
@@ -127,6 +127,7 @@ impl SlashCommandCompletionProvider {
|
||||
&command_name,
|
||||
&[],
|
||||
true,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
@@ -211,6 +212,7 @@ impl SlashCommandCompletionProvider {
|
||||
&command_name,
|
||||
&completed_arguments,
|
||||
true,
|
||||
false,
|
||||
workspace.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -58,7 +58,6 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
let mut paths = HashSet::default();
|
||||
let mut file_command_old_outputs = Vec::new();
|
||||
let mut file_command_new_outputs = Vec::new();
|
||||
|
||||
for section in context_slash_command_output_sections.iter().rev() {
|
||||
if let Some(metadata) = section
|
||||
.metadata
|
||||
@@ -85,7 +84,6 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let mut output = SlashCommandOutput::default();
|
||||
let mut changes_detected = false;
|
||||
|
||||
let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
|
||||
for (old_text, new_output) in file_command_old_outputs
|
||||
@@ -98,7 +96,6 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
if let Some(file_command_range) = new_output.sections.first() {
|
||||
let new_text = &new_output.text[file_command_range.range.clone()];
|
||||
if old_text.chars().ne(new_text.chars()) {
|
||||
changes_detected = true;
|
||||
output.sections.extend(new_output.sections.into_iter().map(
|
||||
|section| SlashCommandOutputSection {
|
||||
range: output.text.len() + section.range.start
|
||||
@@ -115,10 +112,6 @@ impl SlashCommand for DeltaSlashCommand {
|
||||
}
|
||||
}
|
||||
|
||||
if !changes_detected {
|
||||
return Err(anyhow!("no new changes detected"));
|
||||
}
|
||||
|
||||
Ok(output.to_event_stream())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
@@ -90,6 +96,12 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
},
|
||||
)))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
|
||||
for n in 1..=10 {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
@@ -107,6 +119,12 @@ impl SlashCommand for StreamingExampleSlashCommand {
|
||||
)))?;
|
||||
events_tx
|
||||
.unbounded_send(Ok(SlashCommandEvent::EndSection { metadata: None }))?;
|
||||
events_tx.unbounded_send(Ok(SlashCommandEvent::Content(
|
||||
SlashCommandContent::Text {
|
||||
text: "\n".into(),
|
||||
run_commands_in_text: false,
|
||||
},
|
||||
)))?;
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
|
||||
@@ -18,7 +18,6 @@ derive_more.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
parking_lot.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -5,7 +5,6 @@ use futures::stream::{self, BoxStream};
|
||||
use futures::StreamExt;
|
||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||
pub use language_model::Role;
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use slash_command_registry::*;
|
||||
use std::{
|
||||
@@ -104,7 +103,7 @@ pub type RenderFoldPlaceholder = Arc<
|
||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
||||
>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SlashCommandContent {
|
||||
Text {
|
||||
text: String,
|
||||
@@ -112,21 +111,8 @@ pub enum SlashCommandContent {
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for SlashCommandContent {
|
||||
fn from(text: &'a str) -> Self {
|
||||
Self::Text {
|
||||
text: text.into(),
|
||||
run_commands_in_text: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SlashCommandEvent {
|
||||
StartMessage {
|
||||
role: Role,
|
||||
merge_same_roles: bool,
|
||||
},
|
||||
StartSection {
|
||||
icon: IconName,
|
||||
label: SharedString,
|
||||
@@ -246,7 +232,6 @@ impl SlashCommandOutput {
|
||||
output.sections.push(section);
|
||||
}
|
||||
}
|
||||
SlashCommandEvent::StartMessage { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use call::ActiveCall;
|
||||
use collections::HashSet;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _};
|
||||
use gpui::{BackgroundExecutor, Context as _, TestAppContext, UpdateGlobal as _};
|
||||
use http_client::BlockedHttpClient;
|
||||
use language::{
|
||||
language_settings::{
|
||||
@@ -31,12 +31,6 @@ async fn test_sharing_an_ssh_remote_project(
|
||||
server_cx: &mut TestAppContext,
|
||||
) {
|
||||
let executor = cx_a.executor();
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -205,13 +199,6 @@ async fn test_ssh_collaboration_git_branches(
|
||||
cx_b.set_name("b");
|
||||
server_cx.set_name("server");
|
||||
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
@@ -342,13 +329,6 @@ async fn test_ssh_collaboration_formatting_with_prettier(
|
||||
cx_b.set_name("b");
|
||||
server_cx.set_name("server");
|
||||
|
||||
cx_a.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
|
||||
let mut server = TestServer::start(executor.clone()).await;
|
||||
let client_a = server.create_client(cx_a, "user_a").await;
|
||||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
|
||||
@@ -29,38 +29,32 @@ pub enum Role {
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||
pub enum Model {
|
||||
#[default]
|
||||
#[serde(rename = "gpt-4o")]
|
||||
#[serde(alias = "gpt-4o", rename = "gpt-4o-2024-05-13")]
|
||||
Gpt4o,
|
||||
#[serde(rename = "gpt-4o-mini")]
|
||||
Gpt4oMini,
|
||||
#[serde(rename = "gpt-4")]
|
||||
#[serde(alias = "gpt-4", rename = "gpt-4")]
|
||||
Gpt4,
|
||||
#[serde(rename = "gpt-4-turbo")]
|
||||
Gpt4Turbo,
|
||||
#[serde(rename = "gpt-3.5-turbo")]
|
||||
#[serde(alias = "gpt-3.5-turbo", rename = "gpt-3.5-turbo")]
|
||||
Gpt3_5Turbo,
|
||||
#[serde(rename = "o1-preview")]
|
||||
#[serde(alias = "o1-preview", rename = "o1-preview-2024-09-12")]
|
||||
O1Preview,
|
||||
#[serde(rename = "o1-mini")]
|
||||
#[serde(alias = "o1-mini", rename = "o1-mini-2024-09-12")]
|
||||
O1Mini,
|
||||
#[serde(rename = "claude-3.5-sonnet")]
|
||||
#[serde(alias = "claude-3-5-sonnet", rename = "claude-3.5-sonnet")]
|
||||
Claude3_5Sonnet,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn uses_streaming(&self) -> bool {
|
||||
match self {
|
||||
Self::Gpt4o | Self::Gpt4 | Self::Gpt3_5Turbo | Self::Claude3_5Sonnet => true,
|
||||
Self::O1Mini | Self::O1Preview => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_id(id: &str) -> Result<Self> {
|
||||
match id {
|
||||
"gpt-4o" => Ok(Self::Gpt4o),
|
||||
"gpt-4o-mini" => Ok(Self::Gpt4oMini),
|
||||
"gpt-4" => Ok(Self::Gpt4),
|
||||
"gpt-4-turbo" => Ok(Self::Gpt4Turbo),
|
||||
"gpt-3.5-turbo" => Ok(Self::Gpt3_5Turbo),
|
||||
"o1-preview" => Ok(Self::O1Preview),
|
||||
"o1-mini" => Ok(Self::O1Mini),
|
||||
@@ -73,21 +67,18 @@ impl Model {
|
||||
match self {
|
||||
Self::Gpt3_5Turbo => "gpt-3.5-turbo",
|
||||
Self::Gpt4 => "gpt-4",
|
||||
Self::Gpt4Turbo => "gpt-4-turbo",
|
||||
Self::Gpt4o => "gpt-4o",
|
||||
Self::Gpt4oMini => "gpt-4o-mini",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "claude-3-5-sonnet",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Gpt3_5Turbo => "GPT-3.5",
|
||||
Self::Gpt4 => "GPT-4",
|
||||
Self::Gpt4Turbo => "GPT-4 Turbo",
|
||||
Self::Gpt4o => "GPT-4o",
|
||||
Self::Gpt4oMini => "GPT-4o Mini",
|
||||
Self::O1Mini => "o1-mini",
|
||||
Self::O1Preview => "o1-preview",
|
||||
Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
|
||||
@@ -96,13 +87,11 @@ impl Model {
|
||||
|
||||
pub fn max_token_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Gpt4o => 64000,
|
||||
Self::Gpt4oMini => 12288,
|
||||
Self::Gpt4 => 32768,
|
||||
Self::Gpt4Turbo => 64000,
|
||||
Self::Gpt3_5Turbo => 12288,
|
||||
Self::O1Mini => 20000,
|
||||
Self::O1Preview => 20000,
|
||||
Self::Gpt4o => 128000,
|
||||
Self::Gpt4 => 8192,
|
||||
Self::Gpt3_5Turbo => 16385,
|
||||
Self::O1Mini => 128000,
|
||||
Self::O1Preview => 128000,
|
||||
Self::Claude3_5Sonnet => 200_000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use block_map::{BlockRow, BlockSnapshot};
|
||||
use collections::{HashMap, HashSet};
|
||||
pub use crease_map::*;
|
||||
pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint};
|
||||
use fold_map::{FoldMap, FoldMapWriter, FoldOffset, FoldSnapshot};
|
||||
use fold_map::{FoldMap, FoldSnapshot};
|
||||
use gpui::{
|
||||
AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
|
||||
};
|
||||
@@ -65,7 +65,7 @@ use std::{
|
||||
};
|
||||
use sum_tree::{Bias, TreeMap};
|
||||
use tab_map::{TabMap, TabSnapshot};
|
||||
use text::{Edit, LineIndent};
|
||||
use text::LineIndent;
|
||||
use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use wrap_map::{WrapMap, WrapSnapshot};
|
||||
@@ -206,41 +206,10 @@ impl DisplayMap {
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates folds for the given ranges.
|
||||
pub fn fold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.update_fold_map(cx, |fold_map| fold_map.fold(ranges))
|
||||
}
|
||||
|
||||
/// Removes any folds with the given ranges.
|
||||
pub fn remove_folds_with_type<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
type_id: TypeId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.update_fold_map(cx, |fold_map| fold_map.remove_folds(ranges, type_id))
|
||||
}
|
||||
|
||||
/// Removes any folds whose ranges intersect any of the given ranges.
|
||||
pub fn unfold_intersecting<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
self.update_fold_map(cx, |fold_map| {
|
||||
fold_map.unfold_intersecting(ranges, inclusive)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_fold_map(
|
||||
&mut self,
|
||||
cx: &mut ModelContext<Self>,
|
||||
callback: impl FnOnce(&mut FoldMapWriter) -> (FoldSnapshot, Vec<Edit<FoldOffset>>),
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
@@ -252,7 +221,31 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = callback(&mut fold_map);
|
||||
let (snapshot, edits) = fold_map.fold(ranges);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
}
|
||||
|
||||
pub fn unfold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
|
||||
let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
self.block_map.read(snapshot, edits);
|
||||
let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
|
||||
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
|
||||
let (snapshot, edits) = self
|
||||
.wrap_map
|
||||
@@ -1449,7 +1442,7 @@ pub mod tests {
|
||||
if rng.gen() && fold_count > 0 {
|
||||
log::info!("unfolding ranges: {:?}", ranges);
|
||||
map.update(cx, |map, cx| {
|
||||
map.unfold_intersecting(ranges, true, cx);
|
||||
map.unfold(ranges, true, cx);
|
||||
});
|
||||
} else {
|
||||
log::info!("folding ranges: {:?}", ranges);
|
||||
|
||||
@@ -6,14 +6,12 @@ use gpui::{AnyElement, ElementId, WindowContext};
|
||||
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
|
||||
use multi_buffer::{Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, ToOffset};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
cmp::{self, Ordering},
|
||||
fmt, iter,
|
||||
ops::{Add, AddAssign, Deref, DerefMut, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
use sum_tree::{Bias, Cursor, FilterCursor, SumTree, Summary};
|
||||
use ui::IntoElement as _;
|
||||
use util::post_inc;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -24,29 +22,17 @@ pub struct FoldPlaceholder {
|
||||
pub constrain_width: bool,
|
||||
/// If true, merges the fold with an adjacent one.
|
||||
pub merge_adjacent: bool,
|
||||
/// Category of the fold. Useful for carefully removing from overlapping folds.
|
||||
pub type_tag: Option<TypeId>,
|
||||
}
|
||||
|
||||
impl Default for FoldPlaceholder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FoldPlaceholder {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn test() -> Self {
|
||||
use gpui::IntoElement;
|
||||
|
||||
Self {
|
||||
render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()),
|
||||
constrain_width: true,
|
||||
merge_adjacent: true,
|
||||
type_tag: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,34 +173,9 @@ impl<'a> FoldMapWriter<'a> {
|
||||
(self.0.snapshot.clone(), edits)
|
||||
}
|
||||
|
||||
/// Removes any folds with the given ranges.
|
||||
pub(crate) fn remove_folds<T: ToOffset>(
|
||||
pub(crate) fn unfold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
type_id: TypeId,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
self.remove_folds_with(
|
||||
ranges,
|
||||
|fold| fold.placeholder.type_tag == Some(type_id),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Removes any folds whose ranges intersect the given ranges.
|
||||
pub(crate) fn unfold_intersecting<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
self.remove_folds_with(ranges, |_| true, inclusive)
|
||||
}
|
||||
|
||||
/// Removes any folds that intersect the given ranges and for which the given predicate
|
||||
/// returns true.
|
||||
fn remove_folds_with<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
should_unfold: impl Fn(&Fold) -> bool,
|
||||
inclusive: bool,
|
||||
) -> (FoldSnapshot, Vec<FoldEdit>) {
|
||||
let mut edits = Vec::new();
|
||||
@@ -222,23 +183,21 @@ impl<'a> FoldMapWriter<'a> {
|
||||
let snapshot = self.0.snapshot.inlay_snapshot.clone();
|
||||
let buffer = &snapshot.buffer;
|
||||
for range in ranges.into_iter() {
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
// Remove intersecting folds and add their ranges to edits that are passed to sync.
|
||||
let mut folds_cursor =
|
||||
intersecting_folds(&snapshot, &self.0.snapshot.folds, range.clone(), inclusive);
|
||||
intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive);
|
||||
while let Some(fold) = folds_cursor.item() {
|
||||
let offset_range =
|
||||
fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer);
|
||||
if should_unfold(fold) {
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
}
|
||||
fold_ixs_to_delete.push(*folds_cursor.start());
|
||||
if offset_range.end > offset_range.start {
|
||||
let inlay_range = snapshot.to_inlay_offset(offset_range.start)
|
||||
..snapshot.to_inlay_offset(offset_range.end);
|
||||
edits.push(InlayEdit {
|
||||
old: inlay_range.clone(),
|
||||
new: inlay_range,
|
||||
});
|
||||
}
|
||||
fold_ixs_to_delete.push(*folds_cursor.start());
|
||||
folds_cursor.next(buffer);
|
||||
}
|
||||
}
|
||||
@@ -706,8 +665,6 @@ impl FoldSnapshot {
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = &self.inlay_snapshot.buffer;
|
||||
let range = range.start.to_offset(buffer)..range.end.to_offset(buffer);
|
||||
let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false);
|
||||
iter::from_fn(move || {
|
||||
let item = folds.item();
|
||||
@@ -864,12 +821,15 @@ fn push_isomorphic(transforms: &mut SumTree<Transform>, summary: TextSummary) {
|
||||
}
|
||||
}
|
||||
|
||||
fn intersecting_folds<'a>(
|
||||
fn intersecting_folds<'a, T>(
|
||||
inlay_snapshot: &'a InlaySnapshot,
|
||||
folds: &'a SumTree<Fold>,
|
||||
range: Range<usize>,
|
||||
range: Range<T>,
|
||||
inclusive: bool,
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> {
|
||||
) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize>
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let buffer = &inlay_snapshot.buffer;
|
||||
let start = buffer.anchor_before(range.start.to_offset(buffer));
|
||||
let end = buffer.anchor_after(range.end.to_offset(buffer));
|
||||
@@ -1459,12 +1419,12 @@ mod tests {
|
||||
assert_eq!(snapshot4.text(), "123a⋯c123456eee");
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false);
|
||||
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false);
|
||||
let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]);
|
||||
assert_eq!(snapshot5.text(), "123a⋯c123456eee");
|
||||
|
||||
let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]);
|
||||
writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true);
|
||||
writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true);
|
||||
let (snapshot6, _) = map.read(inlay_snapshot, vec![]);
|
||||
assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee");
|
||||
}
|
||||
@@ -1953,7 +1913,7 @@ mod tests {
|
||||
log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive);
|
||||
let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]);
|
||||
snapshot_edits.push((snapshot, edits));
|
||||
let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive);
|
||||
let (snapshot, edits) = writer.unfold(to_unfold, inclusive);
|
||||
snapshot_edits.push((snapshot, edits));
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -75,9 +75,9 @@ use gpui::{
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
|
||||
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
ListSizingBehavior, Model, ModelContext, MouseButton, PaintQuad, ParentElement, Pixels, Render,
|
||||
ScrollStrategy, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
|
||||
TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
|
||||
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
|
||||
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
|
||||
ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
@@ -1016,8 +1016,7 @@ impl CompletionsMenu {
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.selected_item = 0;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1032,8 +1031,7 @@ impl CompletionsMenu {
|
||||
} else {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1048,8 +1046,7 @@ impl CompletionsMenu {
|
||||
} else {
|
||||
self.selected_item = 0;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1060,8 +1057,7 @@ impl CompletionsMenu {
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1542,8 +1538,7 @@ struct CodeActionsMenu {
|
||||
impl CodeActionsMenu {
|
||||
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
|
||||
self.selected_item = 0;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -1553,8 +1548,7 @@ impl CodeActionsMenu {
|
||||
} else {
|
||||
self.selected_item = self.actions.len() - 1;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
@@ -1564,15 +1558,13 @@ impl CodeActionsMenu {
|
||||
} else {
|
||||
self.selected_item = 0;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
|
||||
self.selected_item = self.actions.len() - 1;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -1857,7 +1849,7 @@ impl Editor {
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.unfold_ranges(
|
||||
&[fold_range.start..fold_range.end],
|
||||
[fold_range.start..fold_range.end],
|
||||
true,
|
||||
false,
|
||||
cx,
|
||||
@@ -1869,7 +1861,6 @@ impl Editor {
|
||||
.into_any()
|
||||
}),
|
||||
merge_adjacent: true,
|
||||
..Default::default()
|
||||
};
|
||||
let display_map = cx.new_model(|cx| {
|
||||
DisplayMap::new(
|
||||
@@ -2498,10 +2489,6 @@ impl Editor {
|
||||
buffer_position: language::Anchor,
|
||||
cx: &AppContext,
|
||||
) -> bool {
|
||||
if !self.snippet_stack.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(provider) = self.inline_completion_provider() {
|
||||
if let Some(show_inline_completions) = self.show_inline_completions_override {
|
||||
show_inline_completions
|
||||
@@ -6823,7 +6810,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.unfold_ranges(&unfold_ranges, true, true, cx);
|
||||
this.unfold_ranges(unfold_ranges, true, true, cx);
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
for (range, text) in edits {
|
||||
buffer.edit([(range, text)], None, cx);
|
||||
@@ -6917,7 +6904,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
self.transact(cx, |this, cx| {
|
||||
this.unfold_ranges(&unfold_ranges, true, true, cx);
|
||||
this.unfold_ranges(unfold_ranges, true, true, cx);
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
for (range, text) in edits {
|
||||
buffer.edit([(range, text)], None, cx);
|
||||
@@ -8269,7 +8256,7 @@ impl Editor {
|
||||
to_unfold.push(selection.start..selection.end);
|
||||
}
|
||||
}
|
||||
self.unfold_ranges(&to_unfold, true, true, cx);
|
||||
self.unfold_ranges(to_unfold, true, true, cx);
|
||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges(new_selection_ranges);
|
||||
});
|
||||
@@ -8400,7 +8387,7 @@ impl Editor {
|
||||
auto_scroll: Option<Autoscroll>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
this.unfold_ranges(&[range.clone()], false, true, cx);
|
||||
this.unfold_ranges([range.clone()], false, true, cx);
|
||||
this.change_selections(auto_scroll, cx, |s| {
|
||||
if replace_newest {
|
||||
s.delete(s.newest_anchor().id);
|
||||
@@ -8611,10 +8598,7 @@ impl Editor {
|
||||
|
||||
select_next_state.done = true;
|
||||
self.unfold_ranges(
|
||||
&new_selections
|
||||
.iter()
|
||||
.map(|selection| selection.range())
|
||||
.collect::<Vec<_>>(),
|
||||
new_selections.iter().map(|selection| selection.range()),
|
||||
false,
|
||||
false,
|
||||
cx,
|
||||
@@ -8683,7 +8667,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
if let Some(next_selected_range) = next_selected_range {
|
||||
self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
|
||||
self.unfold_ranges([next_selected_range.clone()], false, true, cx);
|
||||
self.change_selections(Some(Autoscroll::newest()), cx, |s| {
|
||||
if action.replace_newest {
|
||||
s.delete(s.newest_anchor().id);
|
||||
@@ -8760,7 +8744,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
self.unfold_ranges(
|
||||
&selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
|
||||
selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
|
||||
false,
|
||||
true,
|
||||
cx,
|
||||
@@ -11002,7 +10986,7 @@ impl Editor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.unfold_ranges(&ranges, true, true, cx);
|
||||
self.unfold_ranges(ranges, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_recursive(&mut self, _: &UnfoldRecursive, cx: &mut ViewContext<Self>) {
|
||||
@@ -11020,7 +11004,7 @@ impl Editor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.unfold_ranges(&ranges, true, true, cx);
|
||||
self.unfold_ranges(ranges, true, true, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
|
||||
@@ -11038,13 +11022,13 @@ impl Editor {
|
||||
.iter()
|
||||
.any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
|
||||
|
||||
self.unfold_ranges(&[intersection_range], true, autoscroll, cx)
|
||||
self.unfold_ranges(std::iter::once(intersection_range), true, autoscroll, cx)
|
||||
}
|
||||
|
||||
pub fn unfold_all(&mut self, _: &actions::UnfoldAll, cx: &mut ViewContext<Self>) {
|
||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
self.unfold_ranges(
|
||||
&[Point::zero()..display_map.max_point().to_point(&display_map)],
|
||||
[Point::zero()..display_map.max_point().to_point(&display_map)],
|
||||
true,
|
||||
true,
|
||||
cx,
|
||||
@@ -11120,63 +11104,39 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes any folds whose ranges intersect any of the given ranges.
|
||||
pub fn unfold_ranges<T: ToOffset + Clone>(
|
||||
&mut self,
|
||||
ranges: &[Range<T>],
|
||||
ranges: impl IntoIterator<Item = Range<T>>,
|
||||
inclusive: bool,
|
||||
auto_scroll: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
|
||||
map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes any folds with the given ranges.
|
||||
pub fn remove_folds_with_type<T: ToOffset + Clone>(
|
||||
&mut self,
|
||||
ranges: &[Range<T>],
|
||||
type_id: TypeId,
|
||||
auto_scroll: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
|
||||
map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_folds_with<T: ToOffset + Clone>(
|
||||
&mut self,
|
||||
ranges: &[Range<T>],
|
||||
auto_scroll: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
update: impl FnOnce(&mut DisplayMap, &mut ModelContext<DisplayMap>),
|
||||
) {
|
||||
if ranges.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut unfold_ranges = Vec::new();
|
||||
let mut buffers_affected = HashMap::default();
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
for range in ranges {
|
||||
if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
|
||||
buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
|
||||
};
|
||||
unfold_ranges.push(range);
|
||||
}
|
||||
|
||||
self.display_map.update(cx, update);
|
||||
if auto_scroll {
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
}
|
||||
let mut ranges = unfold_ranges.into_iter().peekable();
|
||||
if ranges.peek().is_some() {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.unfold(ranges, inclusive, cx));
|
||||
if auto_scroll {
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
}
|
||||
|
||||
for buffer in buffers_affected.into_values() {
|
||||
self.sync_expanded_diff_hunks(buffer, cx);
|
||||
}
|
||||
for buffer in buffers_affected.into_values() {
|
||||
self.sync_expanded_diff_hunks(buffer, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
self.active_indent_guides_state.dirty = true;
|
||||
cx.notify();
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
self.active_indent_guides_state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_fold_placeholder(&self, cx: &AppContext) -> FoldPlaceholder {
|
||||
|
||||
@@ -1280,7 +1280,7 @@ impl SearchableItem for Editor {
|
||||
matches: &[Range<Anchor>],
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.unfold_ranges(&[matches[index].clone()], false, true, cx);
|
||||
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([range]);
|
||||
@@ -1288,7 +1288,7 @@ impl SearchableItem for Editor {
|
||||
}
|
||||
|
||||
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
|
||||
self.unfold_ranges(matches, false, false, cx);
|
||||
self.unfold_ranges(matches.to_vec(), false, false, cx);
|
||||
let mut ranges = Vec::new();
|
||||
for m in matches {
|
||||
ranges.push(self.range_for_match(m))
|
||||
|
||||
@@ -365,15 +365,12 @@ impl ExtensionBuilder {
|
||||
|
||||
let output = Command::new("rustup")
|
||||
.args(["target", "add", RUST_TARGET])
|
||||
.stderr(Stdio::piped())
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.output()
|
||||
.context("failed to run `rustup target add`")?;
|
||||
if !output.status.success() {
|
||||
bail!(
|
||||
"failed to install the `{RUST_TARGET}` target: {}",
|
||||
String::from_utf8_lossy(&rustc_output.stderr)
|
||||
);
|
||||
bail!("failed to install the `{RUST_TARGET}` target");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -14,6 +14,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
@@ -24,6 +25,7 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
lsp.workspace = true
|
||||
@@ -37,13 +39,16 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
settings.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
task.workspace = true
|
||||
theme.workspace = true
|
||||
toml.workspace = true
|
||||
ui.workspace = true
|
||||
url.workspace = true
|
||||
util.workspace = true
|
||||
wasmparser.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
wasmtime.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
@@ -54,3 +59,4 @@ language = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
pub mod extension_lsp_adapter;
|
||||
pub mod extension_settings;
|
||||
pub mod wasm_host;
|
||||
mod extension_indexed_docs_provider;
|
||||
mod extension_lsp_adapter;
|
||||
mod extension_settings;
|
||||
mod extension_slash_command;
|
||||
mod wasm_host;
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider;
|
||||
use crate::extension_slash_command::ExtensionSlashCommand;
|
||||
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
|
||||
use collections::{btree_map, BTreeMap, HashSet};
|
||||
use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
|
||||
pub use extension::ExtensionManifest;
|
||||
use extension::SchemaVersion;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use futures::{
|
||||
channel::{
|
||||
@@ -20,13 +28,14 @@ use futures::{
|
||||
select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
|
||||
SharedString, Task, WeakModel,
|
||||
actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task,
|
||||
WeakModel,
|
||||
};
|
||||
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
|
||||
QUERY_FILENAME_PREFIXES,
|
||||
LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageRegistry,
|
||||
LoadedLanguage, QUERY_FILENAME_PREFIXES,
|
||||
};
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::ContextProviderWithTasks;
|
||||
@@ -34,6 +43,7 @@ use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::ops::RangeInclusive;
|
||||
use std::str::FromStr;
|
||||
use std::{
|
||||
@@ -42,19 +52,20 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use url::Url;
|
||||
use util::ResultExt;
|
||||
use util::{maybe, ResultExt};
|
||||
use wasm_host::{
|
||||
wit::{is_supported_wasm_api_version, wasm_api_version_range},
|
||||
WasmExtension, WasmHost,
|
||||
};
|
||||
|
||||
pub use extension::{
|
||||
ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,
|
||||
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, OldExtensionManifest,
|
||||
};
|
||||
pub use extension_settings::ExtensionSettings;
|
||||
|
||||
pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
|
||||
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
|
||||
|
||||
/// The current extension [`SchemaVersion`] supported by Zed.
|
||||
@@ -89,98 +100,26 @@ pub fn is_version_compatible(
|
||||
true
|
||||
}
|
||||
|
||||
pub trait DocsDatabase: Send + Sync + 'static {
|
||||
fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
|
||||
}
|
||||
|
||||
pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
|
||||
fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
|
||||
|
||||
fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
|
||||
fn list_theme_names(
|
||||
&self,
|
||||
_theme_path: PathBuf,
|
||||
_fs: Arc<dyn Fs>,
|
||||
) -> Task<Result<Vec<String>>> {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, _cx: &mut AppContext) {}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
_language: LanguageName,
|
||||
_grammar: Option<Arc<str>>,
|
||||
_matcher: language::LanguageMatcher,
|
||||
_load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
|
||||
|
||||
fn remove_lsp_adapter(
|
||||
&self,
|
||||
_language: &LanguageName,
|
||||
_server_name: &language::LanguageServerName,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
_languages_to_remove: &[LanguageName],
|
||||
_grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
_slash_command: wit::SlashCommand,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_docs_provider(
|
||||
&self,
|
||||
_extension: WasmExtension,
|
||||
_host: Arc<WasmHost>,
|
||||
_provider_id: Arc<str>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
_server_name: language::LanguageServerName,
|
||||
_status: language::LanguageServerBinaryStatus,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtensionStore {
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub builder: Arc<ExtensionBuilder>,
|
||||
pub extension_index: ExtensionIndex,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
pub http_client: Arc<HttpClientWithUrl>,
|
||||
pub telemetry: Option<Arc<Telemetry>>,
|
||||
pub reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
pub reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
pub installed_dir: PathBuf,
|
||||
pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
pub index_path: PathBuf,
|
||||
pub modified_extensions: HashSet<Arc<str>>,
|
||||
pub wasm_host: Arc<WasmHost>,
|
||||
pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
pub tasks: Vec<Task<()>>,
|
||||
builder: Arc<ExtensionBuilder>,
|
||||
extension_index: ExtensionIndex,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
reload_tx: UnboundedSender<Option<Arc<str>>>,
|
||||
reload_complete_senders: Vec<oneshot::Sender<()>>,
|
||||
installed_dir: PathBuf,
|
||||
outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
|
||||
index_path: PathBuf,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
modified_extensions: HashSet<Arc<str>>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
|
||||
tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -219,25 +158,26 @@ pub struct ExtensionIndexEntry {
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexThemeEntry {
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
|
||||
pub struct ExtensionIndexLanguageEntry {
|
||||
pub extension: Arc<str>,
|
||||
pub path: PathBuf,
|
||||
pub matcher: LanguageMatcher,
|
||||
pub grammar: Option<Arc<str>>,
|
||||
extension: Arc<str>,
|
||||
path: PathBuf,
|
||||
matcher: LanguageMatcher,
|
||||
grammar: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
actions!(zed, [ReloadExtensions]);
|
||||
|
||||
pub fn init(
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
client: Arc<Client>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
cx: &mut AppContext,
|
||||
) {
|
||||
ExtensionSettings::register(cx);
|
||||
@@ -246,12 +186,16 @@ pub fn init(
|
||||
ExtensionStore::new(
|
||||
paths::extensions_dir().clone(),
|
||||
None,
|
||||
registration_hooks,
|
||||
fs,
|
||||
client.http_client().clone(),
|
||||
client.http_client().clone(),
|
||||
Some(client.telemetry().clone()),
|
||||
node_runtime,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
SnippetRegistry::global(cx),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -278,12 +222,16 @@ impl ExtensionStore {
|
||||
pub fn new(
|
||||
extensions_dir: PathBuf,
|
||||
build_dir: Option<PathBuf>,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<HttpClientWithUrl>,
|
||||
builder_client: Arc<dyn HttpClient>,
|
||||
telemetry: Option<Arc<Telemetry>>,
|
||||
node_runtime: NodeRuntime,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let work_dir = extensions_dir.join("work");
|
||||
@@ -293,7 +241,6 @@ impl ExtensionStore {
|
||||
|
||||
let (reload_tx, mut reload_rx) = unbounded();
|
||||
let mut this = Self {
|
||||
registration_hooks: extension_api.clone(),
|
||||
extension_index: Default::default(),
|
||||
installed_dir,
|
||||
index_path,
|
||||
@@ -305,7 +252,7 @@ impl ExtensionStore {
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
node_runtime,
|
||||
extension_api,
|
||||
language_registry.clone(),
|
||||
work_dir,
|
||||
cx,
|
||||
),
|
||||
@@ -313,6 +260,11 @@ impl ExtensionStore {
|
||||
fs,
|
||||
http_client,
|
||||
telemetry,
|
||||
language_registry,
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
reload_tx,
|
||||
tasks: Vec::new(),
|
||||
};
|
||||
@@ -373,7 +325,6 @@ impl ExtensionStore {
|
||||
async move {
|
||||
load_initial_extensions.await;
|
||||
|
||||
let mut index_changed = false;
|
||||
let mut debounce_timer = cx
|
||||
.background_executor()
|
||||
.spawn(futures::future::pending())
|
||||
@@ -381,21 +332,17 @@ impl ExtensionStore {
|
||||
loop {
|
||||
select_biased! {
|
||||
_ = debounce_timer => {
|
||||
if index_changed {
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
index_changed = false;
|
||||
}
|
||||
let index = this
|
||||
.update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
|
||||
.await;
|
||||
this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
|
||||
.await;
|
||||
}
|
||||
extension_id = reload_rx.next() => {
|
||||
let Some(extension_id) = extension_id else { break; };
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.modified_extensions.extend(extension_id);
|
||||
})?;
|
||||
index_changed = true;
|
||||
debounce_timer = cx
|
||||
.background_executor()
|
||||
.timer(RELOAD_DEBOUNCE_DURATION)
|
||||
@@ -439,7 +386,7 @@ impl ExtensionStore {
|
||||
this
|
||||
}
|
||||
|
||||
pub fn reload(
|
||||
fn reload(
|
||||
&mut self,
|
||||
modified_extension: Option<Arc<str>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
@@ -1092,7 +1039,7 @@ impl ExtensionStore {
|
||||
grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
|
||||
for (language_server_name, config) in extension.manifest.language_servers.iter() {
|
||||
for language in config.languages() {
|
||||
self.registration_hooks
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(&language, language_server_name);
|
||||
}
|
||||
}
|
||||
@@ -1100,8 +1047,8 @@ impl ExtensionStore {
|
||||
|
||||
self.wasm_extensions
|
||||
.retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
|
||||
self.registration_hooks.remove_user_themes(themes_to_remove);
|
||||
self.registration_hooks
|
||||
self.theme_registry.remove_user_themes(&themes_to_remove);
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
|
||||
let languages_to_add = new_index
|
||||
@@ -1136,7 +1083,7 @@ impl ExtensionStore {
|
||||
}));
|
||||
}
|
||||
|
||||
self.registration_hooks
|
||||
self.language_registry
|
||||
.register_wasm_grammars(grammars_to_add);
|
||||
|
||||
for (language_name, language) in languages_to_add {
|
||||
@@ -1145,11 +1092,11 @@ impl ExtensionStore {
|
||||
Path::new(language.extension.as_ref()),
|
||||
language.path.as_path(),
|
||||
]);
|
||||
self.registration_hooks.register_language(
|
||||
self.language_registry.register_language(
|
||||
language_name.clone(),
|
||||
language.grammar.clone(),
|
||||
language.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
let config = std::fs::read_to_string(language_path.join("config.toml"))?;
|
||||
let config: LanguageConfig = ::toml::from_str(&config)?;
|
||||
let queries = load_plugin_queries(&language_path);
|
||||
@@ -1168,14 +1115,15 @@ impl ExtensionStore {
|
||||
context_provider,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let fs = self.fs.clone();
|
||||
let wasm_host = self.wasm_host.clone();
|
||||
let root_dir = self.installed_dir.clone();
|
||||
let api = self.registration_hooks.clone();
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
let snippet_registry = self.snippet_registry.clone();
|
||||
let extension_entries = extensions_to_load
|
||||
.iter()
|
||||
.filter_map(|name| new_index.extensions.get(name).cloned())
|
||||
@@ -1190,14 +1138,18 @@ impl ExtensionStore {
|
||||
.spawn({
|
||||
let fs = fs.clone();
|
||||
async move {
|
||||
for theme_path in themes_to_add.into_iter() {
|
||||
api.load_user_theme(theme_path, fs.clone()).await.log_err();
|
||||
for theme_path in &themes_to_add {
|
||||
theme_registry
|
||||
.load_user_theme(theme_path, fs.clone())
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
|
||||
for snippets_path in &snippets_to_add {
|
||||
if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
|
||||
{
|
||||
api.register_snippets(snippets_path, &snippets_contents)
|
||||
snippet_registry
|
||||
.register_snippets(snippets_path, &snippets_contents)
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
@@ -1211,13 +1163,30 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
let extension_path = root_dir.join(extension.manifest.id.as_ref());
|
||||
let wasm_extension = WasmExtension::load(
|
||||
extension_path,
|
||||
&extension.manifest,
|
||||
wasm_host.clone(),
|
||||
&cx,
|
||||
)
|
||||
let wasm_extension = maybe!(async {
|
||||
let mut path = root_dir.clone();
|
||||
path.extend([extension.manifest.clone().id.as_ref(), "extension.wasm"]);
|
||||
let mut wasm_file = fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(
|
||||
wasm_bytes,
|
||||
extension.manifest.clone().clone(),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("failed to load wasm extension {}", extension.manifest.id)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(wasm_extension) = wasm_extension.log_err() {
|
||||
@@ -1236,9 +1205,9 @@ impl ExtensionStore {
|
||||
for (manifest, wasm_extension) in &wasm_extensions {
|
||||
for (language_server_id, language_server_config) in &manifest.language_servers {
|
||||
for language in language_server_config.languages() {
|
||||
this.registration_hooks.register_lsp_adapter(
|
||||
this.language_registry.register_lsp_adapter(
|
||||
language.clone(),
|
||||
ExtensionLspAdapter {
|
||||
Arc::new(ExtensionLspAdapter {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
language_server_id: language_server_id.clone(),
|
||||
@@ -1246,38 +1215,43 @@ impl ExtensionStore {
|
||||
name: language_server_id.0.to_string(),
|
||||
language_name: language.to_string(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (slash_command_name, slash_command) in &manifest.slash_commands {
|
||||
this.registration_hooks.register_slash_command(
|
||||
crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
this.slash_command_registry.register_command(
|
||||
ExtensionSlashCommand {
|
||||
command: crate::wit::SlashCommand {
|
||||
name: slash_command_name.to_string(),
|
||||
description: slash_command.description.to_string(),
|
||||
// We don't currently expose this as a configurable option, as it currently drives
|
||||
// the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
|
||||
// defined in extensions, as they are not able to be added to the menu.
|
||||
tooltip_text: String::new(),
|
||||
requires_argument: slash_command.requires_argument,
|
||||
},
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
},
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
for (provider_id, _provider) in &manifest.indexed_docs_providers {
|
||||
this.registration_hooks.register_docs_provider(
|
||||
wasm_extension.clone(),
|
||||
this.wasm_host.clone(),
|
||||
provider_id.clone(),
|
||||
);
|
||||
this.indexed_docs_registry.register_provider(Box::new(
|
||||
ExtensionIndexedDocsProvider {
|
||||
extension: wasm_extension.clone(),
|
||||
host: this.wasm_host.clone(),
|
||||
id: ProviderId(provider_id.clone()),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
this.wasm_extensions.extend(wasm_extensions);
|
||||
this.registration_hooks.reload_current_theme(cx);
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -1288,7 +1262,6 @@ impl ExtensionStore {
|
||||
let work_dir = self.wasm_host.work_dir.clone();
|
||||
let extensions_dir = self.installed_dir.clone();
|
||||
let index_path = self.index_path.clone();
|
||||
let extension_api = self.registration_hooks.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let start_time = Instant::now();
|
||||
let mut index = ExtensionIndex::default();
|
||||
@@ -1310,14 +1283,9 @@ impl ExtensionStore {
|
||||
continue;
|
||||
}
|
||||
|
||||
Self::add_extension_to_index(
|
||||
fs.clone(),
|
||||
extension_dir,
|
||||
&mut index,
|
||||
extension_api.clone(),
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
Self::add_extension_to_index(fs.clone(), extension_dir, &mut index)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1337,7 +1305,6 @@ impl ExtensionStore {
|
||||
fs: Arc<dyn Fs>,
|
||||
extension_dir: PathBuf,
|
||||
index: &mut ExtensionIndex,
|
||||
extension_api: Arc<dyn ExtensionRegistrationHooks>,
|
||||
) -> Result<()> {
|
||||
let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
|
||||
let extension_id = extension_manifest.id.clone();
|
||||
@@ -1389,8 +1356,7 @@ impl ExtensionStore {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(theme_families) = extension_api
|
||||
.list_theme_names(theme_path.clone(), fs.clone())
|
||||
let Some(theme_family) = theme::read_user_theme(&theme_path, fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
else {
|
||||
@@ -1402,9 +1368,9 @@ impl ExtensionStore {
|
||||
extension_manifest.themes.push(relative_path.clone());
|
||||
}
|
||||
|
||||
for theme_name in theme_families {
|
||||
for theme in theme_family.themes {
|
||||
index.themes.insert(
|
||||
theme_name.into(),
|
||||
theme.name.into(),
|
||||
ExtensionIndexThemeEntry {
|
||||
extension: extension_id.clone(),
|
||||
path: relative_path.clone(),
|
||||
|
||||
@@ -7,7 +7,7 @@ use futures::FutureExt;
|
||||
use indexed_docs::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId};
|
||||
use wasmtime_wasi::WasiView;
|
||||
|
||||
use extension_host::wasm_host::{WasmExtension, WasmHost};
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
pub struct ExtensionIndexedDocsProvider {
|
||||
pub(crate) extension: WasmExtension,
|
||||
@@ -58,7 +58,7 @@ impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
|
||||
let id = self.id.clone();
|
||||
|extension, store| {
|
||||
async move {
|
||||
let database_resource = store.data_mut().table().push(database as _)?;
|
||||
let database_resource = store.data_mut().table().push(database)?;
|
||||
extension
|
||||
.call_index_docs(
|
||||
store,
|
||||
@@ -5,20 +5,20 @@ use assistant_slash_command::{
|
||||
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
|
||||
SlashCommandResult,
|
||||
};
|
||||
use futures::FutureExt as _;
|
||||
use futures::FutureExt;
|
||||
use gpui::{Task, WeakView, WindowContext};
|
||||
use language::{BufferSnapshot, LspAdapterDelegate};
|
||||
use ui::prelude::*;
|
||||
use wasmtime_wasi::WasiView;
|
||||
use workspace::Workspace;
|
||||
|
||||
use extension_host::wasm_host::{WasmExtension, WasmHost};
|
||||
use crate::wasm_host::{WasmExtension, WasmHost};
|
||||
|
||||
pub struct ExtensionSlashCommand {
|
||||
pub(crate) extension: WasmExtension,
|
||||
#[allow(unused)]
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
pub(crate) command: extension_host::wasm_host::SlashCommand,
|
||||
pub(crate) command: crate::wit::SlashCommand,
|
||||
}
|
||||
|
||||
impl SlashCommand for ExtensionSlashCommand {
|
||||
@@ -1,13 +1,13 @@
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use extension_host::ExtensionSettings;
|
||||
use extension_host::SchemaVersion;
|
||||
use extension_host::{
|
||||
use crate::extension_settings::ExtensionSettings;
|
||||
use crate::{
|
||||
Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
|
||||
ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
|
||||
RELOAD_DEBOUNCE_DURATION,
|
||||
};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use async_compression::futures::bufread::GzipEncoder;
|
||||
use collections::BTreeMap;
|
||||
use extension::SchemaVersion;
|
||||
use fs::{FakeFs, Fs, RealFs};
|
||||
use futures::{io::BufReader, AsyncReadExt, StreamExt};
|
||||
use gpui::{Context, SemanticVersion, TestAppContext};
|
||||
@@ -267,29 +267,24 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
let node_runtime = NodeRuntime::unavailable();
|
||||
|
||||
let store = cx.new_model(|cx| {
|
||||
let extension_registration_hooks = crate::ConcreteExtensionRegistrationHooks::new(
|
||||
theme_registry.clone(),
|
||||
slash_command_registry.clone(),
|
||||
indexed_docs_registry.clone(),
|
||||
snippet_registry.clone(),
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
extension_registration_hooks,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry.clone(),
|
||||
indexed_docs_registry.clone(),
|
||||
snippet_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
cx.executor().advance_clock(super::RELOAD_DEBOUNCE_DURATION);
|
||||
store.read_with(cx, |store, _| {
|
||||
let index = &store.extension_index;
|
||||
assert_eq!(index.extensions, expected_index.extensions);
|
||||
@@ -400,24 +395,19 @@ async fn test_extension_store(cx: &mut TestAppContext) {
|
||||
// Create new extension store, as if Zed were restarting.
|
||||
drop(store);
|
||||
let store = cx.new_model(|cx| {
|
||||
let extension_api = crate::ConcreteExtensionRegistrationHooks::new(
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
ExtensionStore::new(
|
||||
PathBuf::from("/the-extension-dir"),
|
||||
None,
|
||||
extension_api,
|
||||
fs.clone(),
|
||||
http_client.clone(),
|
||||
http_client.clone(),
|
||||
None,
|
||||
node_runtime.clone(),
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -590,23 +580,19 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
Arc::new(ReqwestClient::user_agent(&user_agent).expect("Could not create HTTP client"));
|
||||
|
||||
let extension_store = cx.new_model(|cx| {
|
||||
let extension_api = crate::ConcreteExtensionRegistrationHooks::new(
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
);
|
||||
ExtensionStore::new(
|
||||
extensions_dir.clone(),
|
||||
Some(cache_dir),
|
||||
extension_api,
|
||||
fs.clone(),
|
||||
extension_client.clone(),
|
||||
builder_client,
|
||||
None,
|
||||
node_runtime,
|
||||
language_registry.clone(),
|
||||
theme_registry.clone(),
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -616,7 +602,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
|
||||
let executor = cx.executor();
|
||||
let _task = cx.executor().spawn(async move {
|
||||
while let Some(event) = events.next().await {
|
||||
if let extension_host::Event::StartedReloading = event {
|
||||
if let crate::Event::StartedReloading = event {
|
||||
executor.advance_clock(RELOAD_DEBOUNCE_DURATION);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod wit;
|
||||
pub(crate) mod wit;
|
||||
|
||||
use crate::{ExtensionManifest, ExtensionRegistrationHooks};
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use crate::ExtensionManifest;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use fs::{normalize_path, Fs};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use futures::{
|
||||
@@ -14,6 +14,7 @@ use futures::{
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
|
||||
use http_client::HttpClient;
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::NodeRuntime;
|
||||
use release_channel::ReleaseChannel;
|
||||
use semantic_version::SemanticVersion;
|
||||
@@ -27,16 +28,15 @@ use wasmtime::{
|
||||
};
|
||||
use wasmtime_wasi as wasi;
|
||||
use wit::Extension;
|
||||
pub use wit::SlashCommand;
|
||||
|
||||
pub struct WasmHost {
|
||||
pub(crate) struct WasmHost {
|
||||
engine: Engine,
|
||||
release_channel: ReleaseChannel,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
pub(crate) language_registry: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
pub work_dir: PathBuf,
|
||||
pub(crate) work_dir: PathBuf,
|
||||
_main_thread_message_task: Task<()>,
|
||||
main_thread_message_tx: mpsc::UnboundedSender<MainThreadCall>,
|
||||
}
|
||||
@@ -44,16 +44,16 @@ pub struct WasmHost {
|
||||
#[derive(Clone)]
|
||||
pub struct WasmExtension {
|
||||
tx: UnboundedSender<ExtensionCall>,
|
||||
pub manifest: Arc<ExtensionManifest>,
|
||||
pub(crate) manifest: Arc<ExtensionManifest>,
|
||||
#[allow(unused)]
|
||||
pub zed_api_version: SemanticVersion,
|
||||
}
|
||||
|
||||
pub struct WasmState {
|
||||
pub(crate) struct WasmState {
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
pub table: ResourceTable,
|
||||
pub(crate) table: ResourceTable,
|
||||
ctx: wasi::WasiCtx,
|
||||
pub host: Arc<WasmHost>,
|
||||
pub(crate) host: Arc<WasmHost>,
|
||||
}
|
||||
|
||||
type MainThreadCall =
|
||||
@@ -81,7 +81,7 @@ impl WasmHost {
|
||||
fs: Arc<dyn Fs>,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
node_runtime: NodeRuntime,
|
||||
registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
work_dir: PathBuf,
|
||||
cx: &mut AppContext,
|
||||
) -> Arc<Self> {
|
||||
@@ -97,7 +97,7 @@ impl WasmHost {
|
||||
work_dir,
|
||||
http_client,
|
||||
node_runtime,
|
||||
registration_hooks,
|
||||
language_registry,
|
||||
release_channel: ReleaseChannel::global(cx),
|
||||
_main_thread_message_task: task,
|
||||
main_thread_message_tx: tx,
|
||||
@@ -107,13 +107,13 @@ impl WasmHost {
|
||||
pub fn load_extension(
|
||||
self: &Arc<Self>,
|
||||
wasm_bytes: Vec<u8>,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
manifest: Arc<ExtensionManifest>,
|
||||
executor: BackgroundExecutor,
|
||||
) -> Task<Result<WasmExtension>> {
|
||||
let this = self.clone();
|
||||
let manifest = manifest.clone();
|
||||
executor.clone().spawn(async move {
|
||||
let zed_api_version = parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
|
||||
let zed_api_version =
|
||||
extension::parse_wasm_extension_version(&manifest.id, &wasm_bytes)?;
|
||||
|
||||
let component = Component::from_binary(&this.engine, &wasm_bytes)
|
||||
.context("failed to compile wasm component")?;
|
||||
@@ -151,7 +151,7 @@ impl WasmHost {
|
||||
.detach();
|
||||
|
||||
Ok(WasmExtension {
|
||||
manifest: manifest.clone(),
|
||||
manifest,
|
||||
tx,
|
||||
zed_api_version,
|
||||
})
|
||||
@@ -198,75 +198,7 @@ impl WasmHost {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_wasm_extension_version(
|
||||
extension_id: &str,
|
||||
wasm_bytes: &[u8],
|
||||
) -> Result<SemanticVersion> {
|
||||
let mut version = None;
|
||||
|
||||
for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
|
||||
if let wasmparser::Payload::CustomSection(s) =
|
||||
part.context("error parsing wasm extension")?
|
||||
{
|
||||
if s.name() == "zed:api-version" {
|
||||
version = parse_wasm_extension_version_custom_section(s.data());
|
||||
if version.is_none() {
|
||||
bail!(
|
||||
"extension {} has invalid zed:api-version section: {:?}",
|
||||
extension_id,
|
||||
s.data()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The reason we wait until we're done parsing all of the Wasm bytes to return the version
|
||||
// is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
|
||||
//
|
||||
// By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
|
||||
// earlier as an `Err` rather than as a panic.
|
||||
version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
|
||||
}
|
||||
|
||||
fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
|
||||
if data.len() == 6 {
|
||||
Some(SemanticVersion::new(
|
||||
u16::from_be_bytes([data[0], data[1]]) as _,
|
||||
u16::from_be_bytes([data[2], data[3]]) as _,
|
||||
u16::from_be_bytes([data[4], data[5]]) as _,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl WasmExtension {
|
||||
pub async fn load(
|
||||
extension_dir: PathBuf,
|
||||
manifest: &Arc<ExtensionManifest>,
|
||||
wasm_host: Arc<WasmHost>,
|
||||
cx: &AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
let path = extension_dir.join("extension.wasm");
|
||||
|
||||
let mut wasm_file = wasm_host
|
||||
.fs
|
||||
.open_sync(&path)
|
||||
.await
|
||||
.context("failed to open wasm file")?;
|
||||
|
||||
let mut wasm_bytes = Vec::new();
|
||||
wasm_file
|
||||
.read_to_end(&mut wasm_bytes)
|
||||
.context("failed to read wasm")?;
|
||||
|
||||
wasm_host
|
||||
.load_extension(wasm_bytes, manifest, cx.background_executor().clone())
|
||||
.await
|
||||
.with_context(|| format!("failed to load wasm extension {}", manifest.id))
|
||||
}
|
||||
|
||||
pub async fn call<T, Fn>(&self, f: Fn) -> T
|
||||
where
|
||||
T: 'static + Send,
|
||||
|
||||
@@ -3,12 +3,10 @@ mod since_v0_0_4;
|
||||
mod since_v0_0_6;
|
||||
mod since_v0_1_0;
|
||||
mod since_v0_2_0;
|
||||
// use indexed_docs::IndexedDocsDatabase;
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use release_channel::ReleaseChannel;
|
||||
use since_v0_2_0 as latest;
|
||||
|
||||
use crate::DocsDatabase;
|
||||
|
||||
use super::{wasm_engine, WasmState};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use language::{LanguageServerName, LspAdapterDelegate};
|
||||
@@ -396,7 +394,7 @@ impl Extension {
|
||||
store: &mut Store<WasmState>,
|
||||
provider: &str,
|
||||
package_name: &str,
|
||||
database: Resource<Arc<dyn DocsDatabase>>,
|
||||
database: Resource<Arc<IndexedDocsDatabase>>,
|
||||
) -> Result<Result<(), String>> {
|
||||
match self {
|
||||
Extension::V020(ext) => {
|
||||
|
||||
@@ -148,7 +148,7 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.language_registry
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use crate::DocsDatabase;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
@@ -8,6 +7,7 @@ use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -48,7 +48,7 @@ mod settings {
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
@@ -512,7 +512,7 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.language_registry
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
|
||||
use crate::DocsDatabase;
|
||||
use ::http_client::{AsyncBody, HttpRequestExt};
|
||||
use ::settings::{Settings, WorktreeId};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
@@ -8,6 +7,7 @@ use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, FutureExt as _};
|
||||
use futures::{lock::Mutex, AsyncReadExt};
|
||||
use indexed_docs::IndexedDocsDatabase;
|
||||
use language::{
|
||||
language_settings::AllLanguageSettings, LanguageServerBinaryStatus, LspAdapterDelegate,
|
||||
};
|
||||
@@ -43,7 +43,7 @@ mod settings {
|
||||
}
|
||||
|
||||
pub type ExtensionWorktree = Arc<dyn LspAdapterDelegate>;
|
||||
pub type ExtensionKeyValueStore = Arc<dyn DocsDatabase>;
|
||||
pub type ExtensionKeyValueStore = Arc<IndexedDocsDatabase>;
|
||||
pub type ExtensionHttpResponseStream = Arc<Mutex<::http_client::Response<AsyncBody>>>;
|
||||
|
||||
pub fn linker() -> &'static Linker<WasmState> {
|
||||
@@ -459,7 +459,7 @@ impl ExtensionImports for WasmState {
|
||||
};
|
||||
|
||||
self.host
|
||||
.registration_hooks
|
||||
.language_registry
|
||||
.update_lsp_status(language::LanguageServerName(server_name.into()), status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,18 +16,14 @@ test-support = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
async-trait.workspace = true
|
||||
client.workspace = true
|
||||
collections.workspace = true
|
||||
db.workspace = true
|
||||
editor.workspace = true
|
||||
extension_host.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
num-format.workspace = true
|
||||
picker.workspace = true
|
||||
@@ -37,30 +33,12 @@ semantic_version.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
snippet_provider.workspace = true
|
||||
theme.workspace = true
|
||||
theme_selector.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
vim.workspace = true
|
||||
wasmtime-wasi.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
async-compression.workspace = true
|
||||
async-tar.workspace = true
|
||||
ctor.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
http_client.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
lsp.workspace = true
|
||||
node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
reqwest_client.workspace = true
|
||||
serde_json.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host};
|
||||
use fs::Fs;
|
||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||
use indexed_docs::{IndexedDocsRegistry, ProviderId};
|
||||
use language::{LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage};
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use ui::SharedString;
|
||||
|
||||
use crate::{extension_indexed_docs_provider, extension_slash_command::ExtensionSlashCommand};
|
||||
|
||||
pub struct ConcreteExtensionRegistrationHooks {
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl ConcreteExtensionRegistrationHooks {
|
||||
pub fn new(
|
||||
theme_registry: Arc<ThemeRegistry>,
|
||||
slash_command_registry: Arc<SlashCommandRegistry>,
|
||||
indexed_docs_registry: Arc<IndexedDocsRegistry>,
|
||||
snippet_registry: Arc<SnippetRegistry>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &AppContext,
|
||||
) -> Arc<dyn extension_host::ExtensionRegistrationHooks> {
|
||||
Arc::new(Self {
|
||||
theme_registry,
|
||||
slash_command_registry,
|
||||
indexed_docs_registry,
|
||||
snippet_registry,
|
||||
language_registry,
|
||||
executor: cx.background_executor().clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistrationHooks {
|
||||
fn remove_user_themes(&self, themes: Vec<SharedString>) {
|
||||
self.theme_registry.remove_user_themes(&themes);
|
||||
}
|
||||
|
||||
fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn fs::Fs>) -> Task<Result<()>> {
|
||||
let theme_registry = self.theme_registry.clone();
|
||||
self.executor
|
||||
.spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await })
|
||||
}
|
||||
|
||||
fn register_slash_command(
|
||||
&self,
|
||||
command: wasm_host::SlashCommand,
|
||||
extension: wasm_host::WasmExtension,
|
||||
host: Arc<wasm_host::WasmHost>,
|
||||
) {
|
||||
self.slash_command_registry.register_command(
|
||||
ExtensionSlashCommand {
|
||||
command,
|
||||
extension,
|
||||
host,
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn register_docs_provider(
|
||||
&self,
|
||||
extension: wasm_host::WasmExtension,
|
||||
host: Arc<wasm_host::WasmHost>,
|
||||
provider_id: Arc<str>,
|
||||
) {
|
||||
self.indexed_docs_registry.register_provider(Box::new(
|
||||
extension_indexed_docs_provider::ExtensionIndexedDocsProvider {
|
||||
extension,
|
||||
host,
|
||||
id: ProviderId(provider_id),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn register_snippets(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> {
|
||||
self.snippet_registry
|
||||
.register_snippets(path, snippet_contents)
|
||||
}
|
||||
|
||||
fn update_lsp_status(
|
||||
&self,
|
||||
server_name: language::LanguageServerName,
|
||||
status: LanguageServerBinaryStatus,
|
||||
) {
|
||||
self.language_registry
|
||||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
fn register_lsp_adapter(
|
||||
&self,
|
||||
language_name: language::LanguageName,
|
||||
adapter: ExtensionLspAdapter,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_lsp_adapter(language_name, Arc::new(adapter));
|
||||
}
|
||||
|
||||
fn remove_lsp_adapter(
|
||||
&self,
|
||||
language_name: &language::LanguageName,
|
||||
server_name: &language::LanguageServerName,
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_lsp_adapter(language_name, server_name);
|
||||
}
|
||||
|
||||
fn remove_languages(
|
||||
&self,
|
||||
languages_to_remove: &[language::LanguageName],
|
||||
grammars_to_remove: &[Arc<str>],
|
||||
) {
|
||||
self.language_registry
|
||||
.remove_languages(&languages_to_remove, &grammars_to_remove);
|
||||
}
|
||||
|
||||
fn register_wasm_grammars(&self, grammars: Vec<(Arc<str>, PathBuf)>) {
|
||||
self.language_registry.register_wasm_grammars(grammars)
|
||||
}
|
||||
|
||||
fn register_language(
|
||||
&self,
|
||||
language: language::LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: language::LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
) {
|
||||
self.language_registry
|
||||
.register_language(language, grammar, matcher, load)
|
||||
}
|
||||
|
||||
fn reload_current_theme(&self, cx: &mut AppContext) {
|
||||
ThemeSettings::reload_current_theme(cx)
|
||||
}
|
||||
|
||||
fn list_theme_names(&self, path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
|
||||
self.executor.spawn(async move {
|
||||
let themes = theme::read_user_theme(&path, fs).await?;
|
||||
Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,7 @@
|
||||
mod components;
|
||||
mod extension_indexed_docs_provider;
|
||||
mod extension_registration_hooks;
|
||||
mod extension_slash_command;
|
||||
mod extension_suggest;
|
||||
mod extension_version_selector;
|
||||
|
||||
#[cfg(test)]
|
||||
mod extension_store_test;
|
||||
|
||||
pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks;
|
||||
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -10,11 +10,7 @@ use git::GitHostingProviderRegistry;
|
||||
#[cfg(target_os = "linux")]
|
||||
use ashpd::desktop::trash;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::fs::File;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsFd;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::{fs::File, os::fd::AsFd};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
@@ -55,14 +51,14 @@ pub trait Watcher: Send + Sync {
|
||||
fn remove(&self, path: &Path) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum PathEventKind {
|
||||
Removed,
|
||||
Created,
|
||||
Changed,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct PathEvent {
|
||||
pub path: PathBuf,
|
||||
pub kind: Option<PathEventKind>,
|
||||
@@ -99,7 +95,6 @@ pub trait Fs: Send + Sync {
|
||||
async fn trash_file(&self, path: &Path, options: RemoveOptions) -> Result<()> {
|
||||
self.remove_file(path, options).await
|
||||
}
|
||||
async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>>;
|
||||
async fn open_sync(&self, path: &Path) -> Result<Box<dyn io::Read>>;
|
||||
async fn load(&self, path: &Path) -> Result<String> {
|
||||
Ok(String::from_utf8(self.load_bytes(path).await?)?)
|
||||
@@ -192,52 +187,6 @@ pub struct RealFs {
|
||||
git_binary_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
||||
fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf>;
|
||||
}
|
||||
|
||||
impl FileHandle for std::fs::File {
|
||||
#[cfg(target_os = "macos")]
|
||||
fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
|
||||
use std::{
|
||||
ffi::{CStr, OsStr},
|
||||
os::unix::ffi::OsStrExt,
|
||||
};
|
||||
|
||||
let fd = self.as_fd();
|
||||
let mut path_buf: [libc::c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize];
|
||||
|
||||
let result = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_GETPATH, path_buf.as_mut_ptr()) };
|
||||
if result == -1 {
|
||||
anyhow::bail!("fcntl returned -1".to_string());
|
||||
}
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(path_buf.as_ptr()) };
|
||||
let path = PathBuf::from(OsStr::from_bytes(c_str.to_bytes()));
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
|
||||
let fd = self.as_fd();
|
||||
let fd_path = format!("/proc/self/fd/{}", fd.as_raw_fd());
|
||||
let new_path = std::fs::read_link(fd_path)?;
|
||||
if new_path
|
||||
.file_name()
|
||||
.is_some_and(|f| f.to_string_lossy().ends_with(" (deleted)"))
|
||||
{
|
||||
anyhow::bail!("file was deleted")
|
||||
};
|
||||
|
||||
Ok(new_path)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn current_path(&self, _: &Arc<dyn Fs>) -> Result<PathBuf> {
|
||||
anyhow::bail!("unimplemented")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RealWatcher {}
|
||||
|
||||
impl RealFs {
|
||||
@@ -451,10 +400,6 @@ impl Fs for RealFs {
|
||||
Ok(Box::new(std::fs::File::open(path)?))
|
||||
}
|
||||
|
||||
async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> {
|
||||
Ok(Arc::new(std::fs::File::open(path)?))
|
||||
}
|
||||
|
||||
async fn load(&self, path: &Path) -> Result<String> {
|
||||
let path = path.to_path_buf();
|
||||
let text = smol::unblock(|| std::fs::read_to_string(path)).await?;
|
||||
@@ -810,7 +755,6 @@ struct FakeFsState {
|
||||
buffered_events: Vec<PathEvent>,
|
||||
metadata_call_count: usize,
|
||||
read_dir_call_count: usize,
|
||||
moves: std::collections::HashMap<u64, PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -982,7 +926,6 @@ impl FakeFs {
|
||||
events_paused: false,
|
||||
read_dir_call_count: 0,
|
||||
metadata_call_count: 0,
|
||||
moves: Default::default(),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1419,27 +1362,6 @@ impl Watcher for FakeWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[derive(Debug)]
|
||||
struct FakeHandle {
|
||||
inode: u64,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl FileHandle for FakeHandle {
|
||||
fn current_path(&self, fs: &Arc<dyn Fs>) -> Result<PathBuf> {
|
||||
let state = fs.as_fake().state.lock();
|
||||
let Some(target) = state.moves.get(&self.inode) else {
|
||||
anyhow::bail!("fake fd not moved")
|
||||
};
|
||||
|
||||
if state.try_read_path(&target, false).is_some() {
|
||||
return Ok(target.clone());
|
||||
}
|
||||
anyhow::bail!("fake fd target not found")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[async_trait::async_trait]
|
||||
impl Fs for FakeFs {
|
||||
@@ -1578,14 +1500,6 @@ impl Fs for FakeFs {
|
||||
}
|
||||
})?;
|
||||
|
||||
let inode = match *moved_entry.lock() {
|
||||
FakeFsEntry::File { inode, .. } => inode,
|
||||
FakeFsEntry::Dir { inode, .. } => inode,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
state.moves.insert(inode, new_path.clone());
|
||||
|
||||
state.write_path(&new_path, |e| {
|
||||
match e {
|
||||
btree_map::Entry::Occupied(mut e) => {
|
||||
@@ -1730,19 +1644,6 @@ impl Fs for FakeFs {
|
||||
Ok(Box::new(io::Cursor::new(bytes)))
|
||||
}
|
||||
|
||||
async fn open_handle(&self, path: &Path) -> Result<Arc<dyn FileHandle>> {
|
||||
self.simulate_random_delay().await;
|
||||
let state = self.state.lock();
|
||||
let entry = state.read_path(&path)?;
|
||||
let entry = entry.lock();
|
||||
let inode = match *entry {
|
||||
FakeFsEntry::File { inode, .. } => inode,
|
||||
FakeFsEntry::Dir { inode, .. } => inode,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(Arc::new(FakeHandle { inode }))
|
||||
}
|
||||
|
||||
async fn load(&self, path: &Path) -> Result<String> {
|
||||
let content = self.load_internal(path).await?;
|
||||
Ok(String::from_utf8(content.clone())?)
|
||||
|
||||
@@ -90,8 +90,8 @@ profiling.workspace = true
|
||||
rand = { optional = true, workspace = true }
|
||||
raw-window-handle = "0.6"
|
||||
refineable.workspace = true
|
||||
resvg = { version = "0.44.0", default-features = false }
|
||||
usvg = { version = "0.44.0", default-features = false }
|
||||
resvg = { version = "0.41.0", default-features = false }
|
||||
usvg = { version = "0.41.0", default-features = false }
|
||||
schemars.workspace = true
|
||||
seahash = "4.1"
|
||||
semantic_version.workspace = true
|
||||
|
||||
@@ -88,22 +88,11 @@ pub struct UniformListFrameState {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct UniformListScrollHandle(pub Rc<RefCell<UniformListScrollState>>);
|
||||
|
||||
/// Where to place the element scrolled to.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ScrollStrategy {
|
||||
/// Place the element at the top of the list's viewport.
|
||||
Top,
|
||||
/// Attempt to place the element in the middle of the list's viewport.
|
||||
/// May not be possible if there's not enough list items above the item scrolled to:
|
||||
/// in this case, the element will be placed at the closest possible position.
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct UniformListScrollState {
|
||||
pub base_handle: ScrollHandle,
|
||||
pub deferred_scroll_to_item: Option<(usize, ScrollStrategy)>,
|
||||
pub deferred_scroll_to_item: Option<usize>,
|
||||
/// Size of the item, captured during last layout.
|
||||
pub last_item_size: Option<ItemSize>,
|
||||
}
|
||||
@@ -129,16 +118,14 @@ impl UniformListScrollHandle {
|
||||
}
|
||||
|
||||
/// Scroll the list to the given item index.
|
||||
pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
|
||||
self.0.borrow_mut().deferred_scroll_to_item = Some((ix, strategy));
|
||||
pub fn scroll_to_item(&self, ix: usize) {
|
||||
self.0.borrow_mut().deferred_scroll_to_item = Some(ix);
|
||||
}
|
||||
|
||||
/// Get the index of the topmost visible child.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn logical_scroll_top_index(&self) -> usize {
|
||||
let this = self.0.borrow();
|
||||
this.deferred_scroll_to_item
|
||||
.map(|(ix, _)| ix)
|
||||
.unwrap_or_else(|| this.base_handle.logical_scroll_top().0)
|
||||
}
|
||||
}
|
||||
@@ -286,40 +273,18 @@ impl Element for UniformList {
|
||||
scroll_offset.x = Pixels::ZERO;
|
||||
}
|
||||
|
||||
if let Some((ix, scroll_strategy)) = shared_scroll_to_item {
|
||||
if let Some(ix) = shared_scroll_to_item {
|
||||
let list_height = padded_bounds.size.height;
|
||||
let mut updated_scroll_offset = shared_scroll_offset.borrow_mut();
|
||||
let item_top = item_height * ix + padding.top;
|
||||
let item_bottom = item_top + item_height;
|
||||
let scroll_top = -updated_scroll_offset.y;
|
||||
let mut scrolled_to_top = false;
|
||||
if item_top < scroll_top + padding.top {
|
||||
scrolled_to_top = true;
|
||||
updated_scroll_offset.y = -(item_top) + padding.top;
|
||||
} else if item_bottom > scroll_top + list_height - padding.bottom {
|
||||
scrolled_to_top = true;
|
||||
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
|
||||
}
|
||||
|
||||
match scroll_strategy {
|
||||
ScrollStrategy::Top => {}
|
||||
ScrollStrategy::Center => {
|
||||
if scrolled_to_top {
|
||||
let item_center = item_top + item_height / 2.0;
|
||||
let target_scroll_top = item_center - list_height / 2.0;
|
||||
|
||||
if item_top < scroll_top
|
||||
|| item_bottom > scroll_top + list_height
|
||||
{
|
||||
updated_scroll_offset.y = -target_scroll_top
|
||||
.max(Pixels::ZERO)
|
||||
.min(content_height - list_height)
|
||||
.max(Pixels::ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scroll_offset = *updated_scroll_offset
|
||||
scroll_offset = *updated_scroll_offset;
|
||||
}
|
||||
|
||||
let first_visible_element_ix =
|
||||
|
||||
@@ -60,8 +60,10 @@ impl SvgRenderer {
|
||||
let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width.into(), size.height.into())
|
||||
.ok_or(usvg::Error::InvalidSize)?;
|
||||
|
||||
let scale = size.width.0 as f32 / tree.size().width();
|
||||
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
|
||||
let transform = tree.view_box().to_transform(
|
||||
resvg::tiny_skia::Size::from_wh(size.width.0 as f32, size.height.0 as f32)
|
||||
.ok_or(usvg::Error::InvalidSize)?,
|
||||
);
|
||||
|
||||
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ paths.workspace = true
|
||||
serde.workspace = true
|
||||
strum.workspace = true
|
||||
util.workspace = true
|
||||
extension_host.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
indoc.workspace = true
|
||||
|
||||
@@ -2,7 +2,6 @@ mod item;
|
||||
mod to_markdown;
|
||||
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use extension_host::DocsDatabase;
|
||||
use futures::future::BoxFuture;
|
||||
pub use item::*;
|
||||
use parking_lot::RwLock;
|
||||
@@ -209,7 +208,7 @@ impl IndexedDocsProvider for DocsDotRsProvider {
|
||||
|
||||
async fn index_rustdoc(
|
||||
package: PackageName,
|
||||
database: Arc<dyn DocsDatabase>,
|
||||
database: Arc<IndexedDocsDatabase>,
|
||||
fetch_page: impl Fn(&PackageName, Option<&RustdocItem>) -> BoxFuture<'static, Result<Option<String>>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
|
||||
@@ -324,10 +324,8 @@ impl IndexedDocsDatabase {
|
||||
Ok(any)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl extension_host::DocsDatabase for IndexedDocsDatabase {
|
||||
fn insert(&self, key: String, docs: String) -> Task<Result<()>> {
|
||||
pub fn insert(&self, key: String, docs: String) -> Task<Result<()>> {
|
||||
let env = self.env.clone();
|
||||
let entries = self.entries;
|
||||
|
||||
|
||||
@@ -288,14 +288,14 @@ impl LanguageRegistry {
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: Default::default(),
|
||||
toolchain_provider: None,
|
||||
context_provider: None,
|
||||
})
|
||||
}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -436,8 +436,9 @@ impl LanguageRegistry {
|
||||
name: LanguageName,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
load: impl Fn() -> Result<LoadedLanguage> + 'static + Send + Sync,
|
||||
) {
|
||||
let load = Arc::new(load);
|
||||
let state = &mut *self.state.write();
|
||||
|
||||
for existing_language in &mut state.available_languages {
|
||||
|
||||
@@ -185,8 +185,6 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
_ => {
|
||||
let model = match self.model {
|
||||
CopilotChatModel::Gpt4o => open_ai::Model::FourOmni,
|
||||
CopilotChatModel::Gpt4oMini => open_ai::Model::FourOmniMini,
|
||||
CopilotChatModel::Gpt4Turbo => open_ai::Model::FourTurbo,
|
||||
CopilotChatModel::Gpt4 => open_ai::Model::Four,
|
||||
CopilotChatModel::Gpt3_5Turbo => open_ai::Model::ThreePointFiveTurbo,
|
||||
CopilotChatModel::O1Preview | CopilotChatModel::O1Mini => open_ai::Model::Four,
|
||||
|
||||
@@ -2,8 +2,8 @@ use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
|
||||
use gpui::{
|
||||
actions, div, rems, uniform_list, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
|
||||
Hsla, InteractiveElement, IntoElement, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
ParentElement, Render, ScrollStrategy, SharedString, Styled, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
ParentElement, Render, SharedString, Styled, UniformListScrollHandle, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{Buffer, OwnedSyntaxLayer};
|
||||
use std::{mem, ops::Range};
|
||||
@@ -199,8 +199,7 @@ impl SyntaxTreeView {
|
||||
|
||||
let descendant_ix = cursor.descendant_index();
|
||||
self.selected_descendant_ix = Some(descendant_ix);
|
||||
self.list_scroll_handle
|
||||
.scroll_to_item(descendant_ix, ScrollStrategy::Center);
|
||||
self.list_scroll_handle.scroll_to_item(descendant_ix);
|
||||
|
||||
cx.notify();
|
||||
Some(())
|
||||
|
||||
@@ -61,14 +61,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: None,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr) => {
|
||||
@@ -82,14 +82,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: None,
|
||||
toolchain_provider: None,
|
||||
})
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr, $context_provider:expr) => {
|
||||
@@ -103,14 +103,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: Some(Arc::new($context_provider)),
|
||||
toolchain_provider: None,
|
||||
})
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
($name:literal, $adapters:expr, $context_provider:expr, $toolchain_provider:expr) => {
|
||||
@@ -124,14 +124,14 @@ pub fn init(languages: Arc<LanguageRegistry>, node_runtime: NodeRuntime, cx: &mu
|
||||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
Arc::new(move || {
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: load_queries($name),
|
||||
context_provider: Some(Arc::new($context_provider)),
|
||||
toolchain_provider: Some($toolchain_provider),
|
||||
})
|
||||
}),
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use gpui::{
|
||||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||
IntoElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, Model, MouseButton,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful,
|
||||
StatefulInteractiveElement as _, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
@@ -1078,8 +1078,7 @@ impl OutlinePanel {
|
||||
.iter()
|
||||
.position(|cached_entry| &cached_entry.entry == selected_entry);
|
||||
if let Some(index) = index {
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Center);
|
||||
self.scroll_handle.scroll_to_item(index);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +165,6 @@ pub fn extensions_dir() -> &'static PathBuf {
|
||||
EXTENSIONS_DIR.get_or_init(|| support_dir().join("extensions"))
|
||||
}
|
||||
|
||||
/// Returns the path to the extensions directory.
|
||||
///
|
||||
/// This is where installed extensions are stored on a remote.
|
||||
pub fn remote_extensions_dir() -> &'static PathBuf {
|
||||
static EXTENSIONS_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
EXTENSIONS_DIR.get_or_init(|| support_dir().join("remote_extensions"))
|
||||
}
|
||||
|
||||
/// Returns the path to the extensions directory.
|
||||
///
|
||||
/// This is where installed extensions are stored on a remote.
|
||||
pub fn remote_extensions_uploads_dir() -> &'static PathBuf {
|
||||
static UPLOAD_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||
UPLOAD_DIR.get_or_init(|| remote_extensions_dir().join("uploads"))
|
||||
}
|
||||
|
||||
/// Returns the path to the themes directory.
|
||||
///
|
||||
/// This is where themes that are not provided by extensions are stored.
|
||||
|
||||
@@ -3,8 +3,8 @@ use editor::{scroll::Autoscroll, Editor};
|
||||
use gpui::{
|
||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListSizingBehavior, ListState,
|
||||
MouseButton, MouseUpEvent, Render, ScrollStrategy, Task, UniformListScrollHandle, View,
|
||||
ViewContext, WindowContext,
|
||||
MouseButton, MouseUpEvent, Render, Task, UniformListScrollHandle, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use head::Head;
|
||||
use serde::Deserialize;
|
||||
@@ -495,9 +495,7 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
fn scroll_to_item_index(&mut self, ix: usize) {
|
||||
match &mut self.element_container {
|
||||
ElementContainer::List(state) => state.scroll_to_reveal_item(ix),
|
||||
ElementContainer::UniformList(scroll_handle) => {
|
||||
scroll_handle.scroll_to_item(ix, ScrollStrategy::Top)
|
||||
}
|
||||
ElementContainer::UniformList(scroll_handle) => scroll_handle.scroll_to_item(ix),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ async-trait.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
fancy-regex.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
|
||||
use anyhow::Result;
|
||||
use client::proto;
|
||||
use fancy_regex::Regex as FancyRegex;
|
||||
use gpui::Model;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use regex::{Captures, Regex, RegexBuilder};
|
||||
@@ -54,26 +55,75 @@ impl SearchInputs {
|
||||
&self.buffers
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SearchQuery {
|
||||
Text {
|
||||
search: Arc<AhoCorasick>,
|
||||
replacement: Option<String>,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
include_ignored: bool,
|
||||
inner: SearchInputs,
|
||||
},
|
||||
|
||||
Regex {
|
||||
regex: Regex,
|
||||
replacement: Option<String>,
|
||||
multiline: bool,
|
||||
whole_word: bool,
|
||||
case_sensitive: bool,
|
||||
include_ignored: bool,
|
||||
inner: SearchInputs,
|
||||
},
|
||||
#[derive(Clone, Debug)]
|
||||
enum RegexEngine {
|
||||
Regex(Regex),
|
||||
FancyRegex(FancyRegex),
|
||||
}
|
||||
|
||||
impl RegexEngine {
|
||||
fn detect(&self, text: &str) -> Result<bool> {
|
||||
match self {
|
||||
Self::Regex(regex) => Ok(regex.find(text).is_some()),
|
||||
Self::FancyRegex(fancy_regex) => Ok(fancy_regex.find(text)?.is_some()),
|
||||
}
|
||||
}
|
||||
fn replace<'a>(&self, text: &'a str, replacement: &str) -> Cow<'a, str> {
|
||||
match self {
|
||||
Self::Regex(regex) => regex.replace(text, replacement),
|
||||
Self::FancyRegex(fancy_regex) => fancy_regex.replace(text, replacement),
|
||||
}
|
||||
}
|
||||
async fn find_and_extend_matches(
|
||||
&self,
|
||||
text: &str,
|
||||
offset: usize,
|
||||
matches: &mut Vec<Range<usize>>,
|
||||
yield_interval: usize,
|
||||
) {
|
||||
match self {
|
||||
Self::Regex(regex) => {
|
||||
for (i, mat) in regex.find_iter(text).enumerate() {
|
||||
if (i + 1) % yield_interval == 0 {
|
||||
yield_now().await;
|
||||
}
|
||||
matches.push(mat.start() + offset..mat.end() + offset)
|
||||
}
|
||||
}
|
||||
Self::FancyRegex(fancy_regex) => {
|
||||
for (i, mat) in fancy_regex.find_iter(text).enumerate() {
|
||||
if (i + 1) % yield_interval == 0 {
|
||||
// REVIEW: revisit this yield interval and how it interacts with the outer
|
||||
// line loop, etc...
|
||||
yield_now().await;
|
||||
}
|
||||
if let Ok(mat) = mat {
|
||||
matches.push(mat.start() + offset..mat.end() + offset)
|
||||
} else {
|
||||
// REVIEW: can consider ignoring or percolating up, or logging to see
|
||||
// if this ever actually happens.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SearchQuery {
|
||||
method: SearchQueryMethod,
|
||||
replacement: Option<String>,
|
||||
pub whole_word: bool,
|
||||
pub case_sensitive: bool,
|
||||
inner: SearchInputs,
|
||||
pub include_ignored: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum SearchQueryMethod {
|
||||
Text { search: Arc<AhoCorasick> },
|
||||
Regex { regex: RegexEngine, multiline: bool },
|
||||
}
|
||||
|
||||
impl SearchQuery {
|
||||
@@ -96,8 +146,10 @@ impl SearchQuery {
|
||||
files_to_include,
|
||||
buffers,
|
||||
};
|
||||
Ok(Self::Text {
|
||||
search: Arc::new(search),
|
||||
Ok(Self {
|
||||
method: SearchQueryMethod::Text {
|
||||
search: Arc::new(search),
|
||||
},
|
||||
replacement: None,
|
||||
whole_word,
|
||||
case_sensitive,
|
||||
@@ -129,17 +181,24 @@ impl SearchQuery {
|
||||
let regex = RegexBuilder::new(&query)
|
||||
.case_insensitive(!case_sensitive)
|
||||
.multi_line(multiline)
|
||||
.build()?;
|
||||
.build()
|
||||
.map_err(|e| {
|
||||
// REVIEW: Found by using: (?<!user|tenant)RecordId = randomUUID\(\);
|
||||
log::error!("Failed to build regex: {}", e);
|
||||
e
|
||||
})?;
|
||||
let inner = SearchInputs {
|
||||
query: initial_query,
|
||||
files_to_exclude,
|
||||
files_to_include,
|
||||
buffers,
|
||||
};
|
||||
Ok(Self::Regex {
|
||||
regex,
|
||||
Ok(Self {
|
||||
method: SearchQueryMethod::Regex {
|
||||
regex: RegexEngine::Regex(regex),
|
||||
multiline,
|
||||
},
|
||||
replacement: None,
|
||||
multiline,
|
||||
whole_word,
|
||||
case_sensitive,
|
||||
include_ignored,
|
||||
@@ -172,28 +231,17 @@ impl SearchQuery {
|
||||
}
|
||||
|
||||
pub fn with_replacement(mut self, new_replacement: String) -> Self {
|
||||
match self {
|
||||
Self::Text {
|
||||
ref mut replacement,
|
||||
..
|
||||
}
|
||||
| Self::Regex {
|
||||
ref mut replacement,
|
||||
..
|
||||
} => {
|
||||
*replacement = Some(new_replacement);
|
||||
self
|
||||
}
|
||||
}
|
||||
self.replacement = Some(new_replacement);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_proto(&self) -> proto::SearchQuery {
|
||||
proto::SearchQuery {
|
||||
query: self.as_str().to_string(),
|
||||
regex: self.is_regex(),
|
||||
whole_word: self.whole_word(),
|
||||
case_sensitive: self.case_sensitive(),
|
||||
include_ignored: self.include_ignored(),
|
||||
whole_word: self.whole_word,
|
||||
case_sensitive: self.case_sensitive,
|
||||
include_ignored: self.include_ignored,
|
||||
files_to_include: self.files_to_include().sources().join(","),
|
||||
files_to_exclude: self.files_to_exclude().sources().join(","),
|
||||
}
|
||||
@@ -204,8 +252,8 @@ impl SearchQuery {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Text { search, .. } => {
|
||||
match &self.method {
|
||||
SearchQueryMethod::Text { search, .. } => {
|
||||
let mat = search.stream_find_iter(stream).next();
|
||||
match mat {
|
||||
Some(Ok(_)) => Ok(true),
|
||||
@@ -213,7 +261,7 @@ impl SearchQuery {
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
Self::Regex {
|
||||
SearchQueryMethod::Regex {
|
||||
regex, multiline, ..
|
||||
} => {
|
||||
let mut reader = BufReader::new(stream);
|
||||
@@ -222,12 +270,12 @@ impl SearchQuery {
|
||||
if let Err(err) = reader.read_to_string(&mut text) {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Ok(regex.find(&text).is_some())
|
||||
regex.detect(&text)
|
||||
}
|
||||
} else {
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
if regex.find(&line).is_some() {
|
||||
if regex.detect(&line)? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@@ -238,20 +286,14 @@ impl SearchQuery {
|
||||
}
|
||||
/// Returns the replacement text for this `SearchQuery`.
|
||||
pub fn replacement(&self) -> Option<&str> {
|
||||
match self {
|
||||
SearchQuery::Text { replacement, .. } | SearchQuery::Regex { replacement, .. } => {
|
||||
replacement.as_deref()
|
||||
}
|
||||
}
|
||||
self.replacement.as_deref()
|
||||
}
|
||||
/// Replaces search hits if replacement is set. `text` is assumed to be a string that matches this `SearchQuery` exactly, without any leftovers on either side.
|
||||
pub fn replacement_for<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
|
||||
match self {
|
||||
SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from),
|
||||
SearchQuery::Regex {
|
||||
regex, replacement, ..
|
||||
} => {
|
||||
if let Some(replacement) = replacement {
|
||||
match &self.method {
|
||||
SearchQueryMethod::Text { .. } => self.replacement.clone().map(Cow::from),
|
||||
SearchQueryMethod::Regex { regex, .. } => {
|
||||
if let Some(ref replacement) = self.replacement {
|
||||
let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX
|
||||
.get_or_init(|| Regex::new(r"\\\\|\\n|\\t").unwrap())
|
||||
.replace_all(replacement, |c: &Captures| {
|
||||
@@ -262,7 +304,7 @@ impl SearchQuery {
|
||||
x => unreachable!("Unexpected escape sequence: {}", x),
|
||||
}
|
||||
});
|
||||
Some(regex.replace(text, replacement))
|
||||
Some(regex.replace(text, &replacement))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -288,11 +330,9 @@ impl SearchQuery {
|
||||
buffer.as_rope().clone()
|
||||
};
|
||||
|
||||
let mut matches = Vec::new();
|
||||
match self {
|
||||
Self::Text {
|
||||
search, whole_word, ..
|
||||
} => {
|
||||
let mut matches: Vec<Range<usize>> = Vec::new();
|
||||
match &self.method {
|
||||
SearchQueryMethod::Text { search, .. } => {
|
||||
for (ix, mat) in search
|
||||
.stream_find_iter(rope.bytes_in_range(0..rope.len()))
|
||||
.enumerate()
|
||||
@@ -302,7 +342,7 @@ impl SearchQuery {
|
||||
}
|
||||
|
||||
let mat = mat.unwrap();
|
||||
if *whole_word {
|
||||
if self.whole_word {
|
||||
let classifier = buffer.char_classifier_at(range_offset + mat.start());
|
||||
|
||||
let prev_kind = rope
|
||||
@@ -322,18 +362,14 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
|
||||
Self::Regex {
|
||||
SearchQueryMethod::Regex {
|
||||
regex, multiline, ..
|
||||
} => {
|
||||
if *multiline {
|
||||
let text = rope.to_string();
|
||||
for (ix, mat) in regex.find_iter(&text).enumerate() {
|
||||
if (ix + 1) % YIELD_INTERVAL == 0 {
|
||||
yield_now().await;
|
||||
}
|
||||
|
||||
matches.push(mat.start()..mat.end());
|
||||
}
|
||||
regex
|
||||
.find_and_extend_matches(&text, 0, &mut matches, YIELD_INTERVAL)
|
||||
.await;
|
||||
} else {
|
||||
let mut line = String::new();
|
||||
let mut line_offset = 0;
|
||||
@@ -344,12 +380,14 @@ impl SearchQuery {
|
||||
|
||||
for (newline_ix, text) in chunk.split('\n').enumerate() {
|
||||
if newline_ix > 0 {
|
||||
for mat in regex.find_iter(&line) {
|
||||
let start = line_offset + mat.start();
|
||||
let end = line_offset + mat.end();
|
||||
matches.push(start..end);
|
||||
}
|
||||
|
||||
regex
|
||||
.find_and_extend_matches(
|
||||
&line,
|
||||
line_offset,
|
||||
&mut matches,
|
||||
YIELD_INTERVAL,
|
||||
)
|
||||
.await;
|
||||
line_offset += line.len() + 1;
|
||||
line.clear();
|
||||
}
|
||||
@@ -371,33 +409,8 @@ impl SearchQuery {
|
||||
self.as_inner().as_str()
|
||||
}
|
||||
|
||||
pub fn whole_word(&self) -> bool {
|
||||
match self {
|
||||
Self::Text { whole_word, .. } => *whole_word,
|
||||
Self::Regex { whole_word, .. } => *whole_word,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn case_sensitive(&self) -> bool {
|
||||
match self {
|
||||
Self::Text { case_sensitive, .. } => *case_sensitive,
|
||||
Self::Regex { case_sensitive, .. } => *case_sensitive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn include_ignored(&self) -> bool {
|
||||
match self {
|
||||
Self::Text {
|
||||
include_ignored, ..
|
||||
} => *include_ignored,
|
||||
Self::Regex {
|
||||
include_ignored, ..
|
||||
} => *include_ignored,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_regex(&self) -> bool {
|
||||
matches!(self, Self::Regex { .. })
|
||||
matches!(self.method, SearchQueryMethod::Regex { .. })
|
||||
}
|
||||
|
||||
pub fn files_to_include(&self) -> &PathMatcher {
|
||||
@@ -436,9 +449,7 @@ impl SearchQuery {
|
||||
}
|
||||
}
|
||||
pub fn as_inner(&self) -> &SearchInputs {
|
||||
match self {
|
||||
Self::Regex { inner, .. } | Self::Text { inner, .. } => inner,
|
||||
}
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ use gpui::{
|
||||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, DragMoveEvent, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
|
||||
InteractiveElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, Model,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy,
|
||||
Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
|
||||
VisualContext as _, WeakView, WindowContext,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful,
|
||||
Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext as _,
|
||||
WeakView, WindowContext,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
@@ -1356,8 +1356,7 @@ impl ProjectPanel {
|
||||
|
||||
fn autoscroll(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Center);
|
||||
self.scroll_handle.scroll_to_item(index);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2251,14 +2251,10 @@ message ContextOperation {
|
||||
InsertMessage insert_message = 1;
|
||||
UpdateMessage update_message = 2;
|
||||
UpdateSummary update_summary = 3;
|
||||
SlashCommandFinished slash_command_finished = 4;
|
||||
BufferOperation buffer_operation = 5;
|
||||
SlashCommandStarted slash_command_started = 6;
|
||||
SlashCommandOutputSectionAdded slash_command_output_section_added = 7;
|
||||
SlashCommandCompleted slash_command_completed = 8;
|
||||
}
|
||||
|
||||
reserved 4;
|
||||
|
||||
message InsertMessage {
|
||||
ContextMessage message = 1;
|
||||
repeated VectorClockEntry version = 2;
|
||||
@@ -2279,26 +2275,13 @@ message ContextOperation {
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message SlashCommandStarted {
|
||||
message SlashCommandFinished {
|
||||
LamportTimestamp id = 1;
|
||||
AnchorRange output_range = 2;
|
||||
string name = 3;
|
||||
repeated SlashCommandOutputSection sections = 3;
|
||||
repeated VectorClockEntry version = 4;
|
||||
}
|
||||
|
||||
message SlashCommandOutputSectionAdded {
|
||||
LamportTimestamp timestamp = 1;
|
||||
SlashCommandOutputSection section = 2;
|
||||
repeated VectorClockEntry version = 3;
|
||||
}
|
||||
|
||||
message SlashCommandCompleted {
|
||||
LamportTimestamp id = 1;
|
||||
LamportTimestamp timestamp = 3;
|
||||
optional string error_message = 4;
|
||||
repeated VectorClockEntry version = 5;
|
||||
}
|
||||
|
||||
message BufferOperation {
|
||||
Operation operation = 1;
|
||||
}
|
||||
|
||||
@@ -990,19 +990,6 @@ impl SshRemoteClient {
|
||||
.map(|ssh_connection| ssh_connection.ssh_args())
|
||||
}
|
||||
|
||||
pub fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: PathBuf,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
let state = self.state.lock();
|
||||
let Some(connection) = state.as_ref().and_then(|state| state.ssh_connection()) else {
|
||||
return Task::ready(Err(anyhow!("no ssh connection")));
|
||||
};
|
||||
connection.upload_directory(src_path, dest_path, cx)
|
||||
}
|
||||
|
||||
pub fn proto_client(&self) -> AnyProtoClient {
|
||||
self.client.clone().into()
|
||||
}
|
||||
@@ -1207,12 +1194,6 @@ trait RemoteConnection: Send + Sync {
|
||||
delegate: Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Task<Result<i32>>;
|
||||
fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: PathBuf,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>>;
|
||||
async fn kill(&self) -> Result<()>;
|
||||
fn has_been_killed(&self) -> bool;
|
||||
fn ssh_args(&self) -> Vec<String>;
|
||||
@@ -1251,49 +1232,6 @@ impl RemoteConnection for SshRemoteConnection {
|
||||
fn connection_options(&self) -> SshConnectionOptions {
|
||||
self.socket.connection_options.clone()
|
||||
}
|
||||
|
||||
fn upload_directory(
|
||||
&self,
|
||||
src_path: PathBuf,
|
||||
dest_path: PathBuf,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
let mut command = process::Command::new("scp");
|
||||
let output = self
|
||||
.socket
|
||||
.ssh_options(&mut command)
|
||||
.args(
|
||||
self.socket
|
||||
.connection_options
|
||||
.port
|
||||
.map(|port| vec!["-P".to_string(), port.to_string()])
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.arg("-r")
|
||||
.arg(&src_path)
|
||||
.arg(format!(
|
||||
"{}:{}",
|
||||
self.socket.connection_options.scp_url(),
|
||||
dest_path.display()
|
||||
))
|
||||
.output();
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
let output = output.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"failed to upload directory {} -> {}: {}",
|
||||
src_path.display(),
|
||||
dest_path.display(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn start_proxy(
|
||||
&self,
|
||||
unique_identifier: String,
|
||||
@@ -2348,7 +2286,7 @@ mod fake {
|
||||
},
|
||||
select_biased, FutureExt, SinkExt, StreamExt,
|
||||
};
|
||||
use gpui::{AppContext, AsyncAppContext, SemanticVersion, Task, TestAppContext};
|
||||
use gpui::{AsyncAppContext, SemanticVersion, Task, TestAppContext};
|
||||
use release_channel::ReleaseChannel;
|
||||
use rpc::proto::Envelope;
|
||||
|
||||
@@ -2392,14 +2330,6 @@ mod fake {
|
||||
fn ssh_args(&self) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
fn upload_directory(
|
||||
&self,
|
||||
_src_path: PathBuf,
|
||||
_dest_path: PathBuf,
|
||||
_cx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn connection_options(&self) -> SshConnectionOptions {
|
||||
self.connection_options.clone()
|
||||
|
||||
@@ -78,7 +78,7 @@ impl HeadlessProject {
|
||||
});
|
||||
let prettier_store = cx.new_model(|cx| {
|
||||
PrettierStore::new(
|
||||
node_runtime.clone(),
|
||||
node_runtime,
|
||||
fs.clone(),
|
||||
languages.clone(),
|
||||
worktree_store.clone(),
|
||||
@@ -124,7 +124,7 @@ impl HeadlessProject {
|
||||
toolchain_store.clone(),
|
||||
environment,
|
||||
languages.clone(),
|
||||
http_client.clone(),
|
||||
http_client,
|
||||
fs.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -1086,45 +1086,6 @@ async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext)
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_root_rename(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(server_cx.executor());
|
||||
fs.insert_tree(
|
||||
"/code",
|
||||
json!({
|
||||
"project1": {
|
||||
".git": {},
|
||||
"README.md": "# project 1",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (project, _) = init_test(&fs, cx, server_cx).await;
|
||||
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project1", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
fs.rename(
|
||||
&PathBuf::from("/code/project1"),
|
||||
&PathBuf::from("/code/project2"),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
worktree.update(cx, |worktree, _| {
|
||||
assert_eq!(worktree.root_name(), "project2")
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
let fs = FakeFs::new(server_cx.executor());
|
||||
@@ -1226,9 +1187,6 @@ pub async fn init_test(
|
||||
cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
server_cx.update(|cx| {
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
});
|
||||
init_logger();
|
||||
|
||||
let (opts, ssh_server_client) = SshRemoteClient::fake_server(cx, server_cx);
|
||||
|
||||
@@ -14,7 +14,6 @@ use node_runtime::{NodeBinaryOptions, NodeRuntime};
|
||||
use paths::logs_dir;
|
||||
use project::project_settings::ProjectSettings;
|
||||
|
||||
use release_channel::AppVersion;
|
||||
use remote::proxy::ProxyLaunchError;
|
||||
use remote::ssh_session::ChannelClient;
|
||||
use remote::{
|
||||
@@ -378,8 +377,6 @@ fn init_paths() -> anyhow::Result<()> {
|
||||
paths::languages_dir(),
|
||||
paths::logs_dir(),
|
||||
paths::temp_dir(),
|
||||
paths::remote_extensions_dir(),
|
||||
paths::remote_extensions_uploads_dir(),
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
@@ -421,9 +418,6 @@ pub fn execute_run(
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
gpui::App::headless().run(move |cx| {
|
||||
settings::init(cx);
|
||||
let app_version = AppVersion::init(env!("ZED_PKG_VERSION"));
|
||||
release_channel::init(app_version, cx);
|
||||
|
||||
HeadlessProject::init(cx);
|
||||
|
||||
log::info!("gpui app started, initializing server");
|
||||
|
||||
@@ -1067,7 +1067,7 @@ impl ProjectSearchView {
|
||||
let range_to_select = match_ranges[new_index].clone();
|
||||
self.results_editor.update(cx, |editor, cx| {
|
||||
let range_to_select = editor.range_for_match(&range_to_select);
|
||||
editor.unfold_ranges(&[range_to_select.clone()], false, true, cx);
|
||||
editor.unfold_ranges([range_to_select.clone()], false, true, cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges([range_to_select])
|
||||
});
|
||||
@@ -1747,7 +1747,7 @@ impl Render for ProjectSearchBar {
|
||||
.child(
|
||||
div()
|
||||
.id("matches")
|
||||
.ml_1()
|
||||
.ml_0p5()
|
||||
.child(
|
||||
Label::new(match_text).color(if search.active_match_index.is_some() {
|
||||
Color::Default
|
||||
@@ -1765,8 +1765,10 @@ impl Render for ProjectSearchBar {
|
||||
let search_line = h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.pr_6()
|
||||
.child(query_column)
|
||||
.child(h_flex().min_w_40().child(mode_column).child(matches_column));
|
||||
.child(mode_column)
|
||||
.child(matches_column);
|
||||
|
||||
let replace_line = search.replace_enabled.then(|| {
|
||||
let replace_column =
|
||||
@@ -1774,60 +1776,57 @@ impl Render for ProjectSearchBar {
|
||||
|
||||
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
|
||||
|
||||
let replace_actions =
|
||||
h_flex()
|
||||
.min_w_40()
|
||||
.gap_1()
|
||||
.when(search.replace_enabled, |this| {
|
||||
this.child(
|
||||
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(search) = this.active_project_search.as_ref() {
|
||||
search.update(cx, |this, cx| {
|
||||
this.replace_next(&ReplaceNext, cx);
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace Next Match",
|
||||
&ReplaceNext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(search) = this.active_project_search.as_ref() {
|
||||
search.update(cx, |this, cx| {
|
||||
this.replace_all(&ReplaceAll, cx);
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace All Matches",
|
||||
&ReplaceAll,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
let replace_actions = h_flex().gap_1().when(search.replace_enabled, |this| {
|
||||
this.child(
|
||||
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(search) = this.active_project_search.as_ref() {
|
||||
search.update(cx, |this, cx| {
|
||||
this.replace_next(&ReplaceNext, cx);
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace Next Match",
|
||||
&ReplaceNext,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(search) = this.active_project_search.as_ref() {
|
||||
search.update(cx, |this, cx| {
|
||||
this.replace_all(&ReplaceAll, cx);
|
||||
})
|
||||
}
|
||||
}))
|
||||
.tooltip({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Replace All Matches",
|
||||
&ReplaceAll,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}),
|
||||
)
|
||||
});
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.pr_24()
|
||||
.child(replace_column)
|
||||
.child(replace_actions)
|
||||
});
|
||||
@@ -1836,6 +1835,7 @@ impl Render for ProjectSearchBar {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1p5()
|
||||
.pr_24()
|
||||
.child(
|
||||
input_base_styles()
|
||||
.on_action(
|
||||
@@ -1858,12 +1858,10 @@ impl Render for ProjectSearchBar {
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w_40()
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new("project-search-opened-only", IconName::FileSearch)
|
||||
IconButton::new("project-search-opened-only", IconName::FileDoc)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.selected(self.is_opened_only_enabled(cx))
|
||||
.tooltip(|cx| Tooltip::text("Only Search Open Files", cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing, TintColor};
|
||||
use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing};
|
||||
use crate::{
|
||||
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
|
||||
};
|
||||
@@ -437,103 +437,3 @@ impl RenderOnce for Button {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Button {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A button allows users to take actions, and make choices, with a single tap."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example("Default", Button::new("default", "Default")),
|
||||
single_example(
|
||||
"Filled",
|
||||
Button::new("filled", "Filled").style(ButtonStyle::Filled),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group(
|
||||
"Tinted",
|
||||
vec![
|
||||
single_example(
|
||||
"Accent",
|
||||
Button::new("tinted_accent", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
),
|
||||
single_example(
|
||||
"Negative",
|
||||
Button::new("tinted_negative", "Negative")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Button::new("tinted_warning", "Warning")
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
),
|
||||
single_example(
|
||||
"Positive",
|
||||
Button::new("tinted_positive", "Positive")
|
||||
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group(
|
||||
"States",
|
||||
vec![
|
||||
single_example("Default", Button::new("default_state", "Default")),
|
||||
single_example(
|
||||
"Disabled",
|
||||
Button::new("disabled", "Disabled").disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Button::new("selected", "Selected").selected(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group(
|
||||
"With Icons",
|
||||
vec![
|
||||
single_example(
|
||||
"Icon Start",
|
||||
Button::new("icon_start", "Icon Start")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::Start),
|
||||
),
|
||||
single_example(
|
||||
"Icon End",
|
||||
Button::new("icon_end", "Icon End")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::End),
|
||||
),
|
||||
single_example(
|
||||
"Icon Color",
|
||||
Button::new("icon_color", "Icon Color")
|
||||
.icon(IconName::Check)
|
||||
.icon_color(Color::Accent),
|
||||
),
|
||||
single_example(
|
||||
"Tinted Icons",
|
||||
Button::new("icon_color", "Delete")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.color(Color::Error)
|
||||
.icon_color(Color::Error)
|
||||
.icon(IconName::Trash)
|
||||
.icon_position(IconPosition::Start),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,51 +115,3 @@ impl RenderOnce for Checkbox {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Checkbox {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group(
|
||||
"Default",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
Checkbox::new("checkbox_unselected", Selection::Unselected),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
Checkbox::new("checkbox_indeterminate", Selection::Indeterminate),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Checkbox::new("checkbox_selected", Selection::Selected),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group(
|
||||
"Disabled",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
Checkbox::new("checkbox_disabled_unselected", Selection::Unselected)
|
||||
.disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
Checkbox::new("checkbox_disabled_indeterminate", Selection::Indeterminate)
|
||||
.disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Checkbox::new("checkbox_disabled_selected", Selection::Selected)
|
||||
.disabled(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{prelude::*, Avatar};
|
||||
#![allow(missing_docs)]
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, StyleRefinement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -14,12 +15,10 @@ pub struct Facepile {
|
||||
}
|
||||
|
||||
impl Facepile {
|
||||
/// Creates a new empty facepile.
|
||||
pub fn empty() -> Self {
|
||||
Self::new(SmallVec::new())
|
||||
}
|
||||
|
||||
/// Creates a new facepile with the given faces.
|
||||
pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
|
||||
Self { base: div(), faces }
|
||||
}
|
||||
@@ -59,61 +58,3 @@ impl RenderOnce for Facepile {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Facepile {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A facepile is a collection of faces stacked horizontally–\
|
||||
always with the leftmost face on top and descending in z-index.\
|
||||
\n\nFacepiles are used to display a group of people or things,\
|
||||
such as a list of participants in a collaboration session."
|
||||
}
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
let few_faces: [&'static str; 3] = [
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
];
|
||||
|
||||
let many_faces: [&'static str; 6] = [
|
||||
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
];
|
||||
|
||||
vec![example_group(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Few Faces",
|
||||
Facepile::new(
|
||||
few_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Many Faces",
|
||||
Facepile::new(
|
||||
many_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Custom Size",
|
||||
Facepile::new(
|
||||
few_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
],
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||
use ui_macros::DerivePathStr;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
traits::component_preview::{example_group, ComponentExample, ComponentPreview},
|
||||
Indicator,
|
||||
};
|
||||
use crate::{prelude::*, Indicator};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum AnyIcon {
|
||||
@@ -192,7 +188,6 @@ pub enum IconName {
|
||||
FileGit,
|
||||
FileLock,
|
||||
FileRust,
|
||||
FileSearch,
|
||||
FileText,
|
||||
FileToml,
|
||||
FileTree,
|
||||
@@ -499,26 +494,3 @@ impl RenderOnce for IconWithIndicator {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Icon {
|
||||
fn examples() -> Vec<ComponentExampleGroup<Icon>> {
|
||||
let arrow_icons = vec![
|
||||
IconName::ArrowDown,
|
||||
IconName::ArrowLeft,
|
||||
IconName::ArrowRight,
|
||||
IconName::ArrowUp,
|
||||
IconName::ArrowCircle,
|
||||
];
|
||||
|
||||
vec![example_group(
|
||||
"Arrow Icons",
|
||||
arrow_icons
|
||||
.into_iter()
|
||||
.map(|icon| {
|
||||
let name = format!("{:?}", icon).to_string();
|
||||
ComponentExample::new(name, Icon::new(icon))
|
||||
})
|
||||
.collect(),
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ enum IndicatorKind {
|
||||
#[derive(IntoElement)]
|
||||
pub struct Indicator {
|
||||
kind: IndicatorKind,
|
||||
border_color: Option<Color>,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@ impl Indicator {
|
||||
pub fn dot() -> Self {
|
||||
Self {
|
||||
kind: IndicatorKind::Dot,
|
||||
border_color: None,
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
@@ -28,8 +26,6 @@ impl Indicator {
|
||||
pub fn bar() -> Self {
|
||||
Self {
|
||||
kind: IndicatorKind::Bar,
|
||||
border_color: None,
|
||||
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
@@ -37,8 +33,6 @@ impl Indicator {
|
||||
pub fn icon(icon: impl Into<AnyIcon>) -> Self {
|
||||
Self {
|
||||
kind: IndicatorKind::Icon(icon.into()),
|
||||
border_color: None,
|
||||
|
||||
color: Color::Default,
|
||||
}
|
||||
}
|
||||
@@ -47,25 +41,11 @@ impl Indicator {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn border_color(mut self, color: Color) -> Self {
|
||||
self.border_color = Some(color);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for Indicator {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let container = div().flex_none();
|
||||
let container = if let Some(border_color) = self.border_color {
|
||||
if matches!(self.kind, IndicatorKind::Dot | IndicatorKind::Bar) {
|
||||
container.border_1().border_color(border_color.color(cx))
|
||||
} else {
|
||||
container
|
||||
}
|
||||
} else {
|
||||
container
|
||||
};
|
||||
|
||||
match self.kind {
|
||||
IndicatorKind::Icon(icon) => container
|
||||
@@ -83,34 +63,3 @@ impl RenderOnce for Indicator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Indicator {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"An indicator visually represents a status or state."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group(
|
||||
"Types",
|
||||
vec![
|
||||
single_example("Dot", Indicator::dot().color(Color::Info)),
|
||||
single_example("Bar", Indicator::bar().color(Color::Player(2))),
|
||||
single_example(
|
||||
"Icon",
|
||||
Indicator::icon(Icon::new(IconName::Check).color(Color::Success)),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example("Info", Indicator::dot().color(Color::Info)),
|
||||
single_example("Success", Indicator::dot().color(Color::Success)),
|
||||
single_example("Warning", Indicator::dot().color(Color::Warning)),
|
||||
single_example("Error", Indicator::dot().color(Color::Error)),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use gpui::{
|
||||
|
||||
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography, TextSize};
|
||||
pub use crate::traits::clickable::*;
|
||||
pub use crate::traits::component_preview::*;
|
||||
pub use crate::traits::disableable::*;
|
||||
pub use crate::traits::fixed::*;
|
||||
pub use crate::traits::selectable::*;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod clickable;
|
||||
pub mod component_preview;
|
||||
pub mod disableable;
|
||||
pub mod fixed;
|
||||
pub mod selectable;
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, SharedString};
|
||||
|
||||
/// Implement this trait to enable rich UI previews with metadata in the Theme Preview tool.
|
||||
pub trait ComponentPreview: IntoElement {
|
||||
fn title() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>>;
|
||||
|
||||
fn component_previews() -> Vec<AnyElement> {
|
||||
Self::examples()
|
||||
.into_iter()
|
||||
.map(|example| Self::render_example_group(example))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_component_previews(cx: &WindowContext) -> AnyElement {
|
||||
let title = Self::title();
|
||||
let (source, title) = title
|
||||
.rsplit_once("::")
|
||||
.map_or((None, title), |(s, t)| (Some(s), t));
|
||||
let description = Self::description().into();
|
||||
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_md()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Headline::new(title).size(HeadlineSize::Small))
|
||||
.when_some(source, |this, source| {
|
||||
this.child(Label::new(format!("({})", source)).color(Color::Muted))
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(Self::component_previews())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Label::new(group.title).size(LabelSize::Small))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_6()
|
||||
.children(group.examples.into_iter().map(Self::render_example))
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_example(example: ComponentExample<Self>) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(example.element)
|
||||
.child(
|
||||
Label::new(example.variant_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single example of a component.
|
||||
pub struct ComponentExample<T> {
|
||||
variant_name: SharedString,
|
||||
element: T,
|
||||
}
|
||||
|
||||
impl<T> ComponentExample<T> {
|
||||
/// Create a new example with the given variant name and example value.
|
||||
pub fn new(variant_name: impl Into<SharedString>, example: T) -> Self {
|
||||
Self {
|
||||
variant_name: variant_name.into(),
|
||||
element: example,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A group of component examples.
|
||||
pub struct ComponentExampleGroup<T> {
|
||||
pub title: SharedString,
|
||||
pub examples: Vec<ComponentExample<T>>,
|
||||
}
|
||||
|
||||
impl<T> ComponentExampleGroup<T> {
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn new(title: impl Into<SharedString>, examples: Vec<ComponentExample<T>>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
examples,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a single example
|
||||
pub fn single_example<T>(variant_name: impl Into<SharedString>, example: T) -> ComponentExample<T> {
|
||||
ComponentExample::new(variant_name, example)
|
||||
}
|
||||
|
||||
/// Create a group of examples
|
||||
pub fn example_group<T>(
|
||||
title: impl Into<SharedString>,
|
||||
examples: Vec<ComponentExample<T>>,
|
||||
) -> ComponentExampleGroup<T> {
|
||||
ComponentExampleGroup::new(title, examples)
|
||||
}
|
||||
@@ -628,12 +628,10 @@ fn generate_commands(_: &AppContext) -> Vec<VimCommand> {
|
||||
("tabo", "nly"),
|
||||
workspace::CloseInactiveItems {
|
||||
save_intent: Some(SaveIntent::Close),
|
||||
close_pinned: false,
|
||||
},
|
||||
)
|
||||
.bang(workspace::CloseInactiveItems {
|
||||
save_intent: Some(SaveIntent::Skip),
|
||||
close_pinned: false,
|
||||
}),
|
||||
VimCommand::new(
|
||||
("on", "ly"),
|
||||
|
||||
@@ -105,37 +105,12 @@ pub struct CloseActiveItem {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseInactiveItems {
|
||||
pub save_intent: Option<SaveIntent>,
|
||||
#[serde(default)]
|
||||
pub close_pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseAllItems {
|
||||
pub save_intent: Option<SaveIntent>,
|
||||
#[serde(default)]
|
||||
pub close_pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseCleanItems {
|
||||
#[serde(default)]
|
||||
pub close_pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseItemsToTheRight {
|
||||
#[serde(default)]
|
||||
pub close_pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CloseItemsToTheLeft {
|
||||
#[serde(default)]
|
||||
pub close_pinned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||
@@ -155,9 +130,6 @@ impl_actions!(
|
||||
[
|
||||
CloseAllItems,
|
||||
CloseActiveItem,
|
||||
CloseCleanItems,
|
||||
CloseItemsToTheLeft,
|
||||
CloseItemsToTheRight,
|
||||
CloseInactiveItems,
|
||||
ActivateItem,
|
||||
RevealInProjectPanel,
|
||||
@@ -172,6 +144,9 @@ actions!(
|
||||
ActivateNextItem,
|
||||
ActivateLastItem,
|
||||
AlternateFile,
|
||||
CloseCleanItems,
|
||||
CloseItemsToTheLeft,
|
||||
CloseItemsToTheRight,
|
||||
GoBack,
|
||||
GoForward,
|
||||
JoinIntoNext,
|
||||
@@ -1145,17 +1120,16 @@ impl Pane {
|
||||
}
|
||||
|
||||
let active_item_id = self.items[self.active_item_index].item_id();
|
||||
let non_closeable_items = self.get_non_closeable_item_ids(action.close_pinned);
|
||||
Some(self.close_items(
|
||||
cx,
|
||||
action.save_intent.unwrap_or(SaveIntent::Close),
|
||||
move |item_id| item_id != active_item_id && !non_closeable_items.contains(&item_id),
|
||||
move |item_id| item_id != active_item_id,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn close_clean_items(
|
||||
&mut self,
|
||||
action: &CloseCleanItems,
|
||||
_: &CloseCleanItems,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let item_ids: Vec<_> = self
|
||||
@@ -1163,29 +1137,26 @@ impl Pane {
|
||||
.filter(|item| !item.is_dirty(cx))
|
||||
.map(|item| item.item_id())
|
||||
.collect();
|
||||
let non_closeable_items = self.get_non_closeable_item_ids(action.close_pinned);
|
||||
Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
|
||||
item_ids.contains(&item_id) && !non_closeable_items.contains(&item_id)
|
||||
item_ids.contains(&item_id)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_left(
|
||||
&mut self,
|
||||
action: &CloseItemsToTheLeft,
|
||||
_: &CloseItemsToTheLeft,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].item_id();
|
||||
let non_closeable_items = self.get_non_closeable_item_ids(action.close_pinned);
|
||||
Some(self.close_items_to_the_left_by_id(active_item_id, non_closeable_items, cx))
|
||||
Some(self.close_items_to_the_left_by_id(active_item_id, cx))
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_left_by_id(
|
||||
&mut self,
|
||||
item_id: EntityId,
|
||||
non_closeable_items: Vec<EntityId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let item_ids: Vec<_> = self
|
||||
@@ -1194,27 +1165,25 @@ impl Pane {
|
||||
.map(|item| item.item_id())
|
||||
.collect();
|
||||
self.close_items(cx, SaveIntent::Close, move |item_id| {
|
||||
item_ids.contains(&item_id) && !non_closeable_items.contains(&item_id)
|
||||
item_ids.contains(&item_id)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_right(
|
||||
&mut self,
|
||||
action: &CloseItemsToTheRight,
|
||||
_: &CloseItemsToTheRight,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
if self.items.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let active_item_id = self.items[self.active_item_index].item_id();
|
||||
let non_closeable_items = self.get_non_closeable_item_ids(action.close_pinned);
|
||||
Some(self.close_items_to_the_right_by_id(active_item_id, non_closeable_items, cx))
|
||||
Some(self.close_items_to_the_right_by_id(active_item_id, cx))
|
||||
}
|
||||
|
||||
pub fn close_items_to_the_right_by_id(
|
||||
&mut self,
|
||||
item_id: EntityId,
|
||||
non_closeable_items: Vec<EntityId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let item_ids: Vec<_> = self
|
||||
@@ -1224,7 +1193,7 @@ impl Pane {
|
||||
.map(|item| item.item_id())
|
||||
.collect();
|
||||
self.close_items(cx, SaveIntent::Close, move |item_id| {
|
||||
item_ids.contains(&item_id) && !non_closeable_items.contains(&item_id)
|
||||
item_ids.contains(&item_id)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1237,12 +1206,11 @@ impl Pane {
|
||||
return None;
|
||||
}
|
||||
|
||||
let non_closeable_items = self.get_non_closeable_item_ids(action.close_pinned);
|
||||
Some(self.close_items(
|
||||
cx,
|
||||
action.save_intent.unwrap_or(SaveIntent::Close),
|
||||
|item_id| !non_closeable_items.contains(&item_id),
|
||||
))
|
||||
Some(
|
||||
self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
|
||||
true
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn file_names_for_prompt(
|
||||
@@ -2055,10 +2023,7 @@ impl Pane {
|
||||
)
|
||||
.entry(
|
||||
"Close Others",
|
||||
Some(Box::new(CloseInactiveItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
})),
|
||||
Some(Box::new(CloseInactiveItems { save_intent: None })),
|
||||
cx.handler_for(&pane, move |pane, cx| {
|
||||
pane.close_items(cx, SaveIntent::Close, |id| id != item_id)
|
||||
.detach_and_log_err(cx);
|
||||
@@ -2067,63 +2032,37 @@ impl Pane {
|
||||
.separator()
|
||||
.entry(
|
||||
"Close Left",
|
||||
Some(Box::new(CloseItemsToTheLeft {
|
||||
close_pinned: false,
|
||||
})),
|
||||
Some(Box::new(CloseItemsToTheLeft)),
|
||||
cx.handler_for(&pane, move |pane, cx| {
|
||||
pane.close_items_to_the_left_by_id(
|
||||
item_id,
|
||||
pane.get_non_closeable_item_ids(false),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
pane.close_items_to_the_left_by_id(item_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.entry(
|
||||
"Close Right",
|
||||
Some(Box::new(CloseItemsToTheRight {
|
||||
close_pinned: false,
|
||||
})),
|
||||
Some(Box::new(CloseItemsToTheRight)),
|
||||
cx.handler_for(&pane, move |pane, cx| {
|
||||
pane.close_items_to_the_right_by_id(
|
||||
item_id,
|
||||
pane.get_non_closeable_item_ids(false),
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
pane.close_items_to_the_right_by_id(item_id, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}),
|
||||
)
|
||||
.separator()
|
||||
.entry(
|
||||
"Close Clean",
|
||||
Some(Box::new(CloseCleanItems {
|
||||
close_pinned: false,
|
||||
})),
|
||||
Some(Box::new(CloseCleanItems)),
|
||||
cx.handler_for(&pane, move |pane, cx| {
|
||||
if let Some(task) = pane.close_clean_items(
|
||||
&CloseCleanItems {
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
) {
|
||||
if let Some(task) = pane.close_clean_items(&CloseCleanItems, cx) {
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}),
|
||||
)
|
||||
.entry(
|
||||
"Close All",
|
||||
Some(Box::new(CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
})),
|
||||
Some(Box::new(CloseAllItems { save_intent: None })),
|
||||
cx.handler_for(&pane, |pane, cx| {
|
||||
if let Some(task) = pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
) {
|
||||
if let Some(task) =
|
||||
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
|
||||
{
|
||||
task.detach_and_log_err(cx)
|
||||
}
|
||||
}),
|
||||
@@ -2618,24 +2557,6 @@ impl Pane {
|
||||
pub fn display_nav_history_buttons(&mut self, display: Option<bool>) {
|
||||
self.display_nav_history_buttons = display;
|
||||
}
|
||||
|
||||
fn get_non_closeable_item_ids(&self, close_pinned: bool) -> Vec<EntityId> {
|
||||
if close_pinned {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
self.items
|
||||
.iter()
|
||||
.map(|item| item.item_id())
|
||||
.filter(|item_id| {
|
||||
if let Some(ix) = self.index_for_item_id(*item_id) {
|
||||
self.is_tab_pinned(ix)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for Pane {
|
||||
@@ -3521,13 +3442,7 @@ mod tests {
|
||||
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_inactive_items(
|
||||
&CloseInactiveItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
@@ -3551,17 +3466,10 @@ mod tests {
|
||||
add_labeled_item(&pane, "E", false, cx);
|
||||
assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_clean_items(
|
||||
&CloseCleanItems {
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A^", "C*^"], cx);
|
||||
}
|
||||
|
||||
@@ -3577,12 +3485,7 @@ mod tests {
|
||||
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_items_to_the_left(
|
||||
&CloseItemsToTheLeft {
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
@@ -3602,12 +3505,7 @@ mod tests {
|
||||
set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.close_items_to_the_right(
|
||||
&CloseItemsToTheRight {
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
@@ -3624,42 +3522,17 @@ mod tests {
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
let item_a = add_labeled_item(&pane, "A", false, cx);
|
||||
add_labeled_item(&pane, "A", false, cx);
|
||||
add_labeled_item(&pane, "B", false, cx);
|
||||
add_labeled_item(&pane, "C", false, cx);
|
||||
assert_item_labels(&pane, ["A", "B", "C*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
|
||||
pane.pin_tab_at(ix, cx);
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, ["A*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
|
||||
pane.unpin_tab_at(ix, cx);
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_item_labels(&pane, [], cx);
|
||||
|
||||
add_labeled_item(&pane, "A", true, cx);
|
||||
@@ -3669,13 +3542,7 @@ mod tests {
|
||||
|
||||
let save = pane
|
||||
.update(cx, |pane, cx| {
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -3685,37 +3552,6 @@ mod tests {
|
||||
assert_item_labels(&pane, [], cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_close_all_items_including_pinned(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
|
||||
let project = Project::test(fs, None, cx).await;
|
||||
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
|
||||
|
||||
let item_a = add_labeled_item(&pane, "A", false, cx);
|
||||
add_labeled_item(&pane, "B", false, cx);
|
||||
add_labeled_item(&pane, "C", false, cx);
|
||||
assert_item_labels(&pane, ["A", "B", "C*"], cx);
|
||||
|
||||
pane.update(cx, |pane, cx| {
|
||||
let ix = pane.index_for_item_id(item_a.item_id()).unwrap();
|
||||
pane.pin_tab_at(ix, cx);
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: None,
|
||||
close_pinned: true,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_item_labels(&pane, [], cx);
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
|
||||
@@ -357,7 +357,7 @@ impl PaneAxis {
|
||||
|
||||
pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
|
||||
let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
|
||||
// debug_assert!(members.len() == flexes.len());
|
||||
debug_assert!(members.len() == flexes.len());
|
||||
|
||||
let flexes = Arc::new(Mutex::new(flexes));
|
||||
let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
|
||||
|
||||
@@ -4,8 +4,8 @@ use strum::IntoEnumIterator;
|
||||
use theme::all_theme_colors;
|
||||
use ui::{
|
||||
prelude::*, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar,
|
||||
AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, Checkbox, ElevationIndex,
|
||||
Facepile, Indicator, TintColor, Tooltip,
|
||||
AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, ElevationIndex, Facepile,
|
||||
TintColor, Tooltip,
|
||||
};
|
||||
|
||||
use crate::{Item, Workspace};
|
||||
@@ -26,7 +26,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
enum ThemePreviewPage {
|
||||
Overview,
|
||||
Typography,
|
||||
Components,
|
||||
}
|
||||
|
||||
impl ThemePreviewPage {
|
||||
@@ -34,7 +33,6 @@ impl ThemePreviewPage {
|
||||
match self {
|
||||
Self::Overview => "Overview",
|
||||
Self::Typography => "Typography",
|
||||
Self::Components => "Components",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +58,6 @@ impl ThemePreview {
|
||||
match page {
|
||||
ThemePreviewPage::Overview => self.render_overview_page(cx).into_any_element(),
|
||||
ThemePreviewPage::Typography => self.render_typography_page(cx).into_any_element(),
|
||||
ThemePreviewPage::Components => self.render_components_page(cx).into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,6 +456,8 @@ impl ThemePreview {
|
||||
.text_color(cx.theme().colors().text)
|
||||
.gap_2()
|
||||
.child(Headline::new(layer.clone().to_string()).size(HeadlineSize::Medium))
|
||||
.child(self.render_avatars(cx))
|
||||
.child(self.render_buttons(layer, cx))
|
||||
.child(self.render_text(layer, cx))
|
||||
.child(self.render_colors(layer, cx))
|
||||
}
|
||||
@@ -500,56 +499,39 @@ impl ThemePreview {
|
||||
.child(Label::new("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
|
||||
)
|
||||
}
|
||||
|
||||
fn render_components_page(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let layer = ElevationIndex::Surface;
|
||||
|
||||
v_flex()
|
||||
.id("theme-preview-components")
|
||||
.overflow_scroll()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Checkbox::render_component_previews(cx))
|
||||
.child(Facepile::render_component_previews(cx))
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Indicator::render_component_previews(cx))
|
||||
.child(Icon::render_component_previews(cx))
|
||||
.child(self.render_avatars(cx))
|
||||
.child(self.render_buttons(layer, cx))
|
||||
}
|
||||
|
||||
fn render_page_nav(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("theme-preview-nav")
|
||||
.items_center()
|
||||
.gap_4()
|
||||
.py_2()
|
||||
.bg(Self::preview_bg(cx))
|
||||
.children(ThemePreviewPage::iter().map(|p| {
|
||||
Button::new(ElementId::Name(p.name().into()), p.name())
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.current_page = p;
|
||||
cx.notify();
|
||||
}))
|
||||
.selected(p == self.current_page)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ThemePreview {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
|
||||
v_flex()
|
||||
h_flex()
|
||||
.id("theme-preview")
|
||||
.key_context("ThemePreview")
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.max_h_full()
|
||||
.p_4()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(Self::preview_bg(cx))
|
||||
.child(self.render_page_nav(cx))
|
||||
.gap_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.items_start()
|
||||
.gap_1()
|
||||
.w(px(240.))
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_px()
|
||||
.children(ThemePreviewPage::iter().map(|p| {
|
||||
Button::new(ElementId::Name(p.name().into()), p.name())
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.current_page = p;
|
||||
cx.notify();
|
||||
}))
|
||||
.selected(p == self.current_page)
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(self.view(self.current_page, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2219,13 +2219,7 @@ impl Workspace {
|
||||
|
||||
if retain_active_pane {
|
||||
if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
|
||||
pane.close_inactive_items(
|
||||
&CloseInactiveItems {
|
||||
save_intent: None,
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
|
||||
}) {
|
||||
tasks.push(current_pane_close);
|
||||
};
|
||||
@@ -2240,7 +2234,6 @@ impl Workspace {
|
||||
pane.close_all_items(
|
||||
&CloseAllItems {
|
||||
save_intent: Some(save_intent),
|
||||
close_pinned: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
@@ -3443,17 +3436,6 @@ impl Workspace {
|
||||
let project = self.project().read(cx);
|
||||
let mut title = String::new();
|
||||
|
||||
for (i, name) in project.worktree_root_names(cx).enumerate() {
|
||||
if i > 0 {
|
||||
title.push_str(", ");
|
||||
}
|
||||
title.push_str(name);
|
||||
}
|
||||
|
||||
if title.is_empty() {
|
||||
title = "empty project".to_string();
|
||||
}
|
||||
|
||||
if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
|
||||
let filename = path
|
||||
.path
|
||||
@@ -3469,11 +3451,22 @@ impl Workspace {
|
||||
});
|
||||
|
||||
if let Some(filename) = filename {
|
||||
title.push_str(" — ");
|
||||
title.push_str(filename.as_ref());
|
||||
title.push_str(" — ");
|
||||
}
|
||||
}
|
||||
|
||||
for (i, name) in project.worktree_root_names(cx).enumerate() {
|
||||
if i > 0 {
|
||||
title.push_str(", ");
|
||||
}
|
||||
title.push_str(name);
|
||||
}
|
||||
|
||||
if title.is_empty() {
|
||||
title = "empty project".to_string();
|
||||
}
|
||||
|
||||
if project.is_via_collab() {
|
||||
title.push_str(" ↙");
|
||||
} else if project.is_shared() {
|
||||
@@ -6218,13 +6211,13 @@ mod tests {
|
||||
.map(|e| e.id)
|
||||
);
|
||||
});
|
||||
assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
|
||||
|
||||
// Add a second item to a non-empty pane
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.add_item_to_active_pane(Box::new(item2), None, true, cx)
|
||||
});
|
||||
assert_eq!(cx.window_title().as_deref(), Some("root1 — two.txt"));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("two.txt — root1"));
|
||||
project.update(cx, |project, cx| {
|
||||
assert_eq!(
|
||||
project.active_entry(),
|
||||
@@ -6240,7 +6233,7 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.window_title().as_deref(), Some("root1 — one.txt"));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1"));
|
||||
project.update(cx, |project, cx| {
|
||||
assert_eq!(
|
||||
project.active_entry(),
|
||||
@@ -6257,11 +6250,11 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(cx.window_title().as_deref(), Some("root1, root2 — one.txt"));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("one.txt — root1, root2"));
|
||||
|
||||
// Remove a project folder
|
||||
project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("root2 — one.txt"));
|
||||
assert_eq!(cx.window_title().as_deref(), Some("one.txt — root2"));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -285,9 +285,6 @@ pub struct LocalSnapshot {
|
||||
/// All of the git repositories in the worktree, indexed by the project entry
|
||||
/// id of their parent directory.
|
||||
git_repositories: TreeMap<ProjectEntryId, LocalRepositoryEntry>,
|
||||
/// The file handle of the root dir
|
||||
/// (so we can find it after it's been moved)
|
||||
root_file_handle: Option<Arc<dyn fs::FileHandle>>,
|
||||
}
|
||||
|
||||
struct BackgroundScannerState {
|
||||
@@ -344,9 +341,6 @@ enum ScanState {
|
||||
barrier: SmallVec<[barrier::Sender; 1]>,
|
||||
scanning: bool,
|
||||
},
|
||||
RootUpdated {
|
||||
new_path: Option<Arc<Path>>,
|
||||
},
|
||||
}
|
||||
|
||||
struct UpdateObservationState {
|
||||
@@ -388,8 +382,6 @@ impl Worktree {
|
||||
true
|
||||
});
|
||||
|
||||
let root_file_handle = fs.open_handle(&abs_path).await.log_err();
|
||||
|
||||
cx.new_model(move |cx: &mut ModelContext<Worktree>| {
|
||||
let mut snapshot = LocalSnapshot {
|
||||
ignores_by_parent_abs_path: Default::default(),
|
||||
@@ -401,7 +393,6 @@ impl Worktree {
|
||||
.map_or(String::new(), |f| f.to_string_lossy().to_string()),
|
||||
abs_path,
|
||||
),
|
||||
root_file_handle,
|
||||
};
|
||||
|
||||
if let Some(metadata) = metadata {
|
||||
@@ -1085,17 +1076,6 @@ impl LocalWorktree {
|
||||
this.set_snapshot(snapshot, changes, cx);
|
||||
drop(barrier);
|
||||
}
|
||||
ScanState::RootUpdated { new_path } => {
|
||||
if let Some(new_path) = new_path {
|
||||
this.snapshot.git_repositories = Default::default();
|
||||
this.snapshot.ignores_by_parent_abs_path = Default::default();
|
||||
let root_name = new_path
|
||||
.file_name()
|
||||
.map_or(String::new(), |f| f.to_string_lossy().to_string());
|
||||
this.snapshot.update_abs_path(new_path, root_name);
|
||||
}
|
||||
this.restart_background_scanners(cx);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
})
|
||||
@@ -2093,24 +2073,12 @@ impl Snapshot {
|
||||
.and_then(|entry| entry.git_status)
|
||||
}
|
||||
|
||||
fn update_abs_path(&mut self, abs_path: Arc<Path>, root_name: String) {
|
||||
self.abs_path = abs_path;
|
||||
if root_name != self.root_name {
|
||||
self.root_char_bag = root_name.chars().map(|c| c.to_ascii_lowercase()).collect();
|
||||
self.root_name = root_name;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_remote_update(&mut self, mut update: proto::UpdateWorktree) -> Result<()> {
|
||||
log::trace!(
|
||||
"applying remote worktree update. {} entries updated, {} removed",
|
||||
update.updated_entries.len(),
|
||||
update.removed_entries.len()
|
||||
);
|
||||
self.update_abs_path(
|
||||
Arc::from(PathBuf::from(update.abs_path).as_path()),
|
||||
update.root_name,
|
||||
);
|
||||
|
||||
let mut entries_by_path_edits = Vec::new();
|
||||
let mut entries_by_id_edits = Vec::new();
|
||||
@@ -3764,29 +3732,7 @@ impl BackgroundScanner {
|
||||
let root_canonical_path = match self.fs.canonicalize(&root_path).await {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
let new_path = self
|
||||
.state
|
||||
.lock()
|
||||
.snapshot
|
||||
.root_file_handle
|
||||
.clone()
|
||||
.and_then(|handle| handle.current_path(&self.fs).log_err())
|
||||
.filter(|new_path| **new_path != *root_path);
|
||||
|
||||
if let Some(new_path) = new_path.as_ref() {
|
||||
log::info!(
|
||||
"root renamed from {} to {}",
|
||||
root_path.display(),
|
||||
new_path.display()
|
||||
)
|
||||
} else {
|
||||
log::warn!("root path could not be canonicalized: {}", err);
|
||||
}
|
||||
self.status_updates_tx
|
||||
.unbounded_send(ScanState::RootUpdated {
|
||||
new_path: new_path.map(|p| p.into()),
|
||||
})
|
||||
.ok();
|
||||
log::error!("failed to canonicalize root path: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ name = "zed"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
assistant_slash_command.workspace = true
|
||||
activity_indicator.workspace = true
|
||||
anyhow.workspace = true
|
||||
assets.workspace = true
|
||||
@@ -54,7 +53,6 @@ go_to_line.workspace = true
|
||||
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
|
||||
http_client.workspace = true
|
||||
image_viewer.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
inline_completion_button.workspace = true
|
||||
install_cli.workspace = true
|
||||
journal.workspace = true
|
||||
|
||||
@@ -7,7 +7,6 @@ mod reliability;
|
||||
mod zed;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use chrono::Offset;
|
||||
use clap::{command, Parser};
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
@@ -24,7 +23,6 @@ use gpui::{
|
||||
VisualContext,
|
||||
};
|
||||
use http_client::{read_proxy_from_env, Uri};
|
||||
use indexed_docs::IndexedDocsRegistry;
|
||||
use language::LanguageRegistry;
|
||||
use log::LevelFilter;
|
||||
use reqwest_client::ReqwestClient;
|
||||
@@ -41,7 +39,6 @@ use settings::{
|
||||
};
|
||||
use simplelog::ConfigBuilder;
|
||||
use smol::process::Command;
|
||||
use snippet_provider::SnippetRegistry;
|
||||
use std::{
|
||||
env,
|
||||
fs::OpenOptions,
|
||||
@@ -405,19 +402,12 @@ fn main() {
|
||||
app_state.client.telemetry().clone(),
|
||||
cx,
|
||||
);
|
||||
let api = extensions_ui::ConcreteExtensionRegistrationHooks::new(
|
||||
ThemeRegistry::global(cx),
|
||||
SlashCommandRegistry::global(cx),
|
||||
IndexedDocsRegistry::global(cx),
|
||||
SnippetRegistry::global(cx),
|
||||
app_state.languages.clone(),
|
||||
cx,
|
||||
);
|
||||
extension_host::init(
|
||||
api,
|
||||
app_state.fs.clone(),
|
||||
app_state.client.clone(),
|
||||
app_state.node_runtime.clone(),
|
||||
app_state.languages.clone(),
|
||||
ThemeRegistry::global(cx),
|
||||
cx,
|
||||
);
|
||||
recent_projects::init(cx);
|
||||
|
||||
@@ -495,7 +495,10 @@ async fn upload_panic(
|
||||
) -> Result<bool> {
|
||||
*most_recent_panic = Some((panic.panicked_on, panic.payload.clone()));
|
||||
|
||||
let json_bytes = serde_json::to_vec(&PanicRequest { panic }).unwrap();
|
||||
let json_bytes = serde_json::to_vec(&PanicRequest {
|
||||
panic: panic.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let Some(checksum) = client::telemetry::calculate_json_checksum(&json_bytes) else {
|
||||
return Ok(false);
|
||||
|
||||
@@ -120,32 +120,3 @@ In order to fix this issue, you can manually set the `ZED_RC_TOOLKIT_PATH` envir
|
||||
`C:\Program Files (x86)\Windows Kits\10\bin\<SDK_version>\x64`.
|
||||
|
||||
See this [issue](https://github.com/zed-industries/zed/issues/18393) for more information.
|
||||
|
||||
### Build fails: Path too long
|
||||
|
||||
You may receive an error like the following when building
|
||||
|
||||
```
|
||||
error: failed to get `pet` as a dependency of package `languages v0.1.0 (D:\a\zed-windows-builds\zed-windows-builds\crates\languages)`
|
||||
|
||||
Caused by:
|
||||
failed to load source for dependency `pet`
|
||||
|
||||
Caused by:
|
||||
Unable to update https://github.com/microsoft/python-environment-tools.git?rev=ffcbf3f28c46633abd5448a52b1f396c322e0d6c#ffcbf3f2
|
||||
|
||||
Caused by:
|
||||
path too long: 'C:/Users/runneradmin/.cargo/git/checkouts/python-environment-tools-903993894b37a7d2/ffcbf3f/crates/pet-conda/tests/unix/conda_env_without_manager_but_found_in_history/some_other_location/conda_install/conda-meta/python-fastjsonschema-2.16.2-py310hca03da5_0.json'; class=Filesystem (30)
|
||||
```
|
||||
|
||||
In order to solve this, you can enable longpath support for git and Windows.
|
||||
|
||||
For git: `git config --system core.longpaths true`
|
||||
|
||||
And for Windows with this PS command:
|
||||
|
||||
```powershell
|
||||
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force
|
||||
```
|
||||
|
||||
For more information on this, please see [win32 docs](https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=powershell)
|
||||
|
||||
@@ -11,5 +11,5 @@ name = "zls"
|
||||
language = "Zig"
|
||||
|
||||
[grammars.zig]
|
||||
repository = "https://github.com/tree-sitter-grammars/tree-sitter-zig"
|
||||
commit = "eb7d58c2dc4fbeea4745019dee8df013034ae66b"
|
||||
repository = "https://github.com/maxxnino/tree-sitter-zig"
|
||||
commit = "0d08703e4c3f426ec61695d7617415fff97029bd"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name = "Zig"
|
||||
grammar = "zig"
|
||||
path_suffixes = ["zig", "zon"]
|
||||
line_comments = ["// ", "/// ", "//! "]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
|
||||
@@ -1,179 +1,126 @@
|
||||
; Variables
|
||||
[
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
|
||||
(identifier) @variable
|
||||
] @comment.doc
|
||||
|
||||
; Parameters
|
||||
[
|
||||
(line_comment)
|
||||
] @comment
|
||||
|
||||
(parameter
|
||||
name: (identifier) @variable.parameter)
|
||||
[
|
||||
variable: (IDENTIFIER)
|
||||
variable_type_function: (IDENTIFIER)
|
||||
] @variable
|
||||
|
||||
; Types
|
||||
;; func parameter
|
||||
parameter: (IDENTIFIER) @property
|
||||
|
||||
(parameter
|
||||
type: (identifier) @type)
|
||||
[
|
||||
field_member: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
] @property
|
||||
|
||||
((identifier) @type
|
||||
(#match? @type "^[A-Z_][a-zA-Z0-9_]*"))
|
||||
|
||||
(variable_declaration
|
||||
(identifier) @type
|
||||
"="
|
||||
;; assume TitleCase is a type
|
||||
(
|
||||
[
|
||||
(struct_declaration)
|
||||
(enum_declaration)
|
||||
(union_declaration)
|
||||
(opaque_declaration)
|
||||
])
|
||||
variable_type_function: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
parameter: (IDENTIFIER)
|
||||
] @type
|
||||
(#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)+$")
|
||||
)
|
||||
|
||||
;; assume camelCase is a function
|
||||
(
|
||||
[
|
||||
variable_type_function: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
parameter: (IDENTIFIER)
|
||||
] @function
|
||||
(#match? @function "^[a-z]+([A-Z][a-z0-9]*)+$")
|
||||
)
|
||||
|
||||
;; assume all CAPS_1 is a constant
|
||||
(
|
||||
[
|
||||
variable_type_function: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
] @constant
|
||||
(#match? @constant "^[A-Z][A-Z_0-9]+$")
|
||||
)
|
||||
|
||||
[
|
||||
(builtin_type)
|
||||
"anyframe"
|
||||
] @type.builtin
|
||||
function_call: (IDENTIFIER)
|
||||
function: (IDENTIFIER)
|
||||
] @function
|
||||
|
||||
; Constants
|
||||
exception: "!" @keyword
|
||||
|
||||
((identifier) @constant
|
||||
(#match? @constant "^[A-Z][A-Z_0-9]+$"))
|
||||
(
|
||||
(IDENTIFIER) @variable.special
|
||||
(#eq? @variable.special "_")
|
||||
)
|
||||
|
||||
(PtrTypeStart "c" @variable.special)
|
||||
|
||||
(
|
||||
(ContainerDeclType
|
||||
[
|
||||
(ErrorUnionExpr)
|
||||
"enum"
|
||||
]
|
||||
)
|
||||
(ContainerField (IDENTIFIER) @constant)
|
||||
)
|
||||
|
||||
field_constant: (IDENTIFIER) @constant
|
||||
|
||||
(BUILTINIDENTIFIER) @keyword
|
||||
|
||||
((BUILTINIDENTIFIER) @function
|
||||
(#any-of? @function "@import" "@cImport"))
|
||||
|
||||
(INTEGER) @number
|
||||
|
||||
(FLOAT) @number
|
||||
|
||||
[
|
||||
"null"
|
||||
"unreachable"
|
||||
"undefined"
|
||||
] @constant.builtin
|
||||
|
||||
(field_expression
|
||||
.
|
||||
member: (identifier) @constant)
|
||||
|
||||
(enum_declaration
|
||||
(container_field
|
||||
type: (identifier) @constant))
|
||||
|
||||
; Labels
|
||||
|
||||
(block_label (identifier) @label)
|
||||
|
||||
(break_label (identifier) @label)
|
||||
|
||||
; Fields
|
||||
|
||||
(field_initializer
|
||||
.
|
||||
(identifier) @variable.member)
|
||||
|
||||
(field_expression
|
||||
(_)
|
||||
member: (identifier) @property)
|
||||
|
||||
(field_expression
|
||||
(_)
|
||||
member: (identifier) @type (#match? @type "^[A-Z_][a-zA-Z0-9_]*"))
|
||||
|
||||
(container_field
|
||||
name: (identifier) @property)
|
||||
|
||||
(initializer_list
|
||||
(assignment_expression
|
||||
left: (field_expression
|
||||
.
|
||||
member: (identifier) @property)))
|
||||
|
||||
; Functions
|
||||
|
||||
(builtin_identifier) @function.builtin
|
||||
|
||||
(call_expression
|
||||
function: (identifier) @function.call)
|
||||
|
||||
(call_expression
|
||||
function: (field_expression
|
||||
member: (identifier) @function.call))
|
||||
|
||||
(function_declaration
|
||||
name: (identifier) @function)
|
||||
|
||||
; Modules
|
||||
|
||||
(variable_declaration
|
||||
(identifier) @module
|
||||
(builtin_function
|
||||
(builtin_identifier) @keyword.import
|
||||
(#any-of? @keyword.import "@import" "@cImport")))
|
||||
|
||||
; Builtins
|
||||
"true"
|
||||
"false"
|
||||
] @boolean
|
||||
|
||||
[
|
||||
"c"
|
||||
"..."
|
||||
] @variable.builtin
|
||||
(LINESTRING)
|
||||
(STRINGLITERALSINGLE)
|
||||
] @string
|
||||
|
||||
((identifier) @variable.builtin
|
||||
(#eq? @variable.builtin "_"))
|
||||
(CHAR_LITERAL) @string.special.symbol
|
||||
(EscapeSequence) @string.escape
|
||||
(FormatSequence) @string.special
|
||||
|
||||
(calling_convention
|
||||
(identifier) @variable.builtin)
|
||||
|
||||
; Keywords
|
||||
(BreakLabel (IDENTIFIER) @tag)
|
||||
(BlockLabel (IDENTIFIER) @tag)
|
||||
|
||||
[
|
||||
"fn"
|
||||
"asm"
|
||||
"defer"
|
||||
"errdefer"
|
||||
"test"
|
||||
"error"
|
||||
"const"
|
||||
"var"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"struct"
|
||||
"union"
|
||||
"enum"
|
||||
"opaque"
|
||||
] @keyword.type
|
||||
|
||||
[
|
||||
"async"
|
||||
"await"
|
||||
"suspend"
|
||||
"nosuspend"
|
||||
"resume"
|
||||
] @keyword.coroutine
|
||||
|
||||
"fn" @keyword.function
|
||||
|
||||
[
|
||||
"and"
|
||||
"or"
|
||||
"orelse"
|
||||
] @keyword.operator
|
||||
|
||||
"return" @keyword.return
|
||||
|
||||
[
|
||||
"if"
|
||||
"else"
|
||||
"switch"
|
||||
] @keyword.conditional
|
||||
|
||||
[
|
||||
"error"
|
||||
"try"
|
||||
"catch"
|
||||
"for"
|
||||
"while"
|
||||
"break"
|
||||
"continue"
|
||||
] @keyword.repeat
|
||||
|
||||
[
|
||||
"usingnamespace"
|
||||
"export"
|
||||
] @keyword.import
|
||||
|
||||
[
|
||||
"try"
|
||||
"catch"
|
||||
] @keyword.exception
|
||||
|
||||
[
|
||||
"const"
|
||||
"var"
|
||||
"volatile"
|
||||
"allowzero"
|
||||
"noalias"
|
||||
@@ -181,91 +128,71 @@
|
||||
"align"
|
||||
"callconv"
|
||||
"linksection"
|
||||
"pub"
|
||||
"comptime"
|
||||
"export"
|
||||
"extern"
|
||||
"inline"
|
||||
"noinline"
|
||||
"extern"
|
||||
"comptime"
|
||||
"packed"
|
||||
"pub"
|
||||
"threadlocal"
|
||||
] @keyword.modifier
|
||||
|
||||
; Operator
|
||||
"async"
|
||||
"await"
|
||||
"suspend"
|
||||
"nosuspend"
|
||||
"resume"
|
||||
"and"
|
||||
"or"
|
||||
"orelse"
|
||||
"return"
|
||||
"if"
|
||||
"else"
|
||||
"switch"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"="
|
||||
"*="
|
||||
"*%="
|
||||
"*|="
|
||||
"/="
|
||||
"%="
|
||||
"+="
|
||||
"+%="
|
||||
"+|="
|
||||
"-="
|
||||
"-%="
|
||||
"-|="
|
||||
"<<="
|
||||
"<<|="
|
||||
">>="
|
||||
"&="
|
||||
"^="
|
||||
"|="
|
||||
"!"
|
||||
"~"
|
||||
"-"
|
||||
"-%"
|
||||
"&"
|
||||
"=="
|
||||
"!="
|
||||
">"
|
||||
">="
|
||||
"<="
|
||||
"<"
|
||||
"&"
|
||||
"^"
|
||||
"|"
|
||||
"<<"
|
||||
">>"
|
||||
"<<|"
|
||||
"+"
|
||||
"++"
|
||||
"+%"
|
||||
"-%"
|
||||
"+|"
|
||||
"-|"
|
||||
"usingnamespace"
|
||||
] @constant
|
||||
|
||||
[
|
||||
"anytype"
|
||||
"anyframe"
|
||||
(BuildinTypeExpr)
|
||||
] @type
|
||||
|
||||
[
|
||||
"null"
|
||||
"unreachable"
|
||||
"undefined"
|
||||
] @constant
|
||||
|
||||
[
|
||||
(CompareOp)
|
||||
(BitwiseOp)
|
||||
(BitShiftOp)
|
||||
(AdditionOp)
|
||||
(AssignOp)
|
||||
(MultiplyOp)
|
||||
(PrefixOp)
|
||||
"*"
|
||||
"/"
|
||||
"%"
|
||||
"**"
|
||||
"*%"
|
||||
"*|"
|
||||
"||"
|
||||
".*"
|
||||
"->"
|
||||
".?"
|
||||
".*"
|
||||
"?"
|
||||
".."
|
||||
] @operator
|
||||
|
||||
; Literals
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
(character) @character
|
||||
|
||||
([
|
||||
(string)
|
||||
(multiline_string)
|
||||
] @string
|
||||
(#set! "priority" 95))
|
||||
|
||||
(integer) @number
|
||||
|
||||
(float) @number.float
|
||||
|
||||
(boolean) @boolean
|
||||
|
||||
(escape_sequence) @string.escape
|
||||
|
||||
; Punctuation
|
||||
[
|
||||
".."
|
||||
"..."
|
||||
] @punctuation.special
|
||||
|
||||
[
|
||||
"["
|
||||
@@ -274,22 +201,10 @@
|
||||
")"
|
||||
"{"
|
||||
"}"
|
||||
(Payload "|")
|
||||
(PtrPayload "|")
|
||||
(PtrIndexPayload "|")
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
":"
|
||||
"=>"
|
||||
"->"
|
||||
] @punctuation.delimiter
|
||||
|
||||
(payload "|" @punctuation.bracket)
|
||||
|
||||
; Comments
|
||||
|
||||
(comment) @comment
|
||||
|
||||
((comment) @comment.documentation
|
||||
(#match? @comment.documentation "^//(/|!)"))
|
||||
; Error
|
||||
(ERROR) @error
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
[
|
||||
(block)
|
||||
(switch_expression)
|
||||
(initializer_list)
|
||||
(AsmExpr)
|
||||
(AssignExpr)
|
||||
(Block)
|
||||
(BlockExpr)
|
||||
(ContainerDecl)
|
||||
(ErrorUnionExpr)
|
||||
(InitList)
|
||||
(SwitchExpr)
|
||||
(TestDecl)
|
||||
] @indent.begin
|
||||
|
||||
(block
|
||||
"}" @indent.end)
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
||||
|
||||
[
|
||||
(comment)
|
||||
(multiline_string)
|
||||
(line_comment)
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
(LINESTRING)
|
||||
] @indent.ignore
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
||||
; TODO: add when asm is added
|
||||
; (asm_output_item (string) @injection.content
|
||||
; (#set! injection.language "asm"))
|
||||
; (asm_input_item (string) @injection.content
|
||||
; (#set! injection.language "asm"))
|
||||
; (asm_clobbers (string) @injection.content
|
||||
; (#set! injection.language "asm"))
|
||||
[
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
(line_comment)
|
||||
] @comment
|
||||
|
||||
@@ -1,50 +1,27 @@
|
||||
(test_declaration
|
||||
"test" @context
|
||||
[
|
||||
(string)
|
||||
(identifier)
|
||||
] @name) @item
|
||||
(Decl (
|
||||
FnProto(
|
||||
"fn" @context
|
||||
function: (_) @name
|
||||
)
|
||||
)
|
||||
) @item
|
||||
|
||||
(function_declaration
|
||||
"pub"? @context
|
||||
[
|
||||
"extern"
|
||||
"export"
|
||||
"inline"
|
||||
"noinline"
|
||||
]? @context
|
||||
"fn" @context
|
||||
name: (_) @name) @item
|
||||
(
|
||||
Decl (
|
||||
VarDecl (
|
||||
"const"
|
||||
variable_type_function: (_) @name
|
||||
(ErrorUnionExpr) @context
|
||||
)
|
||||
)
|
||||
) @item
|
||||
|
||||
(source_file
|
||||
(variable_declaration
|
||||
"pub"? @context
|
||||
(identifier) @name
|
||||
"=" (_) @context) @item)
|
||||
|
||||
(struct_declaration
|
||||
(variable_declaration
|
||||
"pub"? @context
|
||||
(identifier) @name
|
||||
"=" (_) @context) @item)
|
||||
|
||||
(union_declaration
|
||||
(variable_declaration
|
||||
"pub"? @context
|
||||
(identifier) @name
|
||||
"=" (_) @context) @item)
|
||||
|
||||
(enum_declaration
|
||||
(variable_declaration
|
||||
"pub"? @context
|
||||
(identifier) @name
|
||||
"=" (_) @context) @item)
|
||||
|
||||
(opaque_declaration
|
||||
(variable_declaration
|
||||
"pub"? @context
|
||||
(identifier) @name
|
||||
"=" (_) @context) @item)
|
||||
|
||||
(container_field
|
||||
. (_) @name) @item
|
||||
(
|
||||
TestDecl (
|
||||
"test" @context
|
||||
[
|
||||
(STRINGLITERALSINGLE)
|
||||
(IDENTIFIER)
|
||||
]? @name
|
||||
)
|
||||
) @item
|
||||
|
||||
@@ -11,7 +11,7 @@ else
|
||||
fi
|
||||
|
||||
# Install sqlx-cli if needed
|
||||
if ! [[ "$(command -v sqlx)" && "$(sqlx --version)" == "sqlx-cli 0.7.2" ]]; then
|
||||
if [[ "$(sqlx --version)" != "sqlx-cli 0.7.2" ]]; then
|
||||
echo "sqlx-cli not found or not the required version, installing version 0.7.2..."
|
||||
cargo install sqlx-cli --version 0.7.2
|
||||
fi
|
||||
|
||||
@@ -225,8 +225,7 @@ function sign_app_binaries() {
|
||||
local architecture=$2
|
||||
local architecture_dir=$3
|
||||
echo "Copying WebRTC.framework into the frameworks folder"
|
||||
rm -rf "${app_path}/Contents/Frameworks"
|
||||
mkdir -p "${app_path}/Contents/Frameworks"
|
||||
mkdir "${app_path}/Contents/Frameworks"
|
||||
if [ "$local_arch" = false ]; then
|
||||
cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/"
|
||||
else
|
||||
|
||||
@@ -33,11 +33,11 @@ main() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if which curl >/dev/null 2>&1; then
|
||||
curl () {
|
||||
command curl -fL "$@"
|
||||
}
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
elif which wget >/dev/null 2>&1; then
|
||||
curl () {
|
||||
wget -O- "$@"
|
||||
}
|
||||
@@ -48,7 +48,7 @@ main() {
|
||||
|
||||
"$platform" "$@"
|
||||
|
||||
if [ "$(command -v zed)" = "$HOME/.local/bin/zed" ]; then
|
||||
if [ "$(which "zed")" = "$HOME/.local/bin/zed" ]; then
|
||||
echo "Zed has been installed. Run with 'zed'"
|
||||
else
|
||||
echo "To run Zed from your terminal, you must add ~/.local/bin to your PATH"
|
||||
|
||||
@@ -9,7 +9,6 @@ accepted = [
|
||||
"BSD-2-Clause",
|
||||
"ISC",
|
||||
"CC0-1.0",
|
||||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"OpenSSL",
|
||||
"Zlib",
|
||||
|
||||
Reference in New Issue
Block a user