From c3e2bf4fc8a5769c2c60aa1e6451cd0ba67ec9b9 Mon Sep 17 00:00:00 2001 From: Vova Kulikov Date: Tue, 31 Oct 2023 18:39:30 -0300 Subject: [PATCH] Vscode extension: Delete vscode extension package (#58023) * Remove vscode from CI * Delete vscode package * Remove vscode dependencies * Update CHANGELOG.md --- .eslintrc.js | 2 +- CHANGELOG.md | 2 + client/vscode/.bazelignore | 2 - client/vscode/.eslintignore | 7 - client/vscode/.gitignore | 5 - client/vscode/.stylelintrc.json | 11 - client/vscode/.vscodeignore | 25 - client/vscode/BUILD.bazel | 245 ---- client/vscode/CHANGELOG.md | 201 ---- client/vscode/CONTRIBUTING.md | 219 ---- client/vscode/LICENSE | 201 ---- client/vscode/README.md | 127 -- client/vscode/code-intel-extensions.json | 1 - client/vscode/globals.d.ts | 13 - client/vscode/images/logo.png | Bin 2614 -> 0 bytes client/vscode/images/logo.svg | 1 - client/vscode/images/logomark_dark.svg | 26 - client/vscode/images/logomark_light.svg | 26 - .../vscode/images/sourcegraph-logo-dark.svg | 1 - .../vscode/images/sourcegraph-logo-light.svg | 1 - client/vscode/package.json | 291 ----- client/vscode/scripts/buffer-shim.js | 5 - .../vscode/scripts/build-inline-extensions.js | 7 - client/vscode/scripts/build.ts | 156 --- client/vscode/scripts/package.ts | 17 - client/vscode/scripts/process-shim.js | 5 - client/vscode/scripts/publish.ts | 79 -- client/vscode/scripts/release.ts | 73 -- client/vscode/src/README.md | 44 - .../vscode/src/backend/authenticatedUser.ts | 76 -- client/vscode/src/backend/blobContent.ts | 39 - client/vscode/src/backend/eventLogger.ts | 50 - client/vscode/src/backend/fetch.ts | 72 -- client/vscode/src/backend/files.ts | 26 - client/vscode/src/backend/instanceVersion.ts | 110 -- .../backend/node-fetch-fake-for-browser.ts | 7 - .../backend/proxy-agent-fake-for-browser.ts | 6 - .../vscode/src/backend/repositoryMetadata.ts | 59 - client/vscode/src/backend/requestGraphQl.ts | 74 -- client/vscode/src/backend/searchContexts.ts | 48 - .../vscode/src/backend/sourcegraphSettings.ts | 57 - client/vscode/src/backend/streamSearch.ts | 91 -- .../SourcegraphDefinitionProvider.ts | 72 -- .../code-intel/SourcegraphHoverProvider.ts | 76 -- .../SourcegraphReferenceProvider.ts | 78 -- client/vscode/src/code-intel/initialize.ts | 77 -- client/vscode/src/code-intel/languages.ts | 14 - client/vscode/src/code-intel/location.ts | 10 - .../vscode/src/commands/browserActionsNode.ts | 68 -- .../vscode/src/commands/browserActionsWeb.ts | 47 - client/vscode/src/commands/git-helpers.ts | 268 ----- client/vscode/src/commands/initialize.ts | 86 -- client/vscode/src/common/links.ts | 59 - client/vscode/src/contract.ts | 79 -- client/vscode/src/extension.ts | 126 -- client/vscode/src/file-system/FileTree.ts | 114 -- .../src/file-system/FilesTreeDataProvider.ts | 248 ---- .../SourcegraphFileSystemProvider.ts | 316 ----- .../vscode/src/file-system/SourcegraphUri.ts | 225 ---- client/vscode/src/file-system/commands.ts | 105 -- client/vscode/src/file-system/initialize.ts | 48 - client/vscode/src/log.ts | 34 - .../src/settings/LocalStorageService.ts | 25 - .../vscode/src/settings/accessTokenSetting.ts | 65 -- client/vscode/src/settings/displayWarnings.ts | 5 - client/vscode/src/settings/endpointSetting.ts | 47 - client/vscode/src/settings/invalidation.ts | 37 - .../vscode/src/settings/readConfiguration.ts | 5 - client/vscode/src/settings/recommendations.ts | 50 - client/vscode/src/settings/uninstall.ts | 69 -- client/vscode/src/state.ts | 295 ----- client/vscode/src/vsCodeApi.ts | 10 - .../src/webview/comlink/extensionEndpoint.ts | 147 --- client/vscode/src/webview/comlink/index.ts | 56 - .../src/webview/comlink/webviewEndpoint.ts | 199 ---- client/vscode/src/webview/commands.ts | 256 ---- client/vscode/src/webview/index.scss | 131 --- client/vscode/src/webview/initialize.ts | 197 ---- .../src/webview/platform/AuthProvider.ts | 154 --- .../src/webview/platform/EventLogger.ts | 170 --- client/vscode/src/webview/platform/context.ts | 119 -- .../src/webview/platform/polyfills/index.ts | 2 - .../webview/platform/polyfills/polyfill.ts | 3 - .../src/webview/platform/telemetryService.ts | 51 - .../search-panel/MatchHandlersContext.ts | 126 -- .../webview/search-panel/RepoView.module.scss | 51 - .../src/webview/search-panel/RepoView.tsx | 146 --- .../webview/search-panel/SearchHomeView.tsx | 200 ---- .../search-panel/SearchResultsView.tsx | 420 ------- .../search-panel/alias/CommitSearchResult.tsx | 112 -- .../search-panel/alias/FileMatchChildren.tsx | 315 ----- .../search-panel/alias/ModalVideo.module.scss | 73 -- .../webview/search-panel/alias/ModalVideo.tsx | 115 -- .../src/webview/search-panel/alias/README.md | 19 - .../search-panel/alias/RepoFileLink.tsx | 95 -- .../search-panel/alias/RepoSearchResult.tsx | 130 --- .../search-panel/alias/SymbolSearchResult.tsx | 183 --- .../search-panel/alias/fetchSearchContext.ts | 181 --- client/vscode/src/webview/search-panel/api.ts | 24 - .../search-panel/components/BrandHeader.tsx | 22 - .../components/ButtonDropdownCta.module.scss | 28 - .../components/ButtonDropdownCta.tsx | 99 -- .../components/HomeFooter.module.scss | 148 --- .../search-panel/components/HomeFooter.tsx | 114 -- .../components/SearchExamples.tsx | 28 - .../SearchResultsInfoBar.module.scss | 51 - .../components/SearchResultsInfoBar.tsx | 173 --- .../webview/search-panel/components/icons.tsx | 99 -- .../webview/search-panel/index.module.scss | 90 -- .../vscode/src/webview/search-panel/index.tsx | 129 --- .../sidebars/auth/AuthSidebarView.module.scss | 57 - .../webview/sidebars/auth/AuthSidebarView.tsx | 325 ------ .../sidebars/help/HelpSidebarView.module.scss | 25 - .../webview/sidebars/help/HelpSidebarView.tsx | 136 --- .../src/webview/sidebars/help/index.tsx | 59 - .../sidebars/history/HistorySidebarView.tsx | 28 - .../history/components/RecentFilesSection.tsx | 134 --- .../components/RecentRepositoriesSection.tsx | 123 -- .../components/RecentSearchesSection.tsx | 128 -- .../search/ContextInvalidatedSidebarView.tsx | 24 - .../search/SearchSidebarView.module.scss | 170 --- .../sidebars/search/SearchSidebarView.tsx | 223 ---- .../vscode/src/webview/sidebars/search/api.ts | 20 - .../sidebars/search/extension-host/index.ts | 57 - .../search/extension-host/main.worker.ts | 25 - .../sidebars/search/extension-host/worker.ts | 18 - .../src/webview/sidebars/search/index.tsx | 142 --- .../vscode/src/webview/theming/highlight.scss | 1025 ----------------- .../src/webview/theming/sourcegraphTheme.ts | 37 - client/vscode/tests/BUILD.bazel | 6 - client/vscode/tests/context.ts | 81 -- client/vscode/tests/getWebview.ts | 96 -- client/vscode/tests/graphql.ts | 52 - client/vscode/tests/installExtension.ts | 106 -- client/vscode/tests/launch.ts | 106 -- client/vscode/tests/tsconfig.json | 12 - client/vscode/tests/vsce.test.ts | 150 --- client/vscode/tsconfig.json | 49 - dev/ci/internal/ci/operations.go | 25 - dev/ci/internal/ci/pipeline.go | 10 - dev/ci/runtype/runtype.go | 17 - dev/ci/runtype/runtype_test.go | 15 - package.json | 6 - pnpm-lock.yaml | 172 --- tsconfig.json | 1 - 145 files changed, 3 insertions(+), 13579 deletions(-) delete mode 100644 client/vscode/.bazelignore delete mode 100644 client/vscode/.eslintignore delete mode 100644 client/vscode/.gitignore delete mode 100644 client/vscode/.stylelintrc.json delete mode 100644 client/vscode/.vscodeignore delete mode 100644 client/vscode/BUILD.bazel delete mode 100644 client/vscode/CHANGELOG.md delete mode 100644 client/vscode/CONTRIBUTING.md delete mode 100644 client/vscode/LICENSE delete mode 100644 client/vscode/README.md delete mode 100644 client/vscode/code-intel-extensions.json delete mode 100644 client/vscode/globals.d.ts delete mode 100644 client/vscode/images/logo.png delete mode 100644 client/vscode/images/logo.svg delete mode 100644 client/vscode/images/logomark_dark.svg delete mode 100644 client/vscode/images/logomark_light.svg delete mode 100644 client/vscode/images/sourcegraph-logo-dark.svg delete mode 100644 client/vscode/images/sourcegraph-logo-light.svg delete mode 100644 client/vscode/package.json delete mode 100644 client/vscode/scripts/buffer-shim.js delete mode 100644 client/vscode/scripts/build-inline-extensions.js delete mode 100644 client/vscode/scripts/build.ts delete mode 100644 client/vscode/scripts/package.ts delete mode 100644 client/vscode/scripts/process-shim.js delete mode 100644 client/vscode/scripts/publish.ts delete mode 100644 client/vscode/scripts/release.ts delete mode 100644 client/vscode/src/README.md delete mode 100644 client/vscode/src/backend/authenticatedUser.ts delete mode 100644 client/vscode/src/backend/blobContent.ts delete mode 100644 client/vscode/src/backend/eventLogger.ts delete mode 100644 client/vscode/src/backend/fetch.ts delete mode 100644 client/vscode/src/backend/files.ts delete mode 100644 client/vscode/src/backend/instanceVersion.ts delete mode 100644 client/vscode/src/backend/node-fetch-fake-for-browser.ts delete mode 100644 client/vscode/src/backend/proxy-agent-fake-for-browser.ts delete mode 100644 client/vscode/src/backend/repositoryMetadata.ts delete mode 100644 client/vscode/src/backend/requestGraphQl.ts delete mode 100644 client/vscode/src/backend/searchContexts.ts delete mode 100644 client/vscode/src/backend/sourcegraphSettings.ts delete mode 100644 client/vscode/src/backend/streamSearch.ts delete mode 100644 client/vscode/src/code-intel/SourcegraphDefinitionProvider.ts delete mode 100644 client/vscode/src/code-intel/SourcegraphHoverProvider.ts delete mode 100644 client/vscode/src/code-intel/SourcegraphReferenceProvider.ts delete mode 100644 client/vscode/src/code-intel/initialize.ts delete mode 100644 client/vscode/src/code-intel/languages.ts delete mode 100644 client/vscode/src/code-intel/location.ts delete mode 100644 client/vscode/src/commands/browserActionsNode.ts delete mode 100644 client/vscode/src/commands/browserActionsWeb.ts delete mode 100644 client/vscode/src/commands/git-helpers.ts delete mode 100644 client/vscode/src/commands/initialize.ts delete mode 100644 client/vscode/src/common/links.ts delete mode 100644 client/vscode/src/contract.ts delete mode 100644 client/vscode/src/extension.ts delete mode 100644 client/vscode/src/file-system/FileTree.ts delete mode 100644 client/vscode/src/file-system/FilesTreeDataProvider.ts delete mode 100644 client/vscode/src/file-system/SourcegraphFileSystemProvider.ts delete mode 100644 client/vscode/src/file-system/SourcegraphUri.ts delete mode 100644 client/vscode/src/file-system/commands.ts delete mode 100644 client/vscode/src/file-system/initialize.ts delete mode 100644 client/vscode/src/log.ts delete mode 100644 client/vscode/src/settings/LocalStorageService.ts delete mode 100644 client/vscode/src/settings/accessTokenSetting.ts delete mode 100644 client/vscode/src/settings/displayWarnings.ts delete mode 100644 client/vscode/src/settings/endpointSetting.ts delete mode 100644 client/vscode/src/settings/invalidation.ts delete mode 100644 client/vscode/src/settings/readConfiguration.ts delete mode 100644 client/vscode/src/settings/recommendations.ts delete mode 100644 client/vscode/src/settings/uninstall.ts delete mode 100644 client/vscode/src/state.ts delete mode 100644 client/vscode/src/vsCodeApi.ts delete mode 100644 client/vscode/src/webview/comlink/extensionEndpoint.ts delete mode 100644 client/vscode/src/webview/comlink/index.ts delete mode 100644 client/vscode/src/webview/comlink/webviewEndpoint.ts delete mode 100644 client/vscode/src/webview/commands.ts delete mode 100644 client/vscode/src/webview/index.scss delete mode 100644 client/vscode/src/webview/initialize.ts delete mode 100644 client/vscode/src/webview/platform/AuthProvider.ts delete mode 100644 client/vscode/src/webview/platform/EventLogger.ts delete mode 100644 client/vscode/src/webview/platform/context.ts delete mode 100644 client/vscode/src/webview/platform/polyfills/index.ts delete mode 100644 client/vscode/src/webview/platform/polyfills/polyfill.ts delete mode 100644 client/vscode/src/webview/platform/telemetryService.ts delete mode 100644 client/vscode/src/webview/search-panel/MatchHandlersContext.ts delete mode 100644 client/vscode/src/webview/search-panel/RepoView.module.scss delete mode 100644 client/vscode/src/webview/search-panel/RepoView.tsx delete mode 100644 client/vscode/src/webview/search-panel/SearchHomeView.tsx delete mode 100644 client/vscode/src/webview/search-panel/SearchResultsView.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/CommitSearchResult.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/FileMatchChildren.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/ModalVideo.module.scss delete mode 100644 client/vscode/src/webview/search-panel/alias/ModalVideo.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/README.md delete mode 100644 client/vscode/src/webview/search-panel/alias/RepoFileLink.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/RepoSearchResult.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/SymbolSearchResult.tsx delete mode 100644 client/vscode/src/webview/search-panel/alias/fetchSearchContext.ts delete mode 100644 client/vscode/src/webview/search-panel/api.ts delete mode 100644 client/vscode/src/webview/search-panel/components/BrandHeader.tsx delete mode 100644 client/vscode/src/webview/search-panel/components/ButtonDropdownCta.module.scss delete mode 100644 client/vscode/src/webview/search-panel/components/ButtonDropdownCta.tsx delete mode 100644 client/vscode/src/webview/search-panel/components/HomeFooter.module.scss delete mode 100644 client/vscode/src/webview/search-panel/components/HomeFooter.tsx delete mode 100644 client/vscode/src/webview/search-panel/components/SearchExamples.tsx delete mode 100644 client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.module.scss delete mode 100644 client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.tsx delete mode 100644 client/vscode/src/webview/search-panel/components/icons.tsx delete mode 100644 client/vscode/src/webview/search-panel/index.module.scss delete mode 100644 client/vscode/src/webview/search-panel/index.tsx delete mode 100644 client/vscode/src/webview/sidebars/auth/AuthSidebarView.module.scss delete mode 100644 client/vscode/src/webview/sidebars/auth/AuthSidebarView.tsx delete mode 100644 client/vscode/src/webview/sidebars/help/HelpSidebarView.module.scss delete mode 100644 client/vscode/src/webview/sidebars/help/HelpSidebarView.tsx delete mode 100644 client/vscode/src/webview/sidebars/help/index.tsx delete mode 100644 client/vscode/src/webview/sidebars/history/HistorySidebarView.tsx delete mode 100644 client/vscode/src/webview/sidebars/history/components/RecentFilesSection.tsx delete mode 100644 client/vscode/src/webview/sidebars/history/components/RecentRepositoriesSection.tsx delete mode 100644 client/vscode/src/webview/sidebars/history/components/RecentSearchesSection.tsx delete mode 100644 client/vscode/src/webview/sidebars/search/ContextInvalidatedSidebarView.tsx delete mode 100644 client/vscode/src/webview/sidebars/search/SearchSidebarView.module.scss delete mode 100644 client/vscode/src/webview/sidebars/search/SearchSidebarView.tsx delete mode 100644 client/vscode/src/webview/sidebars/search/api.ts delete mode 100644 client/vscode/src/webview/sidebars/search/extension-host/index.ts delete mode 100644 client/vscode/src/webview/sidebars/search/extension-host/main.worker.ts delete mode 100644 client/vscode/src/webview/sidebars/search/extension-host/worker.ts delete mode 100644 client/vscode/src/webview/sidebars/search/index.tsx delete mode 100644 client/vscode/src/webview/theming/highlight.scss delete mode 100644 client/vscode/src/webview/theming/sourcegraphTheme.ts delete mode 100644 client/vscode/tests/BUILD.bazel delete mode 100644 client/vscode/tests/context.ts delete mode 100644 client/vscode/tests/getWebview.ts delete mode 100644 client/vscode/tests/graphql.ts delete mode 100644 client/vscode/tests/installExtension.ts delete mode 100644 client/vscode/tests/launch.ts delete mode 100644 client/vscode/tests/tsconfig.json delete mode 100644 client/vscode/tests/vsce.test.ts delete mode 100644 client/vscode/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js index 85ad69fa986..11713cfd816 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -326,7 +326,7 @@ See https://handbook.sourcegraph.com/community/faq#is-all-of-sourcegraph-open-so }, }, { - files: ['client/vscode/**', 'client/browser/**', 'client/jetbrains/**'], + files: ['client/browser/**', 'client/jetbrains/**'], rules: { 'no-console': 'off', }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 26abd07cd19..36992921ba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ All notable changes to Sourcegraph are documented in this file. - The following experimental settings in site-configuration are now deprecated and will not be read anymore: `maxReorderQueueSize`, `maxQueueMatchCount`, `maxReorderDurationMS`. [#57468](https://github.com/sourcegraph/sourcegraph/pull/57468) - The feature-flag `search-ranking`, which allowed to disable the improved ranking introduced in 5.1, is now deprecated and will not be read anymore. [#57468](https://github.com/sourcegraph/sourcegraph/pull/57468) - The GitHub Proxy service is no longer required and has been removed from deployment options. [#55290](https://github.com/sourcegraph/sourcegraph/issues/55290) +- The VSCode search extension "Sourcegraph for VS Code" has been sunset and removed from Sourcegraph + repository. [#58023](https://github.com/sourcegraph/sourcegraph/pull/58023) ## Unreleased 5.2.2 diff --git a/client/vscode/.bazelignore b/client/vscode/.bazelignore deleted file mode 100644 index 52c902c991f..00000000000 --- a/client/vscode/.bazelignore +++ /dev/null @@ -1,2 +0,0 @@ -# TODO(bazel): remove when no longer generated into src -src/graphql-operations.ts diff --git a/client/vscode/.eslintignore b/client/vscode/.eslintignore deleted file mode 100644 index d344db7454c..00000000000 --- a/client/vscode/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -**/graphql-operations.ts -graphql-operations.ts -**/node_modules/** -dist/ -package.json -src/vendor/*.js -code-intel-extensions/ diff --git a/client/vscode/.gitignore b/client/vscode/.gitignore deleted file mode 100644 index 3445a80e301..00000000000 --- a/client/vscode/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -dist/ -*.vsix -.vscode-test/ - -code-intel-extensions diff --git a/client/vscode/.stylelintrc.json b/client/vscode/.stylelintrc.json deleted file mode 100644 index cf84c40f41e..00000000000 --- a/client/vscode/.stylelintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": ["../../.stylelintrc.json"], - "overrides": [ - { - "files": ["./src/webview/index.scss", "./src/webview/theming/highlight.scss"], - "rules": { - "@sourcegraph/filenames-match-regex": null - } - } - ] -} diff --git a/client/vscode/.vscodeignore b/client/vscode/.vscodeignore deleted file mode 100644 index f2d9ad6283d..00000000000 --- a/client/vscode/.vscodeignore +++ /dev/null @@ -1,25 +0,0 @@ -.editorconfig -.eslintignore -.eslintrc.js -.github/** -.gitignore -.prettierignore -.stylelintrc.json -.vscode-test/** -.vscode/** -*.vsix -*.map -**/*.map -**/*.ts -**/tsconfig*.json -dist/*.vsix -node_modules/** -out/** -prettier.config.json -scripts/** -src/** -test/** -tests/** -tsconfig.json -webviews/** -pnpm-lock.yaml diff --git a/client/vscode/BUILD.bazel b/client/vscode/BUILD.bazel deleted file mode 100644 index 848a62d1019..00000000000 --- a/client/vscode/BUILD.bazel +++ /dev/null @@ -1,245 +0,0 @@ -load("@aspect_rules_js//js:defs.bzl", "js_library") -load("@aspect_rules_ts//ts:defs.bzl", "ts_config") -load("@npm//:defs.bzl", "npm_link_all_packages") -load("//client/shared/dev:generate_graphql_operations.bzl", "generate_graphql_operations") -load("//client/shared/dev:tools.bzl", "module_style_typings") -load("//dev:defs.bzl", "sass", "ts_project") -load("//dev:eslint.bzl", "eslint_config_and_lint_root") - -# TODO(bazel): webpack workers? -# gazelle:js_ignore_imports **/*.worker.ts - -# gazelle:js_resolve **/*.module.scss :module_style_typings -# gazelle:js_resolve vscode //:node_modules/@types/vscode - -npm_link_all_packages(name = "node_modules") - -eslint_config_and_lint_root() - -ts_config( - name = "tsconfig", - src = "tsconfig.json", - visibility = ["//client:__subpackages__"], - deps = [ - "//:tsconfig", - "//client/branded:tsconfig", - "//client/build-config:tsconfig", - "//client/client-api:tsconfig", - "//client/codeintellify:tsconfig", - "//client/common:tsconfig", - "//client/extension-api-types:tsconfig", - "//client/http-client:tsconfig", - "//client/shared:tsconfig", - "//client/wildcard:tsconfig", - ], -) - -module_style_typings( - name = "module_style_typings", - deps = ["//client/wildcard:sass-breakpoints"], -) - -sass( - name = "module_styles", - srcs = glob(["src/**/*.module.scss"]), - deps = ["//client/wildcard:sass-breakpoints"], -) - -# TODO(bazel): sass - src/webview/index.scss - -sass( - name = "package_styles", - srcs = glob( - ["src/**/*.scss"], - exclude = [ - "src/**/*.module.scss", - "src/webview/index.scss", - ], - ), - deps = [ - "//:node_modules/@reach/tabs", - "//client/wildcard:global-styles", - ], -) - -js_library( - name = "graphql_operations_files", - # Keep in sync with glob in client/shared/dev/generateGraphQlOperations.js - srcs = glob( - [ - "src/**/*.ts", - "src/**/*.tsx", - ], - # TODO: Ignore legacy build generated file as it conflicts with the Bazel - # build. This can be removed after the migration. - [ - "src/graphql-operations.ts", - "src/**/*.module.scss.d.ts", - ], - ), - visibility = ["//client/shared:__pkg__"], -) - -generate_graphql_operations( - name = "graphql_operations_ts", - srcs = [ - ":graphql_operations_files", - ], - out = "src/graphql-operations.ts", - interface_name = "VSCodeGraphQlOperations", - visibility = ["//client/shared:__pkg__"], -) - -ts_project( - name = "graphql_operations", - srcs = ["src/graphql-operations.ts"], - tsconfig = ":tsconfig", - deps = [ - ":node_modules/@sourcegraph/shared", - ], -) - -ts_project( - name = "vscode", - srcs = [ - "code-intel-extensions.json", - "globals.d.ts", - "package.json", - "src/backend/authenticatedUser.ts", - "src/backend/blobContent.ts", - "src/backend/eventLogger.ts", - "src/backend/fetch.ts", - "src/backend/files.ts", - "src/backend/instanceVersion.ts", - "src/backend/node-fetch-fake-for-browser.ts", - "src/backend/proxy-agent-fake-for-browser.ts", - "src/backend/repositoryMetadata.ts", - "src/backend/requestGraphQl.ts", - "src/backend/searchContexts.ts", - "src/backend/sourcegraphSettings.ts", - "src/backend/streamSearch.ts", - "src/code-intel/SourcegraphDefinitionProvider.ts", - "src/code-intel/SourcegraphHoverProvider.ts", - "src/code-intel/SourcegraphReferenceProvider.ts", - "src/code-intel/initialize.ts", - "src/code-intel/languages.ts", - "src/code-intel/location.ts", - "src/commands/browserActionsNode.ts", - "src/commands/browserActionsWeb.ts", - "src/commands/git-helpers.ts", - "src/commands/initialize.ts", - "src/common/links.ts", - "src/contract.ts", - "src/extension.ts", - "src/file-system/FileTree.ts", - "src/file-system/FilesTreeDataProvider.ts", - "src/file-system/SourcegraphFileSystemProvider.ts", - "src/file-system/SourcegraphUri.ts", - "src/file-system/commands.ts", - "src/file-system/initialize.ts", - "src/log.ts", - "src/settings/LocalStorageService.ts", - "src/settings/accessTokenSetting.ts", - "src/settings/displayWarnings.ts", - "src/settings/endpointSetting.ts", - "src/settings/invalidation.ts", - "src/settings/readConfiguration.ts", - "src/settings/recommendations.ts", - "src/settings/uninstall.ts", - "src/state.ts", - "src/vsCodeApi.ts", - "src/webview/comlink/extensionEndpoint.ts", - "src/webview/comlink/index.ts", - "src/webview/comlink/webviewEndpoint.ts", - "src/webview/commands.ts", - "src/webview/initialize.ts", - "src/webview/platform/AuthProvider.ts", - "src/webview/platform/EventLogger.ts", - "src/webview/platform/context.ts", - "src/webview/platform/polyfills/index.ts", - "src/webview/platform/polyfills/polyfill.ts", - "src/webview/platform/telemetryService.ts", - "src/webview/search-panel/MatchHandlersContext.ts", - "src/webview/search-panel/RepoView.tsx", - "src/webview/search-panel/SearchHomeView.tsx", - "src/webview/search-panel/SearchResultsView.tsx", - "src/webview/search-panel/alias/CommitSearchResult.tsx", - "src/webview/search-panel/alias/FileMatchChildren.tsx", - "src/webview/search-panel/alias/ModalVideo.tsx", - "src/webview/search-panel/alias/RepoFileLink.tsx", - "src/webview/search-panel/alias/RepoSearchResult.tsx", - "src/webview/search-panel/alias/SymbolSearchResult.tsx", - "src/webview/search-panel/alias/fetchSearchContext.ts", - "src/webview/search-panel/api.ts", - "src/webview/search-panel/components/BrandHeader.tsx", - "src/webview/search-panel/components/ButtonDropdownCta.tsx", - "src/webview/search-panel/components/HomeFooter.tsx", - "src/webview/search-panel/components/SearchExamples.tsx", - "src/webview/search-panel/components/SearchResultsInfoBar.tsx", - "src/webview/search-panel/components/icons.tsx", - "src/webview/search-panel/index.tsx", - "src/webview/sidebars/auth/AuthSidebarView.tsx", - "src/webview/sidebars/help/HelpSidebarView.tsx", - "src/webview/sidebars/help/index.tsx", - "src/webview/sidebars/history/HistorySidebarView.tsx", - "src/webview/sidebars/history/components/RecentFilesSection.tsx", - "src/webview/sidebars/history/components/RecentRepositoriesSection.tsx", - "src/webview/sidebars/history/components/RecentSearchesSection.tsx", - "src/webview/sidebars/search/ContextInvalidatedSidebarView.tsx", - "src/webview/sidebars/search/SearchSidebarView.tsx", - "src/webview/sidebars/search/api.ts", - "src/webview/sidebars/search/extension-host/index.ts", - "src/webview/sidebars/search/extension-host/main.worker.ts", - "src/webview/sidebars/search/extension-host/worker.ts", - "src/webview/sidebars/search/index.tsx", - "src/webview/theming/sourcegraphTheme.ts", - ], - tsconfig = ":tsconfig", - deps = [ - ":graphql_operations", - ":module_style_typings", - ":node_modules/@sourcegraph/branded", - ":node_modules/@sourcegraph/client-api", - ":node_modules/@sourcegraph/codeintellify", - ":node_modules/@sourcegraph/common", - ":node_modules/@sourcegraph/extension-api-types", - ":node_modules/@sourcegraph/http-client", - ":node_modules/@sourcegraph/shared", - ":node_modules/@sourcegraph/wildcard", - "//:node_modules/@mdi/js", - "//:node_modules/@reach/visually-hidden", - "//:node_modules/@types/classnames", - "//:node_modules/@types/history", - "//:node_modules/@types/lodash", - "//:node_modules/@types/node", - "//:node_modules/@types/node-fetch", - "//:node_modules/@types/react", - "//:node_modules/@types/react-dom", - "//:node_modules/@types/uuid", - "//:node_modules/@types/vscode", - "//:node_modules/@types/vscode-webview", #keep - "//:node_modules/@vscode/webview-ui-toolkit", - "//:node_modules/agent-base", - "//:node_modules/classnames", - "//:node_modules/comlink", - "//:node_modules/core-js", - "//:node_modules/execa", - "//:node_modules/graphql", - "//:node_modules/history", - "//:node_modules/http-proxy-agent", - "//:node_modules/https-proxy-agent", - "//:node_modules/lodash", - "//:node_modules/mdi-react", - "//:node_modules/node-fetch", - "//:node_modules/react", - "//:node_modules/react-dom", - "//:node_modules/react-router-dom", - "//:node_modules/rxjs", - "//:node_modules/use-deep-compare-effect", - "//:node_modules/utility-types", - "//:node_modules/uuid", - "//:node_modules/zustand", - "//client/common:common_lib", #keep - "//client/shared:shared_lib", #keep - ], -) diff --git a/client/vscode/CHANGELOG.md b/client/vscode/CHANGELOG.md deleted file mode 100644 index b0756f8b768..00000000000 --- a/client/vscode/CHANGELOG.md +++ /dev/null @@ -1,201 +0,0 @@ -# Changelog - -The Sourcegraph extension uses major.EVEN_NUMBER.patch (eg. 2.0.1) for release versions and major.ODD_NUMBER.patch (eg. 2.1.1) for pre-release versions. - -## Unreleased - -### Changes - -- Change the extension name from "Sourcegraph" to "Search by Sourcegraph" [pull/51790](https://github.com/sourcegraph/sourcegraph/pull/51790) - -### Fixes - -- Various UI fixes for dark and light themes [pull/50598](https://github.com/sourcegraph/sourcegraph/pull/50598) - -## 2.2.15 - -### Fixes - -- Prefer `upstream` and `origin` remotes when no remote is selected [issues/2761](https://github.com/sourcegraph/sourcegraph/issues/2761) [pull/48369](https://github.com/sourcegraph/sourcegraph/pull/48369) -- Fixes content security policy configuration: [pull/47263](https://github.com/sourcegraph/sourcegraph/pull/47263) - -## 2.2.14 - -### Changes - -- Implement proxy support in the process that has access to node APIs [issues/41181](https://github.com/sourcegraph/sourcegraph/issues/41181) - -## 2.2.13 - -### Changes - -- Support for logical multiline matches in the UI for Sourcegraph instance versions >= 3.42.0 [pull/43007](https://github.com/sourcegraph/sourcegraph/pull/43007) -- Tokens will now be stored in secret storage and removed from user settings [issues/36731](https://github.com/sourcegraph/sourcegraph/issues/36731) -- Users can now log in through the built-in [authentication API](https://code.visualstudio.com/api/references/vscode-api#authentication) [issues/36731](https://github.com/sourcegraph/sourcegraph/issues/36731) -- Add log out button to `Help and Feedback` sidebar under `User` [issues/36731](https://github.com/sourcegraph/sourcegraph/issues/36731) - -### Fixes - -- Fix issue where pattern type was always set to `literal` for Sourcegraph instance versions earlier than v3.43.0, which was overriding regex/structural toggles [pull/43005](https://github.com/sourcegraph/sourcegraph/pull/43005) - -## 2.2.12 - -### Fixes - -- Vary search pattern type depending on Sourcegraph instance version: [issues/41236](https://github.com/sourcegraph/sourcegraph/issues/41236), [pull/42178](https://github.com/sourcegraph/sourcegraph/pull/42178) - -## 2.2.10 - -### Changes - -- Remove tracking parameters from all shareable URLs [pull/42022](https://github.com/sourcegraph/sourcegraph/pull/42022) - -### Fixes - -- Fix Sourcegraph blob link generation: [issues/42060](https://github.com/sourcegraph/sourcegraph/issues/42060), [pull/42065](https://github.com/sourcegraph/sourcegraph/pull/42065) - -## 2.2.9 - -### Fixes - -- Fix an issue that prevented search results on some older Sourcegraph instance versions to not render properly [pull/40621](https://github.com/sourcegraph/sourcegraph/pull/40621) - -## 2.2.8 - -### Changes - -- `Internal:` Automate release step for Open-VSX registry: [issues/37704](https://github.com/sourcegraph/sourcegraph/issues/37704) -- Remove integrations banners and corresponding pings: [issues/38625](https://github.com/sourcegraph/sourcegraph/issues/38625), [pull/38715](https://github.com/sourcegraph/sourcegraph/pull/38715), [pull/38862](https://github.com/sourcegraph/sourcegraph/pull/38862) - -### Fixes - -## 2.2.7 - -### Changes - -- Remove references to creating an account on cloud, or configuring a cloud account [pull/38071](https://github.com/sourcegraph/sourcegraph/pull/38071) - -### Fixes - -## 2.2.6 - -### Changes - -- Remove notification to add Sourcegraph extension to the workspace [issues/37772](https://github.com/sourcegraph/sourcegraph/issues/37772) - -### Fixes - -- - -## 2.2.5 - -### Changes - -- Update Sourcegraph logo in sidebar [issues/37710](https://github.com/sourcegraph/sourcegraph/issues/37710) -- Sourcegraph extension is now listed in [Open VSX Registry](https://open-vsx.org/extension/sourcegraph/sourcegraph) [issues/36477](https://github.com/sourcegraph/sourcegraph/issues/36477) -- Sourcegraph extension is now available for installation in all Gitpod VS Code Workspaces [issues/37760](https://github.com/sourcegraph/sourcegraph/issues/37760) - -## 2.2.4 - -### Changes - -- Optimize package size [issues/36192](https://github.com/sourcegraph/sourcegraph/issues/36192) - -### Fixes - -- Check if default branch exists when opening files [issues/36743](https://github.com/sourcegraph/sourcegraph/issues/36743) - -## 2.2.3 - -### Changes - -- Update Access Token headers setting method --thanks @ptxmac for the contribution! [issues/34338](https://github.com/sourcegraph/sourcegraph/issues/34338) -- Add options to choose between main branch or current branch when copy/open file. Always use default branch if set [issues/34591](https://github.com/sourcegraph/sourcegraph/issues/34591) -- CTA for adding Sourcegraph extension to Workspace Recommendations [issues/34829](https://github.com/sourcegraph/sourcegraph/issues/34829) - -### Fixes - -- Windows file path issue [issues/34788](https://github.com/sourcegraph/sourcegraph/issues/34788) -- Sourcegraph icon in help sidebar now shows on light theme [issues/35672](https://github.com/sourcegraph/sourcegraph/issues/35672) -- Highlight background color for VS Code Light & Light+ Theme [issues/35767](https://github.com/sourcegraph/sourcegraph/issues/35767) -- Display reload button when instance URL is updated [issues/35980](https://github.com/sourcegraph/sourcegraph/issues/35980) - -## 2.2.2 - -### Changes - -- Display current extension version and instance version in frontend [issues/34729](https://github.com/sourcegraph/sourcegraph/issues/34729) - -### Fixes - -- Remove incorrect unsupported instance error messages on first load [issues/34207](https://github.com/sourcegraph/sourcegraph/issues/34207) -- Links to open remote file in Sourcegraph web are now decoded correctly [issues/34630](https://github.com/sourcegraph/sourcegraph/issues/34630) -- Remove pattern restriction for basePath [issues/34731](https://github.com/sourcegraph/sourcegraph/issues/34731) - -## 2.2.1 - -### Changes - -- Add Help and Feedback sidebar [issue/31021](https://github.com/sourcegraph/sourcegraph/issues/31021) -- Add CONTRIBUTING guide [issue/26536](https://github.com/sourcegraph/sourcegraph/issues/26536) -- Display error message when connected to unsupported instances [issue/31808](https://github.com/sourcegraph/sourcegraph/issues/31808) -- Log events with `IDEEXTENSION` as event source for instances on 3.38.0 and above [issue/32851](https://github.com/sourcegraph/sourcegraph/issues/32851) -- Add new configuration setting: sourcegraph.basePath [issue/32633](https://github.com/sourcegraph/sourcegraph/issues/32633) -- Add ability to open local copy of a search result if file exists in current workspace or basePath [issue/32633](https://github.com/sourcegraph/sourcegraph/issues/32633) - -### Fixes - -- Improve developer scripts [issue/32741](https://github.com/sourcegraph/sourcegraph/issues/32741) -- Code Monitor button redirect issue for non signed-in users [issues/33631](https://github.com/sourcegraph/sourcegraph/issues/33631) -- Error regarding missing PatternType when creating save search [issues/31093](https://github.com/sourcegraph/sourcegraph/issues/31093) - -## 2.2.0 - -### Changes - -- Add pings for Sourcegraph ide extensions usage metrics [issue/29124](https://github.com/sourcegraph/sourcegraph/issues/29124) -- Add input fields to update Sourcegraph instance url [issue/31804](https://github.com/sourcegraph/sourcegraph/issues/31804) -- Clear search results on tab close [issue/30583](https://github.com/sourcegraph/sourcegraph/issues/30583) - -## 2.0.9 - -### Changes - -- Add Changelog for version tracking purpose [issue/28300](https://github.com/sourcegraph/sourcegraph/issues/28300) -- Add VS Code Web support for instances on 3.36.0+ [issue/28403](https://github.com/sourcegraph/sourcegraph/issues/28403) -- Update to use API endpoint for stream search [issue/30916](https://github.com/sourcegraph/sourcegraph/issues/30916) -- Add new configuration setting `sourcegraph.requestHeaders` for adding custom headers [issue/30916](https://github.com/sourcegraph/sourcegraph/issues/30916) - -### Fixes - -- Manage context display issue for instances under v3.36.0 [issue/31022](https://github.com/sourcegraph/sourcegraph/issues/31022) - -## 2.0.8 - -### Fixes - -- Files will open in the correct url scheme [issue/31095](https://github.com/sourcegraph/sourcegraph/issues/31095) -- The 'All Search Keywords' button is now linked to Sourcegraph docs site correctly [issue/31023](https://github.com/sourcegraph/sourcegraph/issues/31023) -- Update Sign Up links with the correct utm parameters - -## 2.0.7 - -### Changes - -- Remove Sign Up CTA in Sidebar for self-host instances - -### Fixes - -- Add backward compatibility for configuration settings from v1: `sourcegraph.defaultBranch` and `sourcegraph.remoteUrlReplacements` - -## 2.0.6 - -### Changes - -- Remove Sign Up CTAs in Search Result for self-host instances - -## 2.0.1 - -### Changes - -- Add Code Monitor diff --git a/client/vscode/CONTRIBUTING.md b/client/vscode/CONTRIBUTING.md deleted file mode 100644 index 1d79161568f..00000000000 --- a/client/vscode/CONTRIBUTING.md +++ /dev/null @@ -1,219 +0,0 @@ -# Contributing to Sourcegraph VS Code Extension - -Thank you for your interest in contributing to Sourcegraph! -The goal of this document is to provide a high-level overview of how you can contribute to the Sourcegraph VS Code Extension. -Please refer to our [main CONTRIBUTING](https://github.com/sourcegraph/sourcegraph/blob/main/CONTRIBUTING.md) docs for general information regarding contributing to any Sourcegraph repository. - -## License - -Apache - -## Feedback - -Your feedback is important to us and is greatly appreciated. Please do not hesitate to submit your ideas or suggestions about how we can improve the extension to our [VS Code Extension Feedback Discussion Thread](https://github.com/sourcegraph/sourcegraph/discussions/34821) on GitHub. - -## Issues / Bugs - -New issues and feature requests can be filed through our [issue tracker](https://github.com/sourcegraph/sourcegraph/issues/new?labels=team/integrations,vscode-extension&title=VSCode+Bug+report:+&projects=Integrations%20Project%20Board) using the `vscode-extension` & `team/integrations` label. - -## Architecture Diagram - - ┌──────────────────────────┐ - │ env: Node OR Web Worker │ - ┌───────────┤ VS Code extension "Core" ├───────────────┐ - │ │ │ │ - │ └──────────────────────────┘ │ - │ │ - ┌─────────────▼────────────┐ ┌──────────────▼───────────┐ - │ env: Web │ │ env: Web │ - ┌───┤ "search sidebar" webview │ │ "search panel" webview │ - │ │ │ │ │ - │ └──────────────────────────┘ └──────────────────────────┘ - │ - ┌▼───────────────────────────┐ - │ env: Web Worker │ - │ Sourcegraph Extension host │ - │ │ - └────────────────────────────┘ - -- See below for documentation on state management. - - One state machine that lives in Core -- See './contract.ts' to see the APIs for the three main components: - - Core, search sidebar, and search panel. - - The extension host API is exposed through the search sidebar. -- See './webview/comlink' for documentation on _how_ communication between contexts works. - - It is _not_ important to understand this layer to add features to the VS Code extension (that's why it exists, after all). - -## State Management - -This extension runs code in 4 (and counting) different execution contexts. -Coordinating state between these contexts is a difficult task. So, instead of managing shared state in each context, we maintain one state machine in the "Core" context (see above for architecure diagram). -All contexts listen for state updates and emit events on which the state machine may transition. - -For example: - -- Commands from VS Code extension core -- The first submitted search in a session will cause the state machine to transition from the `search-home` state to the `search-results` state. -- This new state will be reflected in both the search sidebar and search panel UIs - -We represent a hierarchical state machine in a "flat" manner to reduce code complexity and because our state machine is simple enough to not necessitate bringing in a library. - -``` -┌───►home -│ -search -│ -└───►results -``` - -- remote-browsing -- idle -- context-invalidated - becomes: -- [search-home, search-results, remote-browsing, idle, context-invalidated] - -Example user flow state transitions: - -- User clicks on Sourcegraph logo in VS Code sidebar. -- Extension activates with initial state of `search-home` -- User submits search -> state === `search-results` -- User clicks on a search result, which opens a file -> state === `remote-browsing` -- User copies some code, then focuses an editor for a local file -> state === `idle` - -## File Structure - -Below is a quick overview of the Sourcegraph extension file structure. It does not include all the files and folders. - -``` -client/vscode -├── images -├── scripts // Command line scripts, for example, script to release and publish the extension -├── src // Extension source code -│ └── extension.ts // Extension entry file -│ └── backend // All graphQL queries -│ └── code-intel // Build the extension host that processes code-intel data -│ └── common // Commonly assets that can be shared among different contexts -│ └── commands // Build and register commands -│ └── browserActionsNode // Browser action commands when running as a regular extension where Node.js is available -│ └── browserActionsWeb // Browser action commands when running as a web extension where Node.js is not available -│ └── file-system // Build and register the custom file system -│ └── settings // Extension settings and configurations -│ └── webview // Components to build the search panel and sidebars -│ └── comlink // Handle communications between contexts -│ └── platform // Platform context for the webview -│ └── search-panel // UI for the homepage and search panel -│ └── alias // Alias files for Web extension. See README file in this directory for details -│ └── sidebars // UI for all the sidebars -│ └── theming // Styling the webview using the predefined VS Code themes -│ └── commands.ts // Commands to build the webview views and panel -├── tests // Extension test code -├── .gitignore // Ignore build output and node_modules -├── .vscodeignore // Ignore build output and node_modules -├── CHANGELOG.md // An ordered list of changes and fixes -├── CONTRIBUTING.md // General guide for developers and contributors -├── package.json // Extension manifest -├── README.md // General information about the extension -├── tsconfig.json // TypeScript configuration -``` - -## Development - -### Build and Run - -#### Desktop and Web Version - -1. `git clone` the [Sourcegraph repository](https://github.com/sourcegraph/sourcegraph) -1. Install dependencies via `pnpm install` for the Sourcegraph repository -1. Edit files in the `client/vscode` directory -1. To see your changes, open the `Run and Debug` sidebar view in VS Code, and - select `Launch VS Code Extension` (`Launch VS Code Web Extension` for VS Code Web) from the dropdown menu. (Or, in the `client/vscode` directory, run `pnpm run build`.) - -### Integration Tests - -To run integration tests: - -1. In the Sourcegraph repository root, run `pnpm install` -2. In the `client/vscode` directory: - 1. `pnpm build:test` or `pnpm watch:test` - 2. `pnpm test-integration` - -## GitPod - -The Sourcegraph extension for VS Code also works on GitPod. - -#### Desktop Version - -To install this extension on GitPod Desktop: - -1. Open the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of your workspace -2. Search for `Sourcegraph` -3. Click `install` to install the Sourcegraph extension - -#### Web Version - -To run and test the web extension on GitPod Web (as well as VS Code and GitHub for the web), you must sideload the extension from your local machine as suggested in the following steps: - -1. `git clone` the [Sourcegraph repository](https://github.com/sourcegraph/sourcegraph) -1. Run `pnpm install && pnpm generate` at the root directory to install dependencies and generate the required schemas -1. Run `pnpm -C client/vscode build` at root to build the Sourcegraph VS Code extension for Web -1. Once the build has been completed, move to the extension’s directory: `cd client/vscode` -1. Start an HTTP server inside the extension’s path to host the extension locally: `pnpx serve --cors -l 8988` -1. In another terminal, generate a publicly-accessible URL from your locally running HTTP server using the localtunnel tool: `pnpx localtunnel -p 8988` - 1. A publicly-accessible URL will be generated for you in the output followed by “your url is:” -1. Copy and then open the newly generated URL in a browser and then select “Click to Continue” -1. Open the Command Palette in GitPod Web (a GitPod Workspace using the Open in Browser setting) -1. Select “Developer: Install Web Extension…” -1. Paste the newly generated URL in the input area and select Install -1. The extension is now installed - -### Debugging - -Please refer to the [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute#debugging) guide by VS Code for debugging tips. - -## Questions - -If you need guidance or have any questions regarding Sourcegraph or the extension in general, we invite you to connect with us on the [Sourcegraph Community Slack group](https://about.sourcegraph.com/community). - -## Resources - -- [Changelog](https://marketplace.visualstudio.com/items/sourcegraph.sourcegraph/changelog) -- [Code of Conduct](https://handbook.sourcegraph.com/company-info-and-process/community/code_of_conduct/) -- [Developing Sourcegraph guide](https://docs.sourcegraph.com/dev) -- [Developing the web clients](https://docs.sourcegraph.com/dev/background-information/web) -- [Feedback / Feature Request](https://github.com/sourcegraph/sourcegraph/discussions/34821) -- [Issue Tracker](https://github.com/sourcegraph/sourcegraph/labels/vscode-extension) -- [Report a bug](https://github.com/sourcegraph/sourcegraph/issues/new?labels=team/integrations,vscode-extension&title=VSCode+Bug+report:+&projects=Integrations%20Project%20Board) -- [Troubleshooting docs](https://docs.sourcegraph.com/admin/how-to/troubleshoot-sg-extension#vs-code-extension) - -## Release Process - -The release process for the VS Code Extension for Sourcegraph is currently automated. - -#### Prerequisite - -- Install the [VSCE CLI tool](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#vsce) - -#### Release Steps - -1. Create a new branch when the main branch is ready for release, or use your current working branch if it is ready for release -2. Run `pnpm --filter @sourcegraph/vscode run release:$RELEASE_TYPE` in the root directory - - $RELEASE_TYPE: major, minor, patch, pre - - Example: `pnpm --filter @sourcegraph/vscode run release:patch` - - This command will: - - Update the package.json file with the next version number - - Update the changelog format by listing everything under `Unreleased` to the updated version number - - Make a commit for the release and push to the current branch -3. Open a PR to merge the current branch into main -4. Once the main branch has the updated version number and changelog, run `git push origin main:vsce/release` - - This will trigger the build pipeline for publishing the extension using the `pnpm release` command - - Publish release to [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=sourcegraph.sourcegraph) - - Publish release to [Open VSX Registry](https://open-vsx.org/extension/sourcegraph/sourcegraph) - - The extension will be published with the correct package name via the [vsce CLI tool](https://code.visualstudio.com/api/working-with-extensions/publishing-extension#vsce) -5. Visit the [buildkite page for the vsce/release pipeline](https://buildkite.com/sourcegraph/sourcegraph/builds?branch=vsce%2Frelease) to watch the build process - -Once the build is completed with no error, you should see the new version being verified for the Sourcegraph extension in: - -- VS Code Marketplace: [Marketplace Publisher Dashboard](https://marketplace.visualstudio.com/manage/publishers) -- Open VSX Registry: [Namespaces Tab in User Settings](https://open-vsx.org/user-settings/namespaces) - -> NOTE: It might take up to 10 minutes before the new release is published. diff --git a/client/vscode/LICENSE b/client/vscode/LICENSE deleted file mode 100644 index c3ad6a1758f..00000000000 --- a/client/vscode/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2022 Sourcegraph, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/client/vscode/README.md b/client/vscode/README.md deleted file mode 100644 index 542506327d2..00000000000 --- a/client/vscode/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Sourcegraph for Visual Studio Code - -[![vs marketplace](https://img.shields.io/vscode-marketplace/v/sourcegraph.sourcegraph.svg?label=vs%20marketplace)](https://marketplace.visualstudio.com/items?itemName=sourcegraph.sourcegraph) [![downloads](https://img.shields.io/vscode-marketplace/d/sourcegraph.sourcegraph.svg)](https://marketplace.visualstudio.com/items?itemName=sourcegraph.sourcegraph) - -![Search Gif](https://storage.googleapis.com/sourcegraph-assets/VS%20Marketplace/tableContainer2.gif) - -Sourcegraph’s code search allows you to find & fix things fast across all your code. - -Sourcegraph for VS Code allows you to search millions of open source repositories right from your VS Code IDE—for free. You can learn from helpful code examples, search best practices, and re-use code from millions of repositories across the open source universe. - -Sourcegraph’s Code Intelligence feature provides fast, cross-repository navigation with “Go to definition” and “Find references” features, allowing you to understand new code quickly and find answers in your code across codebases of any size. - -You can read more about Sourcegraph on our [website](https://about.sourcegraph.com/). - -Not the extension you're looking for? Download the [Cody extension](https://marketplace.visualstudio.com/items?itemName=sourcegraph.cody-ai) for code AI assistance. - -## Installation - -### From the Visual Studio Marketplace: - -1. Install Sourcegraph from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=sourcegraph.sourcegraph). -2. Launch VS Code, and click on the Sourcegraph (Wildcard) icon in the VS Code Activity Bar to open the Sourcegraph extension. Alternatively, you can launch the extension by pressing Cmd+Shift+P or Ctrl+Shift+P and searching for “Sourcegraph: Open search tab.” - -### From within VS Code: - -1. Open the extensions tab on the left side of VS Code (Cmd+Shift+X or Ctrl+Shift+X). -2. Search for `Sourcegraph` -> `Install` and `Reload`. - -### From Gitpod VS Code Workspaces: - -1. Open the `Extensions view` by clicking on the Extensions icon in the [Activity Bar](https://code.visualstudio.com/api/ux-guidelines/overview#activity-bar) on the side of your Gitpod VS Code workspace -2. Search for [Sourcegraph](https://open-vsx.org/extension/sourcegraph/sourcegraph) -> `Install` and `Reload`. - -## Using the Sourcegraph extension - -To get started and open the Sourcegraph extension, simply click the Sourcegraph (Wildcard) icon in the VS Code Activity Bar. - -Sourcegraph functions like any search engine; simply type in your search query, and Sourcegraph will populate search results. - -Learn more about Sourcegraph search in our [docs](https://docs.sourcegraph.com/code_search). - -For example, you can search for "auth provider" in a Go repository with a search like this one: - -``` -repo:sourcegraph/sourcegraph lang:go auth provider -``` - -![Lang search gif](https://storage.googleapis.com/sourcegraph-assets/VS%20Marketplace/sourcegraph_search.gif) - -## Adding and searching your own code - -### Creating an account - -In addition to searching open source code, you can create a Sourcegraph account to search your own private and public repositories. You can create an account and sync your repositories with the following steps: - -1. Click the `Create an account` button in the sidebar of the Sourcegraph extension. You will be directed to sourcegraph.com in your browser. -2. Follow the instructions to create and configure your Sourcegraph instance - -### Connecting to your Sourcegraph instance - -1. In Sourcegraph, in your account settings, navigate to `Access tokens`, then click `Generate new token`. -2. Once you have generated a token, navigate to your VS Code Settings, then navigate to "Extension settings". -3. Navigate to `Code preferences`, then click `Settings`. -4. Search for `Sourcegraph`, and enter the newly generated access token as well as your Sourcegraph instance URL. -5. Add custom headers using the `sourcegraph.requestHeaders` setting (added in v2.0.9) if a specific header is required to make connection to your private instance. - -### Adding it to your workspace's recommended extensions - -1. Create a `.vscode` folder (if not already present) in the root of your repository -2. Inside that folder, create a new file named `extensions.json` (if it doesn't already exist) with the following structure. - -``` -{ - "recommendations": ["sourcegraph.sourcegraph"] -} -``` - -3. If the file does exist, append `"sourcegraph.sourcegraph"` to the `"recommendations"` array. -4. Push the changes to your repository for your other colleagues to share. - -Alternatively you can use the `Extensions: Configure Recommended Extensions (Workspace Folder)` command from within VS Code. - -## Keyboard Shortcuts: - -| Description | Mac | Linux / Windows | -| -------------------------------------------- | -------------------------------------------- | --------------------------------------------- | -| Open Sourcegraph Search Tab/Search Selection | Cmd+Shift+8 | Ctrl+Shift+8 | -| Open File in Sourcegraph Cloud | Option+A | Alt+A | -| Search Selected Text in Sourcegraph Cloud | Option+S | Alt+S | - -## Extension Settings - -This extension contributes the following settings: - -| Setting | Description | Example | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| sourcegraph.url | Specify your on-premises Sourcegraph instance here, if applicable. The extension is connected to Sourcegraph Cloud by default. | "https://your-sourcegraph.com" | -| sourcegraph.accessToken | [Depreciated after 2.2.12] The access token to query the Sourcegraph API. Required to use this extension with private instances. Create a new access token at `${SOURCEGRAPH_URL}/users//settings/tokens` | null | -| sourcegraph.remoteUrlReplacements | Object, where each `key` is replaced by `value` in the remote url. | {"github": "gitlab", "master": "main"} | -| sourcegraph.defaultBranch | String to set the name of the default branch. Always open files in the default branch. | "master" | -| sourcegraph.requestHeaders | Takes object, where each value pair will be added to the request headers made to your instance. | {"Cache-Control": "no-cache", "Proxy-Authenticate": "Basic"} | -| sourcegraph.basePath | The file path on the machine to the folder that is expected to contain all repositories. We will try to open search results using the basePath. | "/Users/USERNAME/Documents/" | -| sourcegraph.proxyProtocol | The protocol to use when proxying requests to the Sourcegraph instance. | "http", "https" | -| sourcegraph.proxyHost | The host to use when proxying requests to the Sourcegraph instance. It shouldn't include a protocol (like "http://") or a port (like ":7080"). When this is set, port must be set as well. | "localhost" | -| sourcegraph.proxyPort | The port to use when proxying requests to the Sourcegraph instance. When this is set, host must be set as well. | 80, 443, 7080, 9090 | -| sourcegraph.proxyPath | The full path to a file when proxying requests to the Sourcegraph instance via a UNIX socket. | "/home/user/path/unix.socket" | - -## Questions & Feedback - -Feedback and feature requests can be submitted to our [VS Code Extension Feedback Discussion Board](https://github.com/sourcegraph/sourcegraph/discussions/34821) on GitHub. - -## Uninstallation - -1. Open the extensions tab on the left side of VS Code (Cmd+Shift+X or Ctrl+Shift+X). -2. Search for `Sourcegraph` -> Gear icon -> `Uninstall` and `Reload`. - -## Changelog - -Click [here](https://marketplace.visualstudio.com/items/sourcegraph.sourcegraph/changelog) to check the full changelog. - -VS Code will auto-update extensions to the highest version available. Even if you have opted into a pre-release version, you will be updated to the released version when a higher version is released. - -The Sourcegraph extension uses major.EVEN_NUMBER.patch (eg. 2.0.1) for release versions and major.ODD_NUMBER.patch (eg. 2.1.1) for pre-release versions.``` - -## Development - -Please see the [CONTRIBUTING](./CONTRIBUTING.md) document if you are interested in contributing directly to our code base. diff --git a/client/vscode/code-intel-extensions.json b/client/vscode/code-intel-extensions.json deleted file mode 100644 index f342297748f..00000000000 --- a/client/vscode/code-intel-extensions.json +++ /dev/null @@ -1 +0,0 @@ -["sourcegraph/apex","sourcegraph/clojure","sourcegraph/cobol","sourcegraph/cpp","sourcegraph/csharp","sourcegraph/cuda","sourcegraph/dart","sourcegraph/elixir","sourcegraph/erlang","sourcegraph/go","sourcegraph/graphql","sourcegraph/groovy","sourcegraph/haskell","sourcegraph/java","sourcegraph/jsonnet","sourcegraph/kotlin","sourcegraph/lisp","sourcegraph/lua","sourcegraph/ocaml","sourcegraph/pascal","sourcegraph/perl","sourcegraph/php","sourcegraph/powershell","sourcegraph/protobuf","sourcegraph/python","sourcegraph/r","sourcegraph/ruby","sourcegraph/rust","sourcegraph/scala","sourcegraph/shell","sourcegraph/starlark","sourcegraph/strato","sourcegraph/swift","sourcegraph/tcl","sourcegraph/thrift","sourcegraph/typescript","sourcegraph/verilog","sourcegraph/vhdl"] \ No newline at end of file diff --git a/client/vscode/globals.d.ts b/client/vscode/globals.d.ts deleted file mode 100644 index 732c035e585..00000000000 --- a/client/vscode/globals.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -declare module '*.scss' { - const cssModule: string - export default cssModule -} -declare module '*.css' { - const cssModule: string - export default cssModule -} - -/** - * Set by shared/dev/jest-environment.js - */ -declare var jsdom: import('jsdom').JSDOM diff --git a/client/vscode/images/logo.png b/client/vscode/images/logo.png deleted file mode 100644 index ee37f172658e5a8d8e2d377c09341c725f4aaa0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2614 zcmV-63d!|}P)B8ucNo z78M1v6-yzYU90sMl6cO}-DJDn?9T4qd*oO|b9M;K`T93Py3 zj|?0x9I=M~U4f$r$I_m;je@Y&(j3EtpM;O;CZg|V2ty7)$Xlrm1^|Rd!I$9}?(!5R zIK}~lvX$I006_d& z8vuZe^j!_7cICOPG4}w|hMwC3prH4U!AD$U6LHyw1%T;wr3l){So!$^Fig;fYvpH} z27-IuB5EoTWJ-8O{2iCDHv*5fiqa8rtM+T_k9Y^bI3OQ@@R)_X+RoF#-ysO60E9wR z>i}VhHR=U*;aHco2tSWG{F{0F4t_UE091(Dafy5Va+lc+erFlZF&PyAsOt4Gh&NmU z@n?jZ3goOjSLgv)A#5u{(DPLmo~GqFLk&QYuq{`k_+vuP*u$*MAi@a%q-I6JIAB2S=NQ3tPlOqOMbqK{ zDSj_g*T}(T3xF)qmWi5+%c*BQ2WN!h3l(i~uLK&C4M6XoSK*R71vl&fESV4o$O_$r zFwm?A=rI$JMVVP)sIdkB;sC4#kWJ(6v@9$KXo8$%S`NDhpneQwIuIWNu!Vr{O(_Zh zunPob8CG|phop0u@@m*s!kfKMK6<$1`Yo^Zh`6;9b`QWU7~~>;p;z%u0)RzXSmyd1 z#7E($qL2pGEby{$K!u*&EUTjT8v{VlWnr1?a}j?E#8=iK{LczLdo3p2pW7rADT&bm z-2xQ}MR02=mK2}M3#ItnS2~Ni--qyc1CF&#kSovT!L``p}5NhY~|?S7_JJ@?xK;Pv`>g%_^`27q9VA`1WS z-vP1m-i7LCn8j}%1gaHZNWpNhWF5}E2FD~E@g5!n+Qt9;!}qy}kD`z368~loaQ2!j z-5wR$#j1B09Vq$q00`5X&Wrj2#Q*im2p93O=W87da~h(2mFw|zVr7}@w}nAxS@_~7 zZ;H!5yvQZ{Me&Qe8>xQ;Oa>V6#AW;Vh`>k1tPDbTO9z$PcdOH!#LrTQzvBUd?fo5j z2D!eE($oxv$KKDxav=T;Rbw>|Fvd{)rk>%ILlr$lCTF8&niEI>632fv2T)xOAyUk?D*0Tx7rZSQqah2Tj5LNg3`5Pyox@2(#U z2z4C51O_dWI#dBLBkU^LI$XGhg#!ScI7Yxn(O-mz=HrYfn_VTQW(l` zBsq$IGXNe0m<`x%Rn)-1BG>N>fE@^CZBW=V3`x%7?+$#N?;r#g zhORB!2!X>`{I+i_TNt{sbbValFc!a~C0Q*DzO4*LvX&)q1)!%FRV)l`Ee8P1umnK$ zlB^bnU4p{Vfqm=%0P%4K(xk5QTNrMzah!|bFcKeoJ=tVbgy7<%TVfS}*cjZ3A^-&d zAnQa?{6<^9e@mP{v>7!RiU2^`VIV%mKD)kbaOj-qJNlM*XXvOnv;R49>GlJmj{_Vl zzGrvd^No1og_lJCE!VdH+n0Ae6mA$s2OK9r@!9OX(@gwE3ndIe0l-Oo6dlD!2>e<| zUWv9{51@X|sFgHVV~$d9T(Tld#9xWdK4Xa6F)oQ2Yrl z;txGCr$c zF28$BBMjNbNurKM@!911x83oxczXQ#mW3Xb`89Ze-WG$>14EWzD|2(Il{;e)bGT6eAZ3F1 z0mT8403hgs&v=LH@h%ZwTPE~rAjAMcPNeT@?;UW>l-I1TEX+6y2*Rd%*m%hXgN6fu zxqiNf?^C9OnXm&;4nX`~H(s)Vph5sBH$=d;dfswP&e)*>Hx4mJJj?*B5Q?vI<0VQj zFcxM2C<3aAjQCYX)3z+E0ss&NU1>qH_&PRTvYCLwEFdR^(v|Bgk27ppSRnw?3ruMe zUuRE^d^Vu04j_v%d4<=OBiLb^vMihmH2@NVDMg+Dd;W~>9}kZKN)Ip`dH^H@=mpYU z=GkQ#@D&K}0aY*l650;(M?nB5Yb;4U;{&AYTz{HifzR zYLpBjgR)|j0dRyhs@cN5zcOaZBfvnT@B%D1k+uNPrGqSE4f_b7c?__9Q^Y%Fhn@~7XSbN07*qoM6N<$f-dW;Z~y=R diff --git a/client/vscode/images/logo.svg b/client/vscode/images/logo.svg deleted file mode 100644 index 27ac88b18eb..00000000000 --- a/client/vscode/images/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/client/vscode/images/logomark_dark.svg b/client/vscode/images/logomark_dark.svg deleted file mode 100644 index e017293e6ba..00000000000 --- a/client/vscode/images/logomark_dark.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/client/vscode/images/logomark_light.svg b/client/vscode/images/logomark_light.svg deleted file mode 100644 index ae0423535c9..00000000000 --- a/client/vscode/images/logomark_light.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/client/vscode/images/sourcegraph-logo-dark.svg b/client/vscode/images/sourcegraph-logo-dark.svg deleted file mode 100644 index e44ad04a1ba..00000000000 --- a/client/vscode/images/sourcegraph-logo-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/vscode/images/sourcegraph-logo-light.svg b/client/vscode/images/sourcegraph-logo-light.svg deleted file mode 100644 index fa931a6bc9a..00000000000 --- a/client/vscode/images/sourcegraph-logo-light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/vscode/package.json b/client/vscode/package.json deleted file mode 100644 index 92f0894437d..00000000000 --- a/client/vscode/package.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "private": true, - "name": "@sourcegraph/vscode", - "displayName": "Search by Sourcegraph", - "version": "2.2.15", - "description": "Search all of your repositories across all branches and all code hosts", - "publisher": "sourcegraph", - "sideEffects": true, - "license": "Apache-2.0", - "icon": "images/logo.png", - "repository": { - "type": "git", - "url": "https://github.com/sourcegraph/sourcegraph.git", - "directory": "client/vscode" - }, - "bugs": { - "url": "https://github.com/sourcegraph/sourcegraph/issues/new?labels=team/integrations,vscode-extension&title=VSCode+Bug+report:+&projects=Integrations%20Project%20Board" - }, - "engines": { - "vscode": "^1.63.2" - }, - "categories": [ - "Other" - ], - "activationEvents": [ - "onStartupFinished" - ], - "main": "./dist/node/extension.js", - "browser": "./dist/webworker/extension.js", - "contributes": { - "commands": [ - { - "command": "sourcegraph.search", - "category": "Sourcegraph", - "title": "Search with Sourcegraph", - "icon": { - "light": "images/logo.svg", - "dark": "images/logo.svg" - } - }, - { - "command": "sourcegraph.openInBrowser", - "category": "Sourcegraph", - "title": "Open File in Sourcegraph Web", - "icon": { - "light": "images/logomark_dark.svg", - "dark": "images/logomark_light.svg" - } - }, - { - "command": "sourcegraph.copyFileLink", - "category": "Sourcegraph", - "title": "Copy Sourcegraph File Link" - }, - { - "command": "sourcegraph.selectionSearchWeb", - "category": "Sourcegraph", - "title": "Search Selection in Sourcegraph Web" - }, - { - "command": "sourcegraph.removeRepoTree", - "category": "Sourcegraph", - "title": "Remove Repository from Sourcegraph File System", - "icon": "$(trash)" - } - ], - "authentication": [ - { - "id": "sourcegraphauth", - "label": "Sourcegraph Auth" - } - ], - "viewsContainers": { - "activitybar": [ - { - "id": "sourcegraph-view", - "title": "Sourcegraph Search", - "icon": "images/logomark_dark.svg" - } - ] - }, - "views": { - "sourcegraph-view": [ - { - "type": "webview", - "id": "sourcegraph.searchSidebar", - "name": "Sourcegraph Search", - "visibility": "visible" - }, - { - "id": "sourcegraph.files", - "name": "Files", - "visibility": "visible" - }, - { - "type": "webview", - "id": "sourcegraph.helpSidebar", - "name": "Help and feedback", - "visibility": "collapsed" - } - ] - }, - "viewsWelcome": [ - { - "view": "sourcegraph.files", - "contents": "No open files." - } - ], - "configuration": { - "type": "object", - "title": "Sourcegraph extension configuration", - "properties": { - "sourcegraph.url": { - "type": [ - "string" - ], - "default": "https://sourcegraph.com", - "description": "The base URL of the Sourcegraph instance to use." - }, - "sourcegraph.accessToken": { - "type": [ - null - ], - "description": "[Depreciated after v2.2.12] The access token to query the Sourcegraph API. Create a new access token at ${SOURCEGRAPH_URL}/users//settings/tokens. Unless you are using a private instance of Sourcegraph, then ${SOURCEGRAPH_URL} is https://sourcegraph.com." - }, - "sourcegraph.remoteUrlReplacements": { - "type": [ - "object" - ], - "default": {}, - "examples": [ - { - "github": "gitlab", - "master": "main" - } - ], - "description": "For each item in this object, replace key with value in the remote url." - }, - "sourcegraph.defaultBranch": { - "type": [ - "string" - ], - "default": "", - "description": "Always open local files on Sourcegraph Web at this default branch." - }, - "sourcegraph.requestHeaders": { - "type": [ - "object" - ], - "default": {}, - "examples": [ - { - "Cache-Control": "no-cache", - "Proxy-Authenticate": "Basic" - } - ], - "description": "Each value pair will be added to the request headers made to your instance." - }, - "sourcegraph.basePath": { - "description": "The file path on the machine to the folder that is expected to contain all repositories.", - "type": "string", - "default": null, - "examples": [ - "/Users/USERNAME/Documents/" - ] - }, - "sourcegraph.proxyProtocol": { - "description": "The protocol to use when proxying requests to the Sourcegraph instance.", - "type": "string", - "default": "", - "examples": [ - "http", - "https" - ] - }, - "sourcegraph.proxyHost": { - "description": "The host to use when proxying requests to the Sourcegraph instance. It shouldn't include a protocol (like \"http://\") or a port (like \":7080\"). When this is set, port must be set as well.", - "type": "string", - "default": "", - "examples": [ - "localhost", - "1.2.3.4" - ] - }, - "sourcegraph.proxyPort": { - "description": "The port to use when proxying requests to the Sourcegraph instance. When this is set, host must be set as well.", - "type": "number", - "default": 0, - "examples": [ - 80, - 443, - 7080, - 9090 - ] - }, - "sourcegraph.proxyPath": { - "description": "The full path to a file when proxying requests to the Sourcegraph instance via a UNIX domain socket.", - "type": "string", - "default": "", - "examples": [ - "/home/user/path/unix.socket" - ] - } - } - }, - "keybindings": [ - { - "command": "sourcegraph.search", - "key": "ctrl+shift+8", - "mac": "cmd+shift+8" - }, - { - "command": "sourcegraph.openInBrowser", - "key": "alt+a", - "mac": "option+a" - }, - { - "command": "sourcegraph.selectionSearchWeb", - "key": "alt+s", - "mac": "option+s" - } - ], - "menus": { - "editor/context": [ - { - "command": "sourcegraph.openInBrowser", - "group": "sourcegraph", - "label": "sourcegraph" - }, - { - "command": "sourcegraph.copyFileLink", - "group": "sourcegraph", - "label": "sourcegraph" - }, - { - "command": "sourcegraph.selectionSearchWeb", - "group": "sourcegraph", - "when": "editorHasSelection" - }, - { - "command": "sourcegraph.search", - "group": "sourcegraph" - } - ], - "view/title": [ - { - "command": "sourcegraph.removeRepoTree", - "when": "view == sourcegraph.files && sourcegraph.removeRepository", - "group": "navigation" - } - ], - "editor/title": [ - { - "command": "sourcegraph.openInBrowser", - "when": "resourceScheme == sourcegraph && editorReadonly", - "group": "navigation" - } - ] - } - }, - "scripts": { - "lint:js": "eslint --cache '**/*.[jt]s?(x)'", - "package": "ts-node ./scripts/package.ts", - "prebuild": "pnpm -w run generate && pnpm build-inline-extensions", - "build-inline-extensions": "node scripts/build-inline-extensions", - "build": "pnpm run prebuild && NODE_ENV=development ts-node-transpile-only scripts/build.ts", - "build:test": "IS_TEST=1 pnpm run build", - "watch": "WATCH=1 pnpm run build", - "watch:test": "IS_TEST=1 pnpm run watch", - "test-integration": "TS_NODE_PROJECT=tests/tsconfig.json mocha --parallel=${CI:-\"false\"} --retries=2 ./tests/**/*.test.ts", - "release": "ts-node ./scripts/publish.ts", - "release:major": "VSCE_RELEASE_TYPE=major ts-node ./scripts/release.ts", - "release:minor": "VSCE_RELEASE_TYPE=minor ts-node ./scripts/release.ts", - "release:patch": "VSCE_RELEASE_TYPE=patch ts-node ./scripts/release.ts", - "release:pre": "VSCE_RELEASE_TYPE=prerelease ts-node ./scripts/release.ts" - }, - "devDependencies": { - "vsce": "^2.7.0", - "@sourcegraph/build-config": "workspace:*", - "@sourcegraph/extension-api-types": "workspace:*" - }, - "dependencies": { - "@sourcegraph/branded": "workspace:*", - "@sourcegraph/client-api": "workspace:*", - "@sourcegraph/codeintellify": "workspace:*", - "@sourcegraph/common": "workspace:*", - "@sourcegraph/http-client": "workspace:*", - "@sourcegraph/shared": "workspace:*", - "@sourcegraph/wildcard": "workspace:*" - } -} diff --git a/client/vscode/scripts/buffer-shim.js b/client/vscode/scripts/buffer-shim.js deleted file mode 100644 index 9f0abdf2d81..00000000000 --- a/client/vscode/scripts/buffer-shim.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is used for esbuild's `inject` option -// in order to load node polyfills in the webworker -// extension host. -// See: https://esbuild.github.io/api/#inject. -export const Buffer = require('buffer/').Buffer diff --git a/client/vscode/scripts/build-inline-extensions.js b/client/vscode/scripts/build-inline-extensions.js deleted file mode 100644 index 96694c1235a..00000000000 --- a/client/vscode/scripts/build-inline-extensions.js +++ /dev/null @@ -1,7 +0,0 @@ -const path = require('path') - -const { buildCodeIntelExtensions } = require('../../shared/dev/buildCodeIntelExtensions') - -const pathToExtensionBundles = path.join(process.cwd(), 'dist', 'extensions') - -buildCodeIntelExtensions({ pathToExtensionBundles, revision: 'v3.41.1' }) diff --git a/client/vscode/scripts/build.ts b/client/vscode/scripts/build.ts deleted file mode 100644 index 80cf1c048d4..00000000000 --- a/client/vscode/scripts/build.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { existsSync } from 'fs' -import path from 'path' - -import * as esbuild from 'esbuild' -import { rm } from 'shelljs' - -import { - packageResolutionPlugin, - stylePlugin, - workerPlugin, - RXJS_RESOLUTIONS, - buildTimerPlugin, -} from '@sourcegraph/build-config/src/esbuild/plugins' - -const minify = process.env.NODE_ENV === 'production' -const outdir = path.join(__dirname, '../dist') -const isTest = !!process.env.IS_TEST - -const TARGET_TYPE = process.env.TARGET_TYPE - -const SHARED_CONFIG = { - outdir, - minify, - sourcemap: true, -} - -export async function build(): Promise { - if (existsSync(outdir)) { - rm('-rf', outdir) - } - - const buildPromises: Promise[] = [] - - if (TARGET_TYPE === 'node' || !TARGET_TYPE) { - buildPromises.push( - esbuild.context({ - entryPoints: { extension: path.join(__dirname, '/../src/extension.ts') }, - bundle: true, - format: 'cjs', - platform: 'node', - external: ['vscode'], - banner: { js: 'global.Buffer = require("buffer").Buffer' }, - define: { - 'process.env.IS_TEST': isTest ? 'true' : 'false', - }, - ...SHARED_CONFIG, - outdir: path.join(SHARED_CONFIG.outdir, 'node'), - mainFields: ['module', 'main'], // for node-jsonc-parser; see https://github.com/microsoft/node-jsonc-parser/issues/57#issuecomment-1634726605 - }) - ) - } - if (TARGET_TYPE === 'webworker' || !TARGET_TYPE) { - buildPromises.push( - esbuild.context({ - entryPoints: { extension: path.join(__dirname, '/../src/extension.ts') }, - bundle: true, - format: 'cjs', - platform: 'browser', - external: ['vscode'], - define: { - 'process.env.IS_TEST': isTest ? 'true' : 'false', - global: 'globalThis', - }, - inject: ['./scripts/process-shim.js', './scripts/buffer-shim.js'], - plugins: [ - packageResolutionPlugin({ - process: require.resolve('process/browser'), - path: require.resolve('path-browserify'), - http: require.resolve('stream-http'), - https: require.resolve('https-browserify'), - stream: require.resolve('stream-browserify'), - util: require.resolve('util'), - events: require.resolve('events'), - buffer: require.resolve('buffer/'), - './browserActionsNode': path.resolve(__dirname, '../src', 'commands', 'browserActionsWeb'), - 'http-proxy-agent': path.resolve( - __dirname, - '../src', - 'backend', - 'proxy-agent-fake-for-browser.ts' - ), - 'https-proxy-agent': path.resolve( - __dirname, - '../src', - 'backend', - 'proxy-agent-fake-for-browser.ts' - ), - 'node-fetch': path.resolve(__dirname, '../src', 'backend', 'node-fetch-fake-for-browser.ts'), - }), - ], - ...SHARED_CONFIG, - outdir: path.join(SHARED_CONFIG.outdir, 'webworker'), - }) - ) - } - - buildPromises.push( - esbuild.context({ - entryPoints: { - helpSidebar: path.join(__dirname, '../src/webview/sidebars/help'), - searchSidebar: path.join(__dirname, '../src/webview/sidebars/search'), - searchPanel: path.join(__dirname, '../src/webview/search-panel'), - style: path.join(__dirname, '../src/webview/index.scss'), - }, - bundle: true, - format: 'esm', - platform: 'browser', - splitting: true, - plugins: [ - stylePlugin, - workerPlugin, - packageResolutionPlugin({ - path: require.resolve('path-browserify'), - process: require.resolve('process/browser'), - http: require.resolve('stream-http'), // for stream search - event source polyfills - https: require.resolve('https-browserify'), // for stream search - event source polyfills - ...RXJS_RESOLUTIONS, - './RepoSearchResult': require.resolve('../src/webview/search-panel/alias/RepoSearchResult'), - './CommitSearchResult': require.resolve('../src/webview/search-panel/alias/CommitSearchResult'), - './SymbolSearchResult': require.resolve('../src/webview/search-panel/alias/SymbolSearchResult'), - './FileMatchChildren': require.resolve('../src/webview/search-panel/alias/FileMatchChildren'), - './RepoFileLink': require.resolve('../src/webview/search-panel/alias/RepoFileLink'), - '../documentation/ModalVideo': require.resolve('../src/webview/search-panel/alias/ModalVideo'), - }), - buildTimerPlugin, - ], - loader: { - '.ttf': 'file', - }, - define: { - 'process.env.INTEGRATION_TESTS': isTest ? 'true' : 'false', - }, - assetNames: '[name]', - ...SHARED_CONFIG, - outdir: path.join(SHARED_CONFIG.outdir, 'webview'), - }) - ) - - const ctxs = await Promise.all(buildPromises) - - await Promise.all(ctxs.map(ctx => ctx.rebuild())) - - if (process.env.WATCH) { - await Promise.all(ctxs.map(ctx => ctx.watch())) - await new Promise(() => {}) // wait forever - } - - await Promise.all(ctxs.map(ctx => ctx.dispose())) -} - -if (require.main === module) { - build().catch(error => { - console.error('Error:', error) - process.exit(1) - }) -} diff --git a/client/vscode/scripts/package.ts b/client/vscode/scripts/package.ts deleted file mode 100644 index 2541e6168c5..00000000000 --- a/client/vscode/scripts/package.ts +++ /dev/null @@ -1,17 +0,0 @@ -import childProcess from 'child_process' -import fs from 'fs' - -const originalPackageJson = fs.readFileSync('package.json').toString() - -try { - childProcess.execSync('pnpm build-inline-extensions && pnpm build', { stdio: 'inherit' }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const packageJson: any = JSON.parse(originalPackageJson) - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - packageJson.name = 'sourcegraph' - fs.writeFileSync('package.json', JSON.stringify(packageJson)) - - childProcess.execSync('vsce package --no-dependencies --allow-star-activation -o dist', { stdio: 'inherit' }) -} finally { - fs.writeFileSync('package.json', originalPackageJson) -} diff --git a/client/vscode/scripts/process-shim.js b/client/vscode/scripts/process-shim.js deleted file mode 100644 index 33a4a9d1465..00000000000 --- a/client/vscode/scripts/process-shim.js +++ /dev/null @@ -1,5 +0,0 @@ -// This file is used for esbuild's `inject` option -// in order to load node polyfills in the webworker -// extension host. -// See: https://esbuild.github.io/api/#inject. -export const process = require('process/browser') diff --git a/client/vscode/scripts/publish.ts b/client/vscode/scripts/publish.ts deleted file mode 100644 index df9f9a4d85a..00000000000 --- a/client/vscode/scripts/publish.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -import childProcess from 'child_process' -import fs from 'fs' - -import * as semver from 'semver' - -import { version } from '../package.json' - -/** - * This script is used by the CI to publish the extension to the VS Code Marketplace - * It is triggered when a commit has been made to the vsce release branch - * Refer to our CONTRIBUTION docs to learn more about our release process - */ -// Get current package.json -const originalPackageJson = fs.readFileSync('package.json').toString() -/** - * Build and publish the extension with the updated package name - * using the tokens stored in the pipeline to run commands in pnpm - * and allows all events to activate the extension - */ -const isPreRelease = semver.minor(version) % 2 !== 0 ? '--pre-release' : '' -// Tokens are stored in CI pipeline -const tokens = { - vscode: process.env.VSCODE_MARKETPLACE_TOKEN, - openvsx: process.env.VSCODE_OPENVSX_TOKEN, -} -// Assume this is for testing purpose if tokens are not found -const hasTokens = tokens.vscode !== undefined && tokens.openvsx !== undefined -const commands = { - vscode_info: 'vsce show sourcegraph.sourcegraph --json', - // To publish to VS Code Marketplace - vscode_publish: `vsce publish ${isPreRelease} --pat $VSCODE_MARKETPLACE_TOKEN --no-dependencies --allow-star-activation`, - // To package the extension without publishing - vscode_package: `vsce package ${isPreRelease} --no-dependencies --allow-star-activation`, - // To publish to the open-vsx registry - openvsx_publish: 'npx --yes ovsx publish --no-dependencies -p $VSCODE_OPENVSX_TOKEN', -} -// Publish the extension with the correct extension name "sourcegraph" -try { - childProcess.execSync('pnpm build-inline-extensions && pnpm build', { stdio: 'inherit' }) - // Get the latest release version nubmer of the last release from VS Code Marketplace using the vsce cli tool - const response = childProcess.execSync(commands.vscode_info).toString() - /* - `vscode_info` command output is extension info prepended by command name and arguments, e.g. - $ /Users/taras/projects/sourcegraph/node_modules/.bin/vsce show sourcegraph.sourcegraph --json - { - ...json - } - We cut everything before the json object so that we can parse it as json. - */ - const trimmedResponse = response.slice(Math.max(0, response.indexOf('{'))) - const latestVersion: string = JSON.parse(trimmedResponse).versions[0].version - if (hasTokens && (version === latestVersion || !semver.valid(latestVersion) || !semver.valid(version))) { - throw new Error( - version === latestVersion - ? 'Cannot release extension with the same version number.' - : `invalid version number: ${latestVersion} & ${version}` - ) - } - // Update package name from @sourcegraph/vscode to sourcegraph in package.json - const packageJson = originalPackageJson.replace('@sourcegraph/vscode', 'sourcegraph') - fs.writeFileSync('package.json', packageJson) - if (hasTokens) { - // Run the publish commands - childProcess.execSync(commands.vscode_publish, { stdio: 'inherit' }) - childProcess.execSync(commands.openvsx_publish, { stdio: 'inherit' }) - } else { - // Use vsce package command instead without publishing the extension for testing - childProcess.execSync(commands.vscode_package, { stdio: 'inherit' }) - } - console.log(`The extension has been ${hasTokens ? 'published' : 'packaged'} successfully.`) -} catch (error) { - console.error('Failed to publish VSCE:', error) - console.error('You may not run this script locally to publish the extension.') -} finally { - // Revert changes made to package.json - fs.writeFileSync('package.json', originalPackageJson) -} diff --git a/client/vscode/scripts/release.ts b/client/vscode/scripts/release.ts deleted file mode 100644 index 4c02048b564..00000000000 --- a/client/vscode/scripts/release.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -import childProcess from 'child_process' -import fs from 'fs' - -import * as semver from 'semver' - -import { version } from '../package.json' - -/** - * This script updates the version number and changelog for release purpose - */ -// Get the current package.json and changelog files -const originalPackageJson = fs.readFileSync('package.json').toString() -const originalChangelogFile = fs.readFileSync('CHANGELOG.md').toString() -// Only proceed the release steps if a valid release type is provided -const vsceReleaseType = String(process.env.VSCE_RELEASE_TYPE).toLowerCase() as semver.ReleaseType -const isValidType = ['major', 'minor', 'patch', 'prerelease'].includes(vsceReleaseType) -if (isValidType) { - /** - * Generate the next version number for release purpose - * prerelease is treated as minor update because the prerelease - * tag in semver is not supported by VS Code - * ref: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions - */ - const releaseType: semver.ReleaseType = vsceReleaseType === 'prerelease' ? 'minor' : vsceReleaseType - // Get the latest release version nubmer of the last release from VS Code Marketplace using the vsce cli tool - // We use this to compare the current package version number with the current release version number - they should be the same - const response = childProcess.execSync('vsce show sourcegraph.sourcegraph --json').toString() - const latestVersion: string = JSON.parse(response).versions[0].version - if (!semver.valid(latestVersion)) { - throw new Error( - 'Failed to connect to VSCE to retreive the latest extension version number. Make sure you have vsce cli tool installed.' - ) - } - if (version !== latestVersion) { - throw new Error('The current version number is not align with the version number of the latest release.') - } - // Increase minor version number by twice for minor release because ODD minor number is for pre-release - const nextVersion = - vsceReleaseType === 'minor' - ? semver.inc(semver.inc(latestVersion, releaseType)!, releaseType)! - : semver.inc(latestVersion, releaseType)! - // commit message for the release, eg. vsce: minor release v1.0.1 - if (nextVersion && nextVersion !== latestVersion) { - try { - // Update version number in package.json - const packageJson = originalPackageJson.replace(`"version": "${version}"`, `"version": "${nextVersion}"`) - fs.writeFileSync('package.json', packageJson) - // Update Changelog with the new version number - const changelogFile = originalChangelogFile.replace( - 'Unreleased', - `Unreleased\n\n### Changes\n\n### Fixes\n\n## ${nextVersion}` - ) - fs.writeFileSync('CHANGELOG.md', changelogFile) - // Commit and push - const releaseCommitMessage = `vsce: ${releaseType} release v${nextVersion}` - childProcess.execSync(`git add . && git commit -m "${releaseCommitMessage}" && git push -u origin HEAD`, { - stdio: 'inherit', - }) - } catch (error) { - console.error(`Publish VSCE with ${releaseType} failed:`, error) - // Make sure any changes made are reverted if an error has occured - fs.writeFileSync('package.json', originalPackageJson) - fs.writeFileSync('CHANGELOG.md', originalChangelogFile) - } - } else { - throw new Error('Version number is invalid.') - } -} else { - throw new Error(`Release type is invalid: ${vsceReleaseType}`) -} diff --git a/client/vscode/src/README.md b/client/vscode/src/README.md deleted file mode 100644 index 29c811bfde4..00000000000 --- a/client/vscode/src/README.md +++ /dev/null @@ -1,44 +0,0 @@ -# Extension Source Code - -This folder contains the source code for the extension. - -## Contexts - -As detailed in our CONTRIBUTING.md file, this extension runs code in the following execution contexts: - -1. Core App - -- The main app where all the commands, webviews, file system, and all other components are put together and registered as a single extension - - Runs code with Node.js on Desktop clients (for example, VS Code on Desktop) as a regular extension, or Web Worker on Web clients (for example, github.dev on browser) as a web extension - - [Web extensions](https://code.visualstudio.com/api/extension-guides/web-extensions) run in the web extension host in a Browser [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) environment, and do not have access to Node.js globals and libraries at runtime - -2. Sidebars - -- This is the UI for all the sidebars used in the core app - - Uses the [VS Code Webview API](https://code.visualstudio.com/api/extension-guides/webview) to render the webview views in search sidebar, search history sidebar, auth sidebar, and help and feedback sidebar - -3. Search Panel - -- This is the UI for the extension search homepage and to display search results - - Uses the [VS Code Webview API](https://code.visualstudio.com/api/extension-guides/webview) to render the webview panel for the Sourcegraph search homepage as a distinct editor tab - -4. Extension Host - -- It processes the code-intel data - - Bring Sourcegraph code-intel to IDEs via the [VS Code langauges API](https://code.visualstudio.com/api/language-extensions/programmatic-language-features) - - Runs in [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) - -5. Custom File System - -- This is a custom file system implemented to display file structure of selected repositories from a search result - - Displays file-tree for the selected repositories when opening a [virtual document](https://code.visualstudio.com/api/extension-guides/virtual-documents) using the following VS Code APIs: - - [File System](https://code.visualstudio.com/api/references/vscode-api#FileSystemProvider) - - [Tree View](https://code.visualstudio.com/api/extension-guides/tree-view) - - [Virtual Document](https://code.visualstudio.com/api/extension-guides/virtual-documents) - - A virtual document is a document file located remotely on cloud and not on the local disk - - [Virtual Workspaces](https://code.visualstudio.com/api/extension-guides/virtual-workspaces) - - [Text Document Content Provider](https://code.visualstudio.com/api/extension-guides/virtual-documents#textdocumentcontentprovider) - -## List of Files and Folders - -See the File Structure section in our CONTRIBUTING.md file to learn more about the folders and files in this directory. diff --git a/client/vscode/src/backend/authenticatedUser.ts b/client/vscode/src/backend/authenticatedUser.ts deleted file mode 100644 index 743ea6e0761..00000000000 --- a/client/vscode/src/backend/authenticatedUser.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { type Observable, ReplaySubject } from 'rxjs' -import type * as vscode from 'vscode' - -import { gql } from '@sourcegraph/http-client' -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import type { CurrentAuthStateResult, CurrentAuthStateVariables } from '@sourcegraph/shared/src/graphql-operations' - -import { scretTokenKey } from '../webview/platform/AuthProvider' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -// Minimal auth state for the VS Code extension. -// Uses only old fields for backwards compatibility with old GraphQL API versions. -const currentAuthStateQuery = gql` - query CurrentAuthState { - currentUser { - __typename - id - databaseID - username - avatarURL - email - displayName - siteAdmin - url - settingsURL - organizations { - nodes { - id - name - displayName - url - settingsURL - } - } - session { - canSignOut - } - viewerCanAdminister - } - } -` - -// Update authenticatedUser on accessToken changes -export function observeAuthenticatedUser(secretStorage: vscode.SecretStorage): Observable { - const authenticatedUsers = new ReplaySubject(1) - - function updateAuthenticatedUser(): void { - requestGraphQLFromVSCode(currentAuthStateQuery, {}) - .then(authenticatedUserResult => { - authenticatedUsers.next(authenticatedUserResult.data ? authenticatedUserResult.data.currentUser : null) - if (!authenticatedUserResult.data) { - throw new Error('Not an authenticated user') - } - }) - .catch(error => { - console.error('core auth error', error) - // TODO surface error? - authenticatedUsers.next(null) - }) - } - - // Initial authenticated user - updateAuthenticatedUser() - - secretStorage.onDidChange(async event => { - if (event.key === scretTokenKey) { - const token = await secretStorage.get(scretTokenKey) - if (token) { - updateAuthenticatedUser() - } - } - }) - - return authenticatedUsers -} diff --git a/client/vscode/src/backend/blobContent.ts b/client/vscode/src/backend/blobContent.ts deleted file mode 100644 index 6bb9668a45a..00000000000 --- a/client/vscode/src/backend/blobContent.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { gql } from '@sourcegraph/http-client' - -import type { BlobContentResult, BlobContentVariables } from '../graphql-operations' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -const blobContentQuery = gql` - query BlobContent($repository: String!, $revision: String!, $path: String!) { - repository(name: $repository) { - commit(rev: $revision) { - blob(path: $path) { - content - binary - byteSize - } - } - } - } -` - -export interface FileContents { - content: Uint8Array - isBinary: boolean - byteSize: number -} - -export async function getBlobContent(variables: BlobContentVariables): Promise { - const result = await requestGraphQLFromVSCode(blobContentQuery, variables) - - const blob = result.data?.repository?.commit?.blob - if (blob) { - return { - content: new TextEncoder().encode(blob.content), - isBinary: blob.binary, - byteSize: blob.byteSize, - } - } - return undefined -} diff --git a/client/vscode/src/backend/eventLogger.ts b/client/vscode/src/backend/eventLogger.ts deleted file mode 100644 index 81ccde9fa8d..00000000000 --- a/client/vscode/src/backend/eventLogger.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { EMPTY, Subject } from 'rxjs' -import { bufferTime, catchError, concatMap } from 'rxjs/operators' - -import { gql } from '@sourcegraph/http-client' - -import type { LogEventsResult, LogEventsVariables, Event } from '../graphql-operations' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -// Log events in batches. -const events = new Subject() - -events - .pipe( - bufferTime(1000), - concatMap(events => { - if (events.length > 0) { - return requestGraphQLFromVSCode(logEventsMutation, { - events, - }) - } - return EMPTY - }), - catchError(error => { - console.error('Error logging events:', error) - return [] - }) - ) - // eslint-disable-next-line rxjs/no-ignored-subscription - .subscribe() - -export const logEventsMutation = gql` - mutation LogEvents($events: [Event!]) { - logEvents(events: $events) { - alwaysNil - } - } -` - -/** - * Log a raw user action (used to allow site admins on a Sourcegraph instance - * to see a count of unique users on a daily, weekly, and monthly basis). - * - * When invoked on a non-Sourcegraph.com instance, this data is stored in the - * instance's database, and not sent to Sourcegraph.com. - */ - -export function logEvent(eventVariable: Event): void { - events.next(eventVariable) -} diff --git a/client/vscode/src/backend/fetch.ts b/client/vscode/src/backend/fetch.ts deleted file mode 100644 index f1bd1c9cca5..00000000000 --- a/client/vscode/src/backend/fetch.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type net from 'net' - -import type { ClientRequest, RequestOptions } from 'agent-base' -import HttpProxyAgent from 'http-proxy-agent' -import HttpsProxyAgent from 'https-proxy-agent' -import fetch, { Headers } from 'node-fetch' -import vscode from 'vscode' - -export { fetch, Headers } -export type { BodyInit, Response, HeadersInit } from 'node-fetch' - -interface HttpsProxyAgentInterface { - callback(req: ClientRequest, opts: RequestOptions): Promise -} - -type HttpsProxyAgentConstructor = new ( - options: string | { [key: string]: number | boolean | string | null } -) => HttpsProxyAgentInterface - -export function getProxyAgent(): ((url: URL | string) => HttpsProxyAgentInterface | undefined) | undefined { - const proxyProtocol = vscode.workspace.getConfiguration('sourcegraph').get('proxyProtocol') - const proxyHost = vscode.workspace.getConfiguration('sourcegraph').get('proxyHost') - const proxyPort = vscode.workspace.getConfiguration('sourcegraph').get('proxyPort') - const proxyPath = vscode.workspace.getConfiguration('sourcegraph').get('proxyPath') - - // Quit if we're in the browser—we don't need proxying there. - if (HttpsProxyAgent === null) { - return undefined - } - - if (proxyHost && !proxyPort) { - console.error('proxyHost is set but proxyPort is not. These two settings must be set together.') - return undefined - } - - if (proxyPort && !proxyHost) { - console.error('proxyPort is set but proxyHost is not. These two settings must be set together.') - return undefined - } - - if (proxyHost || proxyPort || proxyPath) { - return (url: URL | string) => { - const protocol = getProtocol(url) - if (protocol === undefined) { - return undefined - } - const ProxyAgent = (protocol === 'http' - ? HttpProxyAgent - : HttpsProxyAgent) as unknown as HttpsProxyAgentConstructor - return new ProxyAgent({ - protocol: proxyProtocol === 'http' || proxyProtocol === 'https' ? proxyProtocol : 'https', - ...(proxyHost ? { host: proxyHost } : null), - ...(proxyPort ? { port: proxyPort } : null), - ...(proxyPath ? { path: proxyPath } : null), - }) - } - } - - return undefined -} - -function getProtocol(url: URL | string): string | undefined { - if (typeof url === 'string') { - return url.startsWith('http:') ? 'http' : 'https' - } - - if (url instanceof URL) { - return url.protocol === 'http:' ? 'http' : 'https' - } - - return undefined -} diff --git a/client/vscode/src/backend/files.ts b/client/vscode/src/backend/files.ts deleted file mode 100644 index 21bd83ab5ad..00000000000 --- a/client/vscode/src/backend/files.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { gql } from '@sourcegraph/http-client' - -import type { FileNamesResult, FileNamesVariables } from '../graphql-operations' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -const fileNamesQuery = gql` - query FileNames($repository: String!, $revision: String!) { - repository(name: $repository) { - commit(rev: $revision) { - fileNames - } - } - } -` - -export async function getFiles(variables: FileNamesVariables): Promise { - const result = await requestGraphQLFromVSCode(fileNamesQuery, variables) - - if (result.data?.repository?.commit) { - return result.data.repository.commit.fileNames - } - - // Debt: surface error to users. - throw new Error(`Failed to fetch file names for ${variables.repository}@${variables.revision}`) -} diff --git a/client/vscode/src/backend/instanceVersion.ts b/client/vscode/src/backend/instanceVersion.ts deleted file mode 100644 index d019616d6d9..00000000000 --- a/client/vscode/src/backend/instanceVersion.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { noop } from 'lodash' -import { from, type Observable } from 'rxjs' -import { catchError, map } from 'rxjs/operators' - -import { dataOrThrowErrors, gql } from '@sourcegraph/http-client' -import { EventSource } from '@sourcegraph/shared/src/graphql-operations' - -import { displayWarning } from '../settings/displayWarnings' -import { INSTANCE_VERSION_NUMBER_KEY, type LocalStorageService } from '../settings/LocalStorageService' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -/** - * Gets the Sourcegraph instance version number via the GrapQL API. - * - * @returns An Observable that emits flattened Sourcegraph instance version number or undefined in case of an error: - * - regular instance version format: 3.38.2 - * - insiders version format: 134683_2022-03-02_5188fes0101 - */ -export const observeInstanceVersionNumber = ( - accessToken: string, - endpointURL: string -): Observable => - from(requestGraphQLFromVSCode(siteVersionQuery, {}, accessToken, endpointURL)).pipe( - map(dataOrThrowErrors), - map(data => data.site.productVersion), - catchError(error => { - console.error('Failed to get instance version from host:', error) - return [undefined] - }) - ) - -interface RegularVersion { - major: number - minor: number -} -type Version = RegularVersion | 'insiders' - -/** - * Parses the Sourcegraph instance version number. - * - * @returns Major and minor version numbers if it's a regular version, or `'insiders'` if it's an insiders version. - */ -const parseVersion = (version: string): Version => { - const versionParts = version.split('.') - if (versionParts.length === 3) { - return { - major: parseInt(versionParts[0], 10), - minor: parseInt(versionParts[1], 10), - } - } - return 'insiders' -} - -/** - * Checks if the Sourcegraph instance version is older than the given version. - * */ -export const isOlderThan = (instanceVersion: string, comparedVersion: RegularVersion): boolean => { - const version = parseVersion(instanceVersion) - return ( - version !== 'insiders' && - (version.major < comparedVersion.major || - (version.major === comparedVersion.major && version.minor < comparedVersion.minor)) - ) -} - -/** - * This function will return the EventSource Type based - * on the instance version - */ -export function initializeInstanceVersionNumber( - localStorageService: LocalStorageService, - initialAccessToken: string | undefined, - initialInstanceURL: string -): EventSource { - // Check only if a user is trying to connect to a private instance with a valid access token provided - if (initialAccessToken && initialAccessToken !== undefined) { - observeInstanceVersionNumber(initialAccessToken, initialInstanceURL) - .toPromise() - .then(async version => { - if (version) { - if (isOlderThan(version, { major: 3, minor: 32 })) { - displayWarning( - 'Your Sourcegraph instance version is not fully compatible with the Sourcegraph extension. Please ask your site admin to upgrade to version 3.32.0 or above. Read more about version support in our [troubleshooting docs](https://docs.sourcegraph.com/admin/how-to/troubleshoot-sg-extension#unsupported-features-by-sourcegraph-version).' - ).catch(() => {}) - } - await localStorageService.setValue(INSTANCE_VERSION_NUMBER_KEY, version) - } - }) - .catch(noop) // We handle potential errors in instanceVersionNumber observable - - const version = localStorageService.getValue(INSTANCE_VERSION_NUMBER_KEY) - // instances below 3.38.0 does not support EventSource.IDEEXTENSION and should fallback to BACKEND source - return version && isOlderThan(version, { major: 3, minor: 38 }) ? EventSource.BACKEND : EventSource.IDEEXTENSION - } - return EventSource.IDEEXTENSION -} - -const siteVersionQuery = gql` - query SiteProductVersion { - site { - productVersion - } - } -` -interface SiteVersionResult { - site: { - productVersion: string - } -} diff --git a/client/vscode/src/backend/node-fetch-fake-for-browser.ts b/client/vscode/src/backend/node-fetch-fake-for-browser.ts deleted file mode 100644 index 354dbe2afc4..00000000000 --- a/client/vscode/src/backend/node-fetch-fake-for-browser.ts +++ /dev/null @@ -1,7 +0,0 @@ -// This fake reuses the built-in "fetch" in browsers. - -// eslint-disable-next-line import/no-default-export -export default fetch -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const Headers = globalThis.Headers diff --git a/client/vscode/src/backend/proxy-agent-fake-for-browser.ts b/client/vscode/src/backend/proxy-agent-fake-for-browser.ts deleted file mode 100644 index 7e8e87183bf..00000000000 --- a/client/vscode/src/backend/proxy-agent-fake-for-browser.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This is a fake that we use instead of the read http-proxy-agent and https-proxy-agent modules in the browser. -// (We don't need proxying in the browser—browser settings will determine the proxying.) - -// Disabling ESLint because we want to match the library interfaces -// eslint-disable-next-line import/no-default-export -export default null diff --git a/client/vscode/src/backend/repositoryMetadata.ts b/client/vscode/src/backend/repositoryMetadata.ts deleted file mode 100644 index 403c758c0ef..00000000000 --- a/client/vscode/src/backend/repositoryMetadata.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { gql } from '@sourcegraph/http-client' - -import type { RepositoryMetadataResult, RepositoryMetadataVariables } from '../graphql-operations' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -const repositoryMetadataQuery = gql` - query RepositoryMetadata($repositoryName: String!) { - repositoryRedirect(name: $repositoryName) { - ... on Repository { - id - mirrorInfo { - cloneInProgress - cloneProgress - cloned - } - commit(rev: "") { - oid - abbreviatedOID - tree(path: "") { - url - } - } - defaultBranch { - abbrevName - } - } - ... on Redirect { - url - } - } - } -` - -export interface RepositoryMetadata { - repositoryId: string - defaultOID?: string - defaultAbbreviatedOID?: string - defaultBranch?: string -} - -export async function getRepositoryMetadata( - variables: RepositoryMetadataVariables -): Promise { - const result = await requestGraphQLFromVSCode( - repositoryMetadataQuery, - variables - ) - if (result.data?.repositoryRedirect?.__typename === 'Repository') { - return { - repositoryId: result.data.repositoryRedirect.id, - defaultOID: result.data.repositoryRedirect.commit?.oid, - defaultAbbreviatedOID: result.data.repositoryRedirect.commit?.abbreviatedOID, - defaultBranch: result.data.repositoryRedirect.defaultBranch?.abbrevName, - } - } - // v1 Debt: surface error to user. - return undefined -} diff --git a/client/vscode/src/backend/requestGraphQl.ts b/client/vscode/src/backend/requestGraphQl.ts deleted file mode 100644 index b5fb3982206..00000000000 --- a/client/vscode/src/backend/requestGraphQl.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { authentication } from 'vscode' - -import { asError } from '@sourcegraph/common' -import { checkOk, GRAPHQL_URI, type GraphQLResult, isHTTPAuthError } from '@sourcegraph/http-client' - -import { handleAccessTokenError } from '../settings/accessTokenSetting' -import { endpointRequestHeadersSetting, endpointSetting } from '../settings/endpointSetting' - -import { fetch, getProxyAgent, Headers, type HeadersInit } from './fetch' - -let invalidated = false - -/** - * To be called when Sourcegraph URL changes. - */ -export function invalidateClient(): void { - invalidated = true -} - -export const requestGraphQLFromVSCode = async ( - request: string, - variables: V, - overrideAccessToken?: string, - overrideSourcegraphURL?: string -): Promise> => { - if (invalidated) { - throw new Error( - 'Sourcegraph GraphQL Client has been invalidated due to instance URL change. Restart VS Code to fix.' - ) - } - const session = await authentication.getSession(endpointSetting(), [], { createIfNone: false }) - const sourcegraphURL = overrideSourcegraphURL || endpointSetting() - const accessToken = overrideAccessToken || session?.accessToken - const nameMatch = request.match(/^\s*(?:query|mutation)\s+(\w+)/) - const apiURL = `${GRAPHQL_URI}${nameMatch ? '?' + nameMatch[1] : ''}` - const customHeaders = endpointRequestHeadersSetting() - // create a headers container based on the custom headers configuration if there are any - // then add Access Token to request header, contributed by @ptxmac! - const headers = new Headers(customHeaders as HeadersInit) - headers.set('Content-Type', 'application/json') - if (accessToken) { - headers.set('Authorization', `token ${accessToken}`) - } - try { - const url = new URL(apiURL, sourcegraphURL).href - // Debt: intercepted requests in integration tests - // have 0 status codes, so don't check in test environment. - const checkFunction = process.env.IS_TEST ? (value: T): T => value : checkOk - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const options: any = { - agent: getProxyAgent(), - body: JSON.stringify({ - query: request, - variables, - }), - method: 'POST', - headers, - } - - const response = checkFunction(await fetch(url, options)) - // TODO request cancellation w/ VS Code cancellation tokens. - return (await response.json()) as GraphQLResult - } catch (error) { - // If `overrideAccessToken` is set, we're validating the token - // and errors will be displayed in the UI. - if (isHTTPAuthError(error) && !overrideAccessToken) { - handleAccessTokenError(accessToken || '', sourcegraphURL).then( - () => {}, - () => {} - ) - } - throw asError(error) - } -} diff --git a/client/vscode/src/backend/searchContexts.ts b/client/vscode/src/backend/searchContexts.ts deleted file mode 100644 index fa9e5b8c3cb..00000000000 --- a/client/vscode/src/backend/searchContexts.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { from, type Observable, of } from 'rxjs' -import { catchError } from 'rxjs/operators' -import type * as vscode from 'vscode' - -import type { GraphQLResult } from '@sourcegraph/http-client' -import { getAvailableSearchContextSpecOrFallback } from '@sourcegraph/shared/src/search' - -import { type LocalStorageService, SELECTED_SEARCH_CONTEXT_SPEC_KEY } from '../settings/LocalStorageService' -import type { VSCEStateMachine } from '../state' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -// Returns an Observable so webviews can easily block rendering on init. -export function initializeSearchContexts({ - localStorageService, - stateMachine, - context, -}: { - localStorageService: LocalStorageService - stateMachine: VSCEStateMachine - context: vscode.ExtensionContext -}): void { - const initialSearchContextSpec = localStorageService.getValue(SELECTED_SEARCH_CONTEXT_SPEC_KEY) - - const fallbackSpec = 'global' - - const subscription = getAvailableSearchContextSpecOrFallback({ - spec: initialSearchContextSpec || fallbackSpec, - fallbackSpec, - platformContext: { - requestGraphQL: ({ request, variables }) => - from(requestGraphQLFromVSCode(request, variables)) as Observable>, - }, - }) - .pipe( - catchError(error => { - console.error('Error validating search context spec:', error) - return of(fallbackSpec) - }) - ) - .subscribe(availableSearchContextSpecOrDefault => { - stateMachine.emit({ type: 'set_selected_search_context_spec', spec: availableSearchContextSpecOrDefault }) - }) - - context.subscriptions.push({ - dispose: () => subscription.unsubscribe(), - }) -} diff --git a/client/vscode/src/backend/sourcegraphSettings.ts b/client/vscode/src/backend/sourcegraphSettings.ts deleted file mode 100644 index 3f56622ddaf..00000000000 --- a/client/vscode/src/backend/sourcegraphSettings.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { type Observable, of, ReplaySubject, Subject } from 'rxjs' -import { catchError, map, switchMap, throttleTime } from 'rxjs/operators' -import type * as vscode from 'vscode' - -import { createAggregateError } from '@sourcegraph/common' -import { viewerSettingsQuery } from '@sourcegraph/shared/src/backend/settings' -import type { ViewerSettingsResult, ViewerSettingsVariables } from '@sourcegraph/shared/src/graphql-operations' -import { - EMPTY_SETTINGS_CASCADE, - gqlToCascade, - type Settings, - type SettingsCascadeOrError, -} from '@sourcegraph/shared/src/settings/settings' - -import { requestGraphQLFromVSCode } from './requestGraphQl' - -export function initializeSourcegraphSettings({ context }: { context: vscode.ExtensionContext }): { - settings: Observable> - refreshSettings: () => void -} { - const settings = new ReplaySubject>(1) - - const refreshes = new Subject() - - // Throttle refreshes for one hour. - const ONE_HOUR_MS = 60 * 60 * 1000 - - const subscription = refreshes - .pipe( - throttleTime(ONE_HOUR_MS, undefined, { leading: true, trailing: true }), - switchMap(() => - requestGraphQLFromVSCode(viewerSettingsQuery, {}) - ), - map(({ data, errors }) => { - if (!data?.viewerSettings) { - throw createAggregateError(errors) - } - - return gqlToCascade(data.viewerSettings) - }), - catchError(() => of(EMPTY_SETTINGS_CASCADE)) - ) - .subscribe(settingsCascade => { - settings.next(settingsCascade) - }) - context.subscriptions.push({ dispose: () => subscription.unsubscribe() }) - - // Initial settings - refreshes.next() - - return { - settings: settings.asObservable(), - refreshSettings: () => { - refreshes.next() - }, - } -} diff --git a/client/vscode/src/backend/streamSearch.ts b/client/vscode/src/backend/streamSearch.ts deleted file mode 100644 index 7d740f36309..00000000000 --- a/client/vscode/src/backend/streamSearch.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { of, type Subscription } from 'rxjs' -import { map, switchMap, throttleTime } from 'rxjs/operators' -import type * as vscode from 'vscode' - -import { SearchMode } from '@sourcegraph/shared/src/search' -import { appendContextFilter } from '@sourcegraph/shared/src/search/query/transformer' -import { aggregateStreamingSearch } from '@sourcegraph/shared/src/search/stream' - -import type { ExtensionCoreAPI } from '../contract' -import { SearchPatternType } from '../graphql-operations' -import type { VSCEStateMachine } from '../state' -import { focusSearchPanel } from '../webview/commands' - -import { isOlderThan, observeInstanceVersionNumber } from './instanceVersion' - -export function createStreamSearch({ - context, - stateMachine, - sourcegraphURL, - session, -}: { - context: vscode.ExtensionContext - stateMachine: VSCEStateMachine - sourcegraphURL: string - session: vscode.AuthenticationSession | undefined -}): ExtensionCoreAPI['streamSearch'] { - // Ensure only one search is active at a time - let previousSearchSubscription: Subscription | null - - context.subscriptions.push({ - dispose: () => { - previousSearchSubscription?.unsubscribe() - }, - }) - const token = session?.accessToken === undefined ? '' : session?.accessToken - const instanceVersionNumber = observeInstanceVersionNumber(token, sourcegraphURL) - - return function streamSearch(query, options) { - previousSearchSubscription?.unsubscribe() - - stateMachine.emit({ - type: 'submit_search_query', - submittedSearchQueryState: { - queryState: { query }, - searchCaseSensitivity: options.caseSensitive, - searchPatternType: options.patternType, - searchMode: options.searchMode || SearchMode.Precise, - }, - }) - // Focus search panel if not already focused - // (in case e.g. user initiates search from search sidebar when panel is hidden). - focusSearchPanel() - - previousSearchSubscription = instanceVersionNumber - .pipe( - map(version => { - let patternType = options.patternType - - if ( - patternType === SearchPatternType.standard && - version && - isOlderThan(version, { major: 3, minor: 43 }) - ) { - /** - * SearchPatternType.standard support was added in Sourcegraph v3.43.0. - * Use SearchPatternType.literal for earlier versions instead (it was the default before v3.43.0). - * See: https://docs.sourcegraph.com/CHANGELOG#3-43-0, https://github.com/sourcegraph/sourcegraph/pull/38141. - */ - patternType = SearchPatternType.literal - } - - return patternType - }), - switchMap(patternType => - aggregateStreamingSearch( - of(appendContextFilter(query, stateMachine.state.context.selectedSearchContextSpec)), - { ...options, patternType, sourcegraphURL } - ).pipe(throttleTime(500, undefined, { leading: true, trailing: true })) - ) - ) - .subscribe(searchResults => { - if (searchResults.state === 'error') { - // Pass only primitive copied values because Error object is not cloneable - const { name, message, stack } = searchResults.error - searchResults.error = { name, message, stack } - } - - stateMachine.emit({ type: 'received_search_results', searchResults }) - }) - } -} diff --git a/client/vscode/src/code-intel/SourcegraphDefinitionProvider.ts b/client/vscode/src/code-intel/SourcegraphDefinitionProvider.ts deleted file mode 100644 index 816dc4ef152..00000000000 --- a/client/vscode/src/code-intel/SourcegraphDefinitionProvider.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type * as Comlink from 'comlink' -import { EMPTY, of } from 'rxjs' -import { first, switchMap } from 'rxjs/operators' -import type * as vscode from 'vscode' - -import { finallyReleaseProxy, wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { makeRepoURI, parseRepoURI } from '@sourcegraph/shared/src/util/url' - -import type { SearchSidebarAPI } from '../contract' -import type { SourcegraphFileSystemProvider } from '../file-system/SourcegraphFileSystemProvider' - -export class SourcegraphDefinitionProvider implements vscode.DefinitionProvider { - constructor( - private readonly fs: SourcegraphFileSystemProvider, - private readonly sourcegraphExtensionHostAPI: Comlink.Remote - ) {} - public async provideDefinition( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const uri = this.fs.sourcegraphUri(document.uri) - const extensionHostUri = makeRepoURI({ - repoName: uri.repositoryName, - revision: uri.revision, - filePath: uri.path, - }) - - const definitions = wrapRemoteObservable( - this.sourcegraphExtensionHostAPI.getDefinition({ - textDocument: { - uri: extensionHostUri, - }, - position: { - line: position.line, - character: position.character, - }, - }) - ) - .pipe( - finallyReleaseProxy(), - switchMap(({ isLoading, result }) => { - if (isLoading) { - return EMPTY - } - - const locations = result.map(location => { - const uri = parseRepoURI(location.uri) - - return this.fs.toVscodeLocation({ - resource: { - path: uri.filePath ?? '', - repositoryName: uri.repoName, - revision: uri.commitID ?? uri.revision ?? '', - }, - range: location.range, - }) - }) - - return of(locations) - }), - first() - ) - .toPromise() - - token.onCancellationRequested(() => { - // Debt: manually create promise so we can cancel request. - }) - - return definitions - } -} diff --git a/client/vscode/src/code-intel/SourcegraphHoverProvider.ts b/client/vscode/src/code-intel/SourcegraphHoverProvider.ts deleted file mode 100644 index d399782a9d7..00000000000 --- a/client/vscode/src/code-intel/SourcegraphHoverProvider.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type * as Comlink from 'comlink' -import { EMPTY, of } from 'rxjs' -import { first, switchMap } from 'rxjs/operators' -import * as vscode from 'vscode' - -import { finallyReleaseProxy, wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { makeRepoURI } from '@sourcegraph/shared/src/util/url' - -import type { SearchSidebarAPI } from '../contract' -import type { SourcegraphFileSystemProvider } from '../file-system/SourcegraphFileSystemProvider' - -export class SourcegraphHoverProvider implements vscode.HoverProvider { - constructor( - private readonly fs: SourcegraphFileSystemProvider, - private readonly sourcegraphExtensionHostAPI: Comlink.Remote - ) {} - public async provideHover( - document: vscode.TextDocument, - position: vscode.Position, - token: vscode.CancellationToken - ): Promise { - const uri = this.fs.sourcegraphUri(document.uri) - const extensionHostUri = makeRepoURI({ - repoName: uri.repositoryName, - revision: uri.revision, - filePath: uri.path, - }) - - const definitions = wrapRemoteObservable( - this.sourcegraphExtensionHostAPI.getHover({ - textDocument: { - uri: extensionHostUri, - }, - position: { - line: position.line, - character: position.character, - }, - }) - ) - .pipe( - finallyReleaseProxy(), - switchMap(({ isLoading, result }) => { - if (isLoading) { - return EMPTY - } - - const prefix = - result?.aggregatedBadges?.reduce((prefix, badge) => { - if (badge.linkURL) { - return prefix + `[${badge.text}](${badge.linkURL})\n` - } - return prefix + `${badge.text}\n` - }, `![*](${sourcegraphLogoDataURI}) `) || '' - - return of({ - contents: [ - ...(result?.contents ?? []).map( - content => new vscode.MarkdownString(prefix + content.value) - ), - ], - }) - }), - first() - ) - .toPromise() - - token.onCancellationRequested(() => { - // Debt: manually create promise so we can cancel request. - }) - - return definitions - } -} - -const sourcegraphLogoDataURI = - 'data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE0IiB2aWV3Qm94PSIwIDAgNTIgNTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMwLjggNTEuOGMtMi44LjUtNS41LTEuMy02LTQuMUwxNy4yIDYuMmMtLjUtMi44IDEuMy01LjUgNC4xLTZzNS41IDEuMyA2IDQuMWw3LjYgNDEuNWMuNSAyLjgtMS40IDUuNS00LjEgNnoiIGZpbGw9IiNGRjU1NDMiLz48cGF0aCBkPSJNMTAuOSA0NC43QzkuMSA0NSA3LjMgNDQuNCA2IDQzYy0xLjgtMi4yLTEuNi01LjQuNi03LjJMMzguNyA4LjVjMi4yLTEuOCA1LjQtMS42IDcuMi42IDEuOCAyLjIgMS42IDUuNC0uNiA3LjJsLTMyIDI3LjNjLS43LjYtMS42IDEtMi40IDEuMXoiIGZpbGw9IiNBMTEyRkYiLz48cGF0aCBkPSJNNDYuOCAzOC4xYy0uOS4yLTEuOC4xLTIuNi0uMkw0LjQgMjMuOGMtMi43LTEtNC4xLTMuOS0zLjEtNi42IDEtMi43IDMuOS00LjEgNi42LTMuMWwzOS43IDE0LjFjMi43IDEgNC4xIDMuOSAzLjEgNi42LS42IDEuOC0yLjIgMy0zLjkgMy4zeiIgZmlsbD0iIzAwQ0JFQyIvPjwvc3ZnPg==' diff --git a/client/vscode/src/code-intel/SourcegraphReferenceProvider.ts b/client/vscode/src/code-intel/SourcegraphReferenceProvider.ts deleted file mode 100644 index e844cf74957..00000000000 --- a/client/vscode/src/code-intel/SourcegraphReferenceProvider.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type * as Comlink from 'comlink' -import { EMPTY, of } from 'rxjs' -import { debounceTime, first, switchMap } from 'rxjs/operators' -import type * as vscode from 'vscode' - -import { finallyReleaseProxy, wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { makeRepoURI, parseRepoURI } from '@sourcegraph/shared/src/util/url' - -import type { SearchSidebarAPI } from '../contract' -import type { SourcegraphFileSystemProvider } from '../file-system/SourcegraphFileSystemProvider' - -export class SourcegraphReferenceProvider implements vscode.ReferenceProvider { - constructor( - private readonly fs: SourcegraphFileSystemProvider, - private readonly sourcegraphExtensionHostAPI: Comlink.Remote - ) {} - public async provideReferences( - document: vscode.TextDocument, - position: vscode.Position, - referenceContext: vscode.ReferenceContext, - token: vscode.CancellationToken - ): Promise { - const uri = this.fs.sourcegraphUri(document.uri) - const extensionHostUri = makeRepoURI({ - repoName: uri.repositoryName, - revision: uri.revision, - filePath: uri.path, - }) - - const definitions = wrapRemoteObservable( - this.sourcegraphExtensionHostAPI.getReferences( - { - textDocument: { - uri: extensionHostUri, - }, - position: { - line: position.line, - character: position.character, - }, - }, - referenceContext - ) - ) - .pipe( - finallyReleaseProxy(), - switchMap(({ isLoading, result }) => { - if (isLoading) { - return EMPTY - } - - const locations = result.map(location => { - // Create a sourcegraph URI from this git URI (so we need both fromGitURI and toGitURI.)` - const uri = parseRepoURI(location.uri) - - return this.fs.toVscodeLocation({ - resource: { - path: uri.filePath ?? '', - repositoryName: uri.repoName, - revision: uri.commitID ?? uri.revision ?? '', - }, - range: location.range, - }) - }) - - return of(locations) - }), - debounceTime(1000), - first() - ) - .toPromise() - - token.onCancellationRequested(() => { - // Debt: manually create promise so we can cancel request. - }) - - return definitions - } -} diff --git a/client/vscode/src/code-intel/initialize.ts b/client/vscode/src/code-intel/initialize.ts deleted file mode 100644 index a0dd3558079..00000000000 --- a/client/vscode/src/code-intel/initialize.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type * as Comlink from 'comlink' -import vscode from 'vscode' - -import { makeRepoURI } from '@sourcegraph/shared/src/util/url' - -import type { SearchSidebarAPI } from '../contract' -import type { SourcegraphFileSystemProvider } from '../file-system/SourcegraphFileSystemProvider' - -import { toSourcegraphLanguage } from './languages' -import { SourcegraphDefinitionProvider } from './SourcegraphDefinitionProvider' -import { SourcegraphHoverProvider } from './SourcegraphHoverProvider' -import { SourcegraphReferenceProvider } from './SourcegraphReferenceProvider' - -export function initializeCodeIntel({ - context, - fs, - searchSidebarAPI, -}: { - context: vscode.ExtensionContext - fs: SourcegraphFileSystemProvider - searchSidebarAPI: Comlink.Remote -}): void { - // Register language-related features (they depend on Sourcegraph extensions). - context.subscriptions.push( - vscode.languages.registerDefinitionProvider( - { scheme: 'sourcegraph' }, - new SourcegraphDefinitionProvider(fs, searchSidebarAPI) - ) - ) - context.subscriptions.push( - vscode.languages.registerReferenceProvider( - { scheme: 'sourcegraph' }, - new SourcegraphReferenceProvider(fs, searchSidebarAPI) - ) - ) - context.subscriptions.push( - vscode.languages.registerHoverProvider( - { scheme: 'sourcegraph' }, - new SourcegraphHoverProvider(fs, searchSidebarAPI) - ) - ) - - // Debt: remove closed editors/documents - context.subscriptions.push( - vscode.window.onDidChangeActiveTextEditor(editor => { - // TODO store previously active editor -> SG viewer so we can remove on change - if (editor?.document.uri.scheme === 'sourcegraph') { - const text = editor.document.getText() - const sourcegraphUri = fs.sourcegraphUri(editor.document.uri) - const languageId = toSourcegraphLanguage(editor.document.languageId) - - const extensionHostUri = makeRepoURI({ - repoName: sourcegraphUri.repositoryName, - revision: sourcegraphUri.revision, - filePath: sourcegraphUri.path, - }) - - // We'll use the viewerId return value to remove viewer, get/set text decorations. - searchSidebarAPI - .addTextDocumentIfNotExists({ - text, - uri: extensionHostUri, - languageId, - }) - .then(() => - searchSidebarAPI.addViewerIfNotExists({ - type: 'CodeEditor', - resource: extensionHostUri, - selections: [], - isActive: true, - }) - ) - .catch(error => console.error(error)) - } - }) - ) -} diff --git a/client/vscode/src/code-intel/languages.ts b/client/vscode/src/code-intel/languages.ts deleted file mode 100644 index 6651660ab03..00000000000 --- a/client/vscode/src/code-intel/languages.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Converts VS Code language ID to Sourcegraph-compatible language ID - * if necessary (e.g. "typescriptreact" -> "typescript") - */ -export function toSourcegraphLanguage(vscodeLanguageID: string): string { - if (vscodeLanugageIDReplacements[vscodeLanguageID]) { - return vscodeLanugageIDReplacements[vscodeLanguageID]! - } - return vscodeLanguageID -} - -const vscodeLanugageIDReplacements: Record = { - typescriptreact: 'typescript', -} diff --git a/client/vscode/src/code-intel/location.ts b/client/vscode/src/code-intel/location.ts deleted file mode 100644 index 8bf581f47ce..00000000000 --- a/client/vscode/src/code-intel/location.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Range } from '@sourcegraph/extension-api-types' - -export interface LocationNode { - resource: { - path: string - repositoryName: string - revision: string - } - range?: Range -} diff --git a/client/vscode/src/commands/browserActionsNode.ts b/client/vscode/src/commands/browserActionsNode.ts deleted file mode 100644 index 2898f1354a7..00000000000 --- a/client/vscode/src/commands/browserActionsNode.ts +++ /dev/null @@ -1,68 +0,0 @@ -import vscode, { env } from 'vscode' - -import { getSourcegraphFileUrl, repoInfo } from './git-helpers' -import { generateSourcegraphBlobLink } from './initialize' - -/** - * Open active file in the browser on the configured Sourcegraph instance. - */ -export async function browserActions(action: string, logRedirectEvent: (uri: string) => void): Promise { - const editor = vscode.window.activeTextEditor - if (!editor) { - throw new Error('No active editor') - } - const uri = editor.document.uri - const instanceUrl = - vscode.workspace.getConfiguration('sourcegraph').get('url') || 'https://sourcegraph.com/' - let sourcegraphUrl = '' - // check if the current file is a remote file or not - if (uri.scheme === 'sourcegraph') { - sourcegraphUrl = generateSourcegraphBlobLink( - uri, - editor.selection.start.line, - editor.selection.start.character, - editor.selection.end.line, - editor.selection.end.character - ) - } else { - const repositoryInfo = await repoInfo(editor.document.uri.fsPath) - if (!repositoryInfo) { - await vscode.window.showErrorMessage('Cannot get git info for this repository.') - return - } - let { remoteURL, branch, fileRelative } = repositoryInfo - // construct sourcegraph url for current file - // Ask if user want to open file in HEAD instead if set current branch or default branch - // do not exist on Sourcegraph - if (!branch) { - const userChoice = await vscode.window.showInformationMessage( - 'Current (or default) branch does not exist on Sourcegraph. Publish your branch or continue to main branch.', - 'Continue to main', - 'Cancel' - ) - branch = userChoice === 'Continue to main' ? 'HEAD' : '' - if (!branch) { - return - } - } - sourcegraphUrl = getSourcegraphFileUrl(instanceUrl, remoteURL, branch, fileRelative, editor) - } - // Decode URI - const decodedUri = decodeURIComponent(sourcegraphUrl) - // Log redirect events - logRedirectEvent(sourcegraphUrl) - // Open in browser or Copy file link - switch (action) { - case 'open': { - await vscode.env.openExternal(vscode.Uri.parse(decodedUri)) - break - } - case 'copy': { - await env.clipboard.writeText(decodedUri).then(() => vscode.window.showInformationMessage('Copied!')) - break - } - default: { - throw new Error(`Failed to ${action} file link: invalid URL`) - } - } -} diff --git a/client/vscode/src/commands/browserActionsWeb.ts b/client/vscode/src/commands/browserActionsWeb.ts deleted file mode 100644 index 43df9a6e5d8..00000000000 --- a/client/vscode/src/commands/browserActionsWeb.ts +++ /dev/null @@ -1,47 +0,0 @@ -import vscode, { env } from 'vscode' - -import { generateSourcegraphBlobLink } from './initialize' - -/** - * browser Actions for Web does not run node modules to get git info - * Open active file in the browser on the configured Sourcegraph instance. - */ -export async function browserActions(action: string, logRedirectEvent: (uri: string) => void): Promise { - const editor = vscode.window.activeTextEditor - if (!editor) { - throw new Error('No active editor') - } - const uri = editor.document.uri - const instanceUrl = vscode.workspace.getConfiguration('sourcegraph').get('url') - let sourcegraphUrl = String() - // check if the current file is a remote file or not - if (uri.scheme === 'sourcegraph') { - sourcegraphUrl = generateSourcegraphBlobLink( - uri, - editor.selection.start.line, - editor.selection.start.character, - editor.selection.end.line, - editor.selection.end.character - ) - } else if (uri.authority === 'github' && typeof instanceUrl === 'string') { - // For remote github files - const repoInfo = uri.fsPath.split('/') - const repositoryName = `${repoInfo[1]}/${repoInfo[2]}` - const filePath = repoInfo.length === 4 ? repoInfo[3] : repoInfo.slice(3).join('/') - sourcegraphUrl = `${instanceUrl}github.com/${repositoryName}/-/blob/${filePath || ''}` - } else { - await vscode.window.showInformationMessage('Non-Remote files are not supported on VS Code Web currently') - } - // Log redirect events - logRedirectEvent(sourcegraphUrl) - - // Open in browser or Copy file link - if (action === 'open' && sourcegraphUrl) { - await vscode.env.openExternal(vscode.Uri.parse(sourcegraphUrl)) - } else if (action === 'copy' && sourcegraphUrl) { - const decodedUri = decodeURIComponent(sourcegraphUrl) - await env.clipboard.writeText(decodedUri).then(() => vscode.window.showInformationMessage('Copied!')) - } else { - throw new Error(`Failed to ${action} file link: invalid URL`) - } -} diff --git a/client/vscode/src/commands/git-helpers.ts b/client/vscode/src/commands/git-helpers.ts deleted file mode 100644 index 5e0017abf30..00000000000 --- a/client/vscode/src/commands/git-helpers.ts +++ /dev/null @@ -1,268 +0,0 @@ -import * as path from 'path' - -import execa from 'execa' -import vscode, { type TextEditor } from 'vscode' - -import { gql } from '@sourcegraph/http-client' - -import { version } from '../../package.json' -import { requestGraphQLFromVSCode } from '../backend/requestGraphQl' -import { log } from '../log' - -interface RepositoryInfo extends Branch, RemoteName { - /** Git repository remote URL */ - remoteURL: string - - /** File path relative to the repository root */ - fileRelative: string -} - -export type GitHelpers = typeof gitHelpers - -export interface RemoteName { - /** - * Remote name of the upstream repository, - * or the first found remote name if no upstream is found - */ - remoteName: string -} - -export interface Branch { - /** - * Remote branch name, or 'HEAD' if it isn't found because - * e.g. detached HEAD state, upstream branch points to a local branch - */ - branch: string -} - -/** - * Returns the Git repository remote URL, the current branch, and the file path - * relative to the repository root. Returns undefined if no remote is found - */ -export async function repoInfo(filePath: string): Promise { - try { - // Determine repository root directory. - const fileDirectory = path.dirname(filePath) - const repoRoot = await gitHelpers.rootDirectory(fileDirectory) - // Determine file path relative to repository root, then replace slashes - // as \\ does not work in Sourcegraphl links - const fileRelative = filePath.slice(repoRoot.length + 1).replaceAll('\\', '/') - let { branch, remoteName } = await gitRemoteNameAndBranch(repoRoot, gitHelpers, log) - const remoteURL = await gitRemoteUrlWithReplacements(repoRoot, remoteName, gitHelpers, log) - // check if the default branch or branch exist remotely - branch = (await isOnSourcegraph(remoteURL, getDefaultBranch() || branch)) ? getDefaultBranch() || branch : '' - return { remoteURL, branch, fileRelative, remoteName } - } catch { - return undefined - } -} - -export async function gitRemoteNameAndBranch( - repoDirectory: string, - git: Pick, - log?: { - appendLine: (value: string) => void - } -): Promise { - let remoteName: string | undefined - - // Used to determine which part of upstreamAndBranch is the remote name, or as fallback if no upstream is set - const remotes = await git.remotes(repoDirectory) - const branch = await git.branch(repoDirectory) - - try { - const upstreamAndBranch = await git.upstreamAndBranch(repoDirectory) - // Subtract $BRANCH_NAME from $UPSTREAM_REMOTE/$BRANCH_NAME. - // We can't just split on the delineating `/`, since refnames can include `/`: - // https://sourcegraph.com/github.com/git/git@454cb6bd52a4de614a3633e4f547af03d5c3b640/-/blob/refs.c#L52-67 - - // Example: - // stdout: remote/two/tj/feature - // remoteName: remote/two, branch: tj/feature - - const branchPosition = upstreamAndBranch.lastIndexOf(branch) - const maybeRemote = upstreamAndBranch.slice(0, branchPosition - 1) - if (branchPosition !== -1 && maybeRemote) { - remoteName = maybeRemote - } - } catch { - // noop. upstream may not be set - } - - // If we cannot find the remote name from the branch name, we use the remote list in this order: - // - "upstream" - // - "origin" - // - the first remote alphabetically - if (!remoteName && remotes.length > 0) { - if (remotes.includes('upstream')) { - remoteName = 'upstream' - } else if (remotes.includes('origin')) { - remoteName = 'origin' - } else { - log?.appendLine(`no upstream found, using first git remote: ${remotes[0]}`) - remoteName = remotes[0] - } - } - - // Throw if a remote still isn't found - if (!remoteName) { - throw new Error('no configured git remotes') - } - - return { remoteName, branch } -} - -export const gitHelpers = { - /** - * Returns the repository root directory for any directory within the - * repository. - */ - async rootDirectory(repoDirectory: string): Promise { - const { stdout } = await execa('git', ['rev-parse', '--show-toplevel'], { cwd: repoDirectory }) - return stdout - }, - - /** - * Returns the names of all git remotes, e.g. ["origin", "foobar"] - */ - async remotes(repoDirectory: string): Promise { - const { stdout } = await execa('git', ['remote'], { cwd: repoDirectory }) - return stdout.split('\n') - }, - - /** - * Returns the remote URL for the given remote name. - * e.g. `origin` -> `git@github.com:foo/bar` - */ - async remoteUrl(remoteName: string, repoDirectory: string): Promise { - const { stdout } = await execa('git', ['remote', 'get-url', remoteName], { cwd: repoDirectory }) - return stdout - }, - - /** - * Returns either the current branch name of the repository OR in all - * other cases (e.g. detached HEAD state), it returns "HEAD". - */ - async branch(repoDirectory: string): Promise { - const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoDirectory }) - return stdout - }, - - /** - * Returns a string in the format $UPSTREAM_REMOTE/$BRANCH_NAME, e.g. "origin/branch-name", throws if not found - */ - async upstreamAndBranch(repoDirectory: string): Promise { - const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD@{upstream}'], { cwd: repoDirectory }) - return stdout - }, -} - -/** - * Returns the remote URL for the given remote name with remote URL replacements. - * e.g. `origin` -> `git@github.com:foo/bar` - */ -export async function gitRemoteUrlWithReplacements( - repoDirectory: string, - remoteName: string, - gitHelpers: Pick, - log?: { appendLine: (value: string) => void } -): Promise { - let stdout = await gitHelpers.remoteUrl(remoteName, repoDirectory) - const replacementsList = getRemoteUrlReplacements() - - const stdoutBefore = stdout - - for (const replacement in replacementsList) { - if (typeof replacement === 'string') { - stdout = stdout.replace(replacement, replacementsList[replacement]) - } - } - - log?.appendLine(`${stdoutBefore} became ${stdout}`) - return stdout -} - -/** - * Uses editor endpoint to construct sourcegraph file URL - */ -export function getSourcegraphFileUrl( - SourcegraphUrl: string, - remoteURL: string, - branch: string, - fileRelative: string, - editor: TextEditor -): string { - const parameters = { - remote_url: encodeURIComponent(remoteURL), - branch: encodeURIComponent(branch), - file: encodeURIComponent(fileRelative), - editor: encodeURIComponent('VSCode'), - version: encodeURIComponent(version), - start_row: encodeURIComponent(String(editor.selection.start.line)), - start_col: encodeURIComponent(String(editor.selection.start.character)), - end_row: encodeURIComponent(String(editor.selection.end.line)), - end_col: encodeURIComponent(String(editor.selection.end.character)), - } - const uri = new URL('/-/editor', SourcegraphUrl) - const parametersString = new URLSearchParams({ ...parameters }).toString() - uri.search = parametersString - return uri.href -} - -function getRemoteUrlReplacements(): Record { - // has default value - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const replacements = vscode.workspace - .getConfiguration('sourcegraph') - .get>('remoteUrlReplacements')! - return replacements -} - -export function getDefaultBranch(): string { - // has default value - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return vscode.workspace.getConfiguration('sourcegraph').get('defaultBranch')! -} - -/** - * Check if branch exists on Sourcegraph instance - * Return 'HEAD' if it does not exists remotely - */ -export async function isOnSourcegraph(remoteURL: string, currentBranch: string): Promise { - const repoNameRegex = /(\w+(:\/\/|@))(.+@)*([\w.]+)(:?)(\d+){0,1}\/*(.*)(\.git)(\/)?/ - const repoNameRegexMatches = remoteURL.match(repoNameRegex) - const repoName = - repoNameRegexMatches?.[4] && repoNameRegexMatches?.[7] - ? repoNameRegexMatches?.[4] + '/' + repoNameRegexMatches?.[7] - : remoteURL.replace('git@', '').replace('https://', '').replace('.git', '').replace(':', '/') - const isOnSourcegraph = await requestGraphQLFromVSCode(checkBranchQuery, { - repoName, - branchName: currentBranch, - }) - .then(response => response.data?.repository.branches.nodes) - .then(nodes => nodes?.filter(branch => branch.name.replace('refs/heads/', '') === currentBranch)) - .then(filtered => filtered?.length === 1) - .catch(error => console.error(error)) - console.log(isOnSourcegraph) - return isOnSourcegraph || false -} - -const checkBranchQuery = gql` - query CheckBranch($repoName: String!, $branchName: String) { - repository(name: $repoName) { - branches(query: $branchName) { - nodes { - name - } - } - } - } -` - -interface CheckBranchResult { - repository: { - branches: { - nodes: { name: string }[] - } - } -} diff --git a/client/vscode/src/commands/initialize.ts b/client/vscode/src/commands/initialize.ts deleted file mode 100644 index 5827ed1480a..00000000000 --- a/client/vscode/src/commands/initialize.ts +++ /dev/null @@ -1,86 +0,0 @@ -import vscode from 'vscode' - -import type { EventSource } from '@sourcegraph/shared/src/graphql-operations' - -import { version } from '../../package.json' -import { logEvent } from '../backend/eventLogger' -import { SourcegraphUri } from '../file-system/SourcegraphUri' -import { type LocalStorageService, ANONYMOUS_USER_ID_KEY } from '../settings/LocalStorageService' - -import { browserActions } from './browserActionsNode' - -export function initializeCodeSharingCommands( - context: vscode.ExtensionContext, - eventSourceType: EventSource, - localStorageService: LocalStorageService -): void { - // Open local file or remote Sourcegraph file in browser - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.openInBrowser', async () => { - await browserActions('open', logRedirectEvent) - }) - ) - // Copy Sourcegraph link to file - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.copyFileLink', async () => { - await browserActions('copy', logRedirectEvent) - }) - ) - // Search Selected Text in Sourcegraph Search Tab - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.selectionSearchWeb', async () => { - const instanceUrl = - vscode.workspace.getConfiguration('sourcegraph').get('url') || 'https://sourcegraph.com' - const editor = vscode.window.activeTextEditor - const selectedQuery = editor?.document.getText(editor.selection) - if (!editor || !selectedQuery) { - throw new Error('No selection detected') - } - const uri = `${instanceUrl}/search?q=context:global+${encodeURIComponent( - selectedQuery - )}&patternType=literal` - await vscode.env.openExternal(vscode.Uri.parse(uri)) - }) - ) - // Log Redirect Event - function logRedirectEvent(sourcegraphUrl: string): void { - const userEventVariables = { - event: 'IDERedirected', - userCookieID: localStorageService.getValue(ANONYMOUS_USER_ID_KEY), - referrer: 'VSCE', - url: sourcegraphUrl, - source: eventSourceType, - argument: JSON.stringify({ editor: 'vscode', version }), - } - logEvent(userEventVariables) - } -} - -/** - * Generates a link to a blob on a Sourcegraph instance. - * - * @param uri - The VSCode URI of the blob. - * @param startLine - The zero-based line value. - * @param startChar - The zero-based character value. - * @param endLine - The zero-based line value. - * @param endChar - The zero-based character value. - */ -export function generateSourcegraphBlobLink( - uri: vscode.Uri, - startLine: number, - startChar: number, - endLine: number, - endChar: number -): string { - const instanceUrl = new URL( - vscode.workspace.getConfiguration('sourcegraph').get('url') || 'https://sourcegraph.com' - ) - // Using SourcegraphUri.parse to properly decode repo revision - const decodedUri = SourcegraphUri.parse(uri.toString()) - const finalUri = new URL(decodedUri.uri) - // Sourcegraph expects 1-based line and character values - finalUri.search = `L${encodeURIComponent(String(startLine + 1))}:${encodeURIComponent( - String(startChar + 1) - )}-${encodeURIComponent(String(endLine + 1))}:${encodeURIComponent(String(endChar + 1))}` - return finalUri.href.replace(finalUri.protocol, instanceUrl.protocol) -} diff --git a/client/vscode/src/common/links.ts b/client/vscode/src/common/links.ts deleted file mode 100644 index a565be90717..00000000000 --- a/client/vscode/src/common/links.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * All Sourcegraph Cloud related links - * MAIN - */ -export const VSCE_LINK_DOTCOM = 'https://sourcegraph.com' -export const VSCE_LINK_TOKEN_CALLBACK = - 'https://sourcegraph.com/sign-in?returnTo=user/settings/tokens/new/callback?requestFrom=VSCEAUTH' -export const VSCE_LINK_TOKEN_CALLBACK_TEST = - 'https://sourcegraph.test:3443/sign-in?returnTo=user/settings/tokens/new/callback?requestFrom=VSCEAUTH' -/** - * UNRELEASED FEATURE - * Token Callback Page - */ -// const VSCE_CALLBACK_CODE = 'VSCEAUTH' -// const VSCE_LINK_PARAMS_TOKEN_REDIRECT = { -// returnTo: `user/settings/tokens/new/callback?requestFrom=${VSCE_CALLBACK_CODE}`, -// } -/** - * Params - */ -export const VSCE_SIDEBAR_PARAMS = '?utm_medium=VSCODE&utm_source=sidebar&utm_campaign=vsce-sign-up&utm_content=sign-up' -const VSCE_LINK_PARAMS_TOKEN_REDIRECT = { - returnTo: 'user/settings/tokens/new', -} -const VSCE_LINK_PARAMS_EDITOR = { editor: 'vscode' } -// UTM for Sidebar actions -const VSCE_LINK_PARAMS_UTM_SIDEBAR = { - utm_campaign: 'vsce-sign-up', - utm_medium: 'VSCODE', - utm_source: 'sidebar', - utm_content: 'sign-up', -} -// MISC -export const VSCE_LINK_MARKETPLACE = 'https://marketplace.visualstudio.com/items?itemName=sourcegraph.sourcegraph' -export const VSCE_LINK_USER_DOCS = - 'https://docs.sourcegraph.com/cli/how-tos/creating_an_access_token' + VSCE_SIDEBAR_PARAMS -export const VSCE_LINK_FEEDBACK = 'https://github.com/sourcegraph/sourcegraph/discussions/categories/feedback' -export const VSCE_LINK_ISSUES = - 'https://github.com/sourcegraph/sourcegraph/issues/new?labels=team/integrations,vscode-extension&title=VSCode+Bug+report:+&projects=Integrations%20Project%20Board' -export const VSCE_LINK_TROUBLESHOOT = - 'https://docs.sourcegraph.com/admin/how-to/troubleshoot-sg-extension#vs-code-extension' -export const VSCE_SG_LOGOMARK_LIGHT = - 'https://raw.githubusercontent.com/sourcegraph/sourcegraph/fd431743e811ba756490e5e7bd88aa2362b6453e/client/vscode/images/logomark_light.svg' -export const VSCE_SG_LOGOMARK_DARK = - 'https://raw.githubusercontent.com/sourcegraph/sourcegraph/2636c64c9f323d78281a68dd4bdf432d9a97835a/client/vscode/images/logomark_dark.svg' -export const VSCE_LINK_SIGNUP = 'https://about.sourcegraph.com/get-started/cloud' + VSCE_SIDEBAR_PARAMS - -// Generate sign-in and sign-up links using the above params -export const VSCE_LINK_AUTH = (mode: 'sign-in' | 'sign-up'): string => { - const uri = new URL(VSCE_LINK_DOTCOM) - const parameters = new URLSearchParams({ - ...VSCE_LINK_PARAMS_UTM_SIDEBAR, - ...VSCE_LINK_PARAMS_EDITOR, - ...VSCE_LINK_PARAMS_TOKEN_REDIRECT, - }).toString() - uri.pathname = mode - uri.search = parameters - return uri.href -} diff --git a/client/vscode/src/contract.ts b/client/vscode/src/contract.ts deleted file mode 100644 index 5e933643397..00000000000 --- a/client/vscode/src/contract.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { GraphQLResult } from '@sourcegraph/http-client' -import type { FlatExtensionHostAPI } from '@sourcegraph/shared/src/api/contract' -import type { ProxySubscribable } from '@sourcegraph/shared/src/api/extension/api/common' -import type { ViewerData, ViewerId } from '@sourcegraph/shared/src/api/viewerTypes' -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import type { EventSource } from '@sourcegraph/shared/src/graphql-operations' -import type { SearchMatch, StreamSearchOptions } from '@sourcegraph/shared/src/search/stream' -import type { SettingsCascadeOrError } from '@sourcegraph/shared/src/settings/settings' - -import type { Event } from './graphql-operations' -import type { VSCEQueryState, VSCEState, VSCEStateMachine } from './state' - -export interface ExtensionCoreAPI { - /** For search panel webview to signal that it is ready for messages. */ - panelInitialized: (panelId: string) => void - - requestGraphQL: ( - request: string, - variables: any, - overrideAccessToken?: string, - overrideSourcegraphURL?: string - ) => Promise> - observeSourcegraphSettings: () => ProxySubscribable - getAuthenticatedUser: () => ProxySubscribable - /** Endpoint settings */ - getInstanceURL: () => ProxySubscribable - getAccessToken: Promise - removeAccessToken: () => Promise - setEndpointUri: (accessToken: string, uri: string) => Promise - /** - * Observe search box query state. - * Used to send current query from panel to sidebar. - * - * v1 Debt: Transient query state isn't stored in state machine for performance - * as it would lead to re-rendering the whole search panel on each keystroke. - * Implement selector system w/ key path for state machine. Alternatively, - * aggressively memoize top-level "View" components (i.e. don't just take whole state as prop). - */ - observePanelQueryState: () => ProxySubscribable - /** State Management*/ - observeState: () => ProxySubscribable - emit: VSCEStateMachine['emit'] - /** Opens a remote file given a serialized SourcegraphUri */ - openSourcegraphFile: (uri: string) => Promise - openLink: (uri: string) => Promise - copyLink: (uri: string) => Promise - reloadWindow: () => void - focusSearchPanel: () => void - /** Cancels previous search when called. */ - streamSearch: (query: string, options: StreamSearchOptions) => void - fetchStreamSuggestions: (query: string, sourcegraphURL: string) => ProxySubscribable - setSelectedSearchContextSpec: (spec: string) => void - /** Used to send current query from panel to sidebar. */ - setSidebarQueryState: (queryState: VSCEQueryState) => void - /** Local Storage Item */ - getLocalStorageItem: (key: string) => string - setLocalStorageItem: (key: string, value: string) => Promise - /** For Telemetry Service / logging */ - logEvents: (variables: Event) => void - /** Get EventSource Type to use based on instance version */ - getEventSource: EventSource - /** Get EventSource Type to use based on instance version */ - getEditorTheme: string -} - -export interface SearchPanelAPI { - ping: () => ProxySubscribable<'pong'> - - focusSearchBox: () => void -} - -export interface SearchSidebarAPI - extends Pick { - ping: () => ProxySubscribable<'pong'> - - addViewerIfNotExists: (viewer: ViewerData) => Promise -} - -export interface HelpSidebarAPI {} diff --git a/client/vscode/src/extension.ts b/client/vscode/src/extension.ts deleted file mode 100644 index dc586785ec7..00000000000 --- a/client/vscode/src/extension.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { of, ReplaySubject } from 'rxjs' -import vscode from 'vscode' - -import { proxySubscribable } from '@sourcegraph/shared/src/api/extension/api/common' -import polyfillEventSource from '@sourcegraph/shared/src/polyfills/vendor/eventSource' -import { fetchStreamSuggestions } from '@sourcegraph/shared/src/search/suggestions' - -import { observeAuthenticatedUser } from './backend/authenticatedUser' -import { logEvent } from './backend/eventLogger' -import { getProxyAgent } from './backend/fetch' -import { initializeInstanceVersionNumber } from './backend/instanceVersion' -import { requestGraphQLFromVSCode } from './backend/requestGraphQl' -import { initializeSearchContexts } from './backend/searchContexts' -import { initializeSourcegraphSettings } from './backend/sourcegraphSettings' -import { createStreamSearch } from './backend/streamSearch' -import { initializeCodeSharingCommands } from './commands/initialize' -import type { ExtensionCoreAPI } from './contract' -import { openSourcegraphUriCommand } from './file-system/commands' -import { initializeSourcegraphFileSystem } from './file-system/initialize' -import { SourcegraphUri } from './file-system/SourcegraphUri' -import type { Event } from './graphql-operations' -import { accessTokenSetting, processOldToken } from './settings/accessTokenSetting' -import { endpointRequestHeadersSetting, endpointSetting } from './settings/endpointSetting' -import { invalidateContextOnSettingsChange } from './settings/invalidation' -import { LocalStorageService, SELECTED_SEARCH_CONTEXT_SPEC_KEY } from './settings/LocalStorageService' -import { watchUninstall } from './settings/uninstall' -import { createVSCEStateMachine, type VSCEQueryState } from './state' -import { copySourcegraphLinks, focusSearchPanel, openSourcegraphLinks, registerWebviews } from './webview/commands' -import { scretTokenKey, SourcegraphAuthActions, SourcegraphAuthProvider } from './webview/platform/AuthProvider' - -/** - * See CONTRIBUTING docs for the Architecture Diagram - */ -export async function activate(context: vscode.ExtensionContext): Promise { - const secretStorage = context.secrets - // Register SourcegraphAuthProvider - context.subscriptions.push( - vscode.authentication.registerAuthenticationProvider( - endpointSetting(), - scretTokenKey, - new SourcegraphAuthProvider(secretStorage) - ) - ) - await processOldToken(secretStorage) - const initialInstanceURL = endpointSetting() - const initialAccessToken = await secretStorage.get(scretTokenKey) - const createIfNone = initialAccessToken ? { createIfNone: true } : { createIfNone: false } - const session = await vscode.authentication.getSession(endpointSetting(), [], createIfNone) - const authenticatedUser = observeAuthenticatedUser(secretStorage) - const localStorageService = new LocalStorageService(context.globalState) - const stateMachine = createVSCEStateMachine({ localStorageService }) - invalidateContextOnSettingsChange({ context, stateMachine }) - initializeSearchContexts({ localStorageService, stateMachine, context }) - const sourcegraphSettings = initializeSourcegraphSettings({ context }) - const editorTheme = vscode.ColorThemeKind[vscode.window.activeColorTheme.kind] - const eventSourceType = initializeInstanceVersionNumber(localStorageService, initialAccessToken, initialInstanceURL) - // Sets global `EventSource` for Node, which is required for streaming search. - // Add custom headers to `EventSource` Authorization header when provided - const customHeaders = endpointRequestHeadersSetting() - polyfillEventSource( - initialAccessToken ? { Authorization: `token ${initialAccessToken}`, ...customHeaders } : {}, - getProxyAgent() - ) - - // For search panel webview to signal that it is ready for messages. - // Replay subject with large buffer size just in case panels are opened in quick succession. - const initializedPanelIDs = new ReplaySubject(7) - // Used to observe search box query state from sidebar - const sidebarQueryStates = new ReplaySubject(1) - // Use for file tree panel - const { fs } = initializeSourcegraphFileSystem({ context, initialInstanceURL }) - // Use api endpoint for stream search - const streamSearch = createStreamSearch({ - context, - stateMachine, - sourcegraphURL: `${initialInstanceURL}/.api`, - session, - }) - const authActions = new SourcegraphAuthActions(secretStorage) - const extensionCoreAPI: ExtensionCoreAPI = { - panelInitialized: panelId => initializedPanelIDs.next(panelId), - observeState: () => proxySubscribable(stateMachine.observeState()), - observePanelQueryState: () => proxySubscribable(sidebarQueryStates.asObservable()), - emit: event => stateMachine.emit(event), - requestGraphQL: requestGraphQLFromVSCode, - observeSourcegraphSettings: () => proxySubscribable(sourcegraphSettings.settings), - // Debt: converting Promises into Observables for ease of use with - // `useObservable` hook. Add `usePromise`s hook to fix. - getAuthenticatedUser: () => proxySubscribable(authenticatedUser), - getInstanceURL: () => proxySubscribable(of(initialInstanceURL)), - openSourcegraphFile: (uri: string) => openSourcegraphUriCommand(fs, SourcegraphUri.parse(uri)), - openLink: uri => openSourcegraphLinks(uri), - copyLink: uri => copySourcegraphLinks(uri), - getAccessToken: accessTokenSetting(context.secrets), - removeAccessToken: () => authActions.logout(), - setEndpointUri: (accessToken, uri) => authActions.login(accessToken, uri), - reloadWindow: () => vscode.commands.executeCommand('workbench.action.reloadWindow'), - focusSearchPanel, - streamSearch, - fetchStreamSuggestions: (query, sourcegraphURL) => - // Use api endpoint for stream search - proxySubscribable(fetchStreamSuggestions(query, `${sourcegraphURL}/.api`)), - setSelectedSearchContextSpec: spec => { - stateMachine.emit({ type: 'set_selected_search_context_spec', spec }) - return localStorageService.setValue(SELECTED_SEARCH_CONTEXT_SPEC_KEY, spec) - }, - setSidebarQueryState: sidebarQueryState => sidebarQueryStates.next(sidebarQueryState), - getLocalStorageItem: key => localStorageService.getValue(key), - setLocalStorageItem: (key: string, value: string) => localStorageService.setValue(key, value), - logEvents: (variables: Event) => logEvent(variables), - getEventSource: eventSourceType, - getEditorTheme: editorTheme, - } - // Also initializes code intel. - registerWebviews({ - context, - extensionCoreAPI, - initializedPanelIDs, - sourcegraphSettings, - fs, - instanceURL: initialInstanceURL, - }) - initializeCodeSharingCommands(context, eventSourceType, localStorageService) - // Watch for uninstall to log uninstall event - watchUninstall(eventSourceType, localStorageService) -} diff --git a/client/vscode/src/file-system/FileTree.ts b/client/vscode/src/file-system/FileTree.ts deleted file mode 100644 index 903eb598b96..00000000000 --- a/client/vscode/src/file-system/FileTree.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { SourcegraphUri } from './SourcegraphUri' - -/** - * Helper class to represent a flat list of relative file paths (type `string[]`) as a hierarchical file tree. - */ -export class FileTree { - constructor(public readonly uri: SourcegraphUri, public readonly files: string[]) { - files.sort() - } - - public toString(): string { - return `FileTree(${this.uri.uri}, files.length=${this.files.length})` - } - - public directChildren(directory: string): string[] { - return this.directChildrenInternal(directory, true) - } - - private directChildrenInternal(directory: string, allowRecursion: boolean): string[] { - const depth = this.depth(directory) - const directFiles = new Set() - const directDirectories = new Set() - const isRoot = directory === '' - if (!isRoot && !directory.endsWith('/')) { - directory = directory + '/' - } - let index = this.binarySearchDirectoryStart(directory) - while (index < this.files.length) { - const startIndex = index - const file = this.files[index] - if (file === '') { - index++ - continue - } - if (file.startsWith(directory)) { - const fileDepth = this.depth(file) - const isFile = isRoot ? fileDepth === 0 : fileDepth === depth + 1 - let path = isFile ? file : file.slice(0, file.indexOf('/', directory.length)) - let nestedChildren = allowRecursion && !isFile ? this.directChildrenInternal(path, false) : [] - while (allowRecursion && nestedChildren.length === 1) { - const child = SourcegraphUri.parse(nestedChildren[0]) - if (child.isDirectory()) { - path = child.path || '' - nestedChildren = this.directChildrenInternal(path, false) - } else { - break - } - } - const uri = SourcegraphUri.fromParts(this.uri.host, this.uri.repositoryName, { - revision: this.uri.revision, - path, - isDirectory: !isFile, - }).uri - if (isFile) { - directFiles.add(uri) - } else { - index = this.binarySearchDirectoryEnd(path + '/', index + 1) - directDirectories.add(uri) - } - } - if (index === startIndex) { - index++ - } - } - return [...directDirectories, ...directFiles] - } - - private binarySearchDirectoryStart(directory: string): number { - if (directory === '') { - return 0 - } - return this.binarySearch( - { low: 0, high: this.files.length }, - midpoint => this.files[midpoint].localeCompare(directory) > 0 - ) - } - - private binarySearchDirectoryEnd(directory: string, low: number): number { - while (low < this.files.length && this.files[low].localeCompare(directory) <= 0) { - low++ - } - return this.binarySearch( - { low, high: this.files.length }, - midpoint => !this.files[midpoint].startsWith(directory) - ) - } - - private binarySearch({ low, high }: SearchRange, isGreater: (midpoint: number) => boolean): number { - while (low < high) { - const midpoint = Math.floor(low + (high - low) / 2) - if (isGreater(midpoint)) { - high = midpoint - } else { - low = midpoint + 1 - } - } - return high - } - - private depth(path: string): number { - let result = 0 - for (const char of path) { - if (char === '/') { - result += 1 - } - } - return result - } -} - -interface SearchRange { - low: number - high: number -} diff --git a/client/vscode/src/file-system/FilesTreeDataProvider.ts b/client/vscode/src/file-system/FilesTreeDataProvider.ts deleted file mode 100644 index 586675acbe5..00000000000 --- a/client/vscode/src/file-system/FilesTreeDataProvider.ts +++ /dev/null @@ -1,248 +0,0 @@ -import * as vscode from 'vscode' - -import { log } from '../log' - -import type { SourcegraphFileSystemProvider } from './SourcegraphFileSystemProvider' -import { SourcegraphUri } from './SourcegraphUri' - -export class FilesTreeDataProvider implements vscode.TreeDataProvider { - constructor(public readonly fs: SourcegraphFileSystemProvider) { - fs.onDidDownloadRepositoryFilenames(() => this.didChangeTreeData.fire(undefined)) - } - - private _isViewVisible = false - private isExpandedNode = new Set() - private treeView: vscode.TreeView | undefined - private activeUri: vscode.Uri | undefined - private selectedRepository: string | undefined - private didFocusToken = new vscode.CancellationTokenSource() - private treeItemCache = new Map() - private readonly didChangeTreeData = new vscode.EventEmitter() - public readonly onDidChangeTreeData: vscode.Event = this.didChangeTreeData.event - - public activeTextDocument(): SourcegraphUri | undefined { - return this.activeUri && this.activeUri.scheme === 'sourcegraph' - ? this.fs.sourcegraphUri(this.activeUri) - : undefined - } - public isViewVisible(): boolean { - return this._isViewVisible - } - public setTreeView(treeView: vscode.TreeView): void { - this.treeView = treeView - treeView.onDidChangeSelection(async event => { - // Check if a repository is selected for removing purpose - await this.isRepository(event.selection[0]) - }) - treeView.onDidChangeVisibility(async event => { - const didBecomeVisible = !this._isViewVisible && event.visible - this._isViewVisible = event.visible - if (didBecomeVisible) { - // NOTE: do not remove the line below even if you think it - // doesn't have an effect. Before you remove this line, make - // sure that the following steps don't cause the "Collapse All" - // button to become disabled: - // 1. Close "Files" view. - // 2. Execute "Reload window" command. - // 3. After VS Code loads, open the "Files" view. - this.didChangeTreeData.fire(undefined) - await this.didFocus(this.activeUri) - } - }) - treeView.onDidExpandElement(async event => { - await this.isRepository(event.element) - this.isExpandedNode.add(event.element) - }) - treeView.onDidCollapseElement(async event => { - await this.isRepository(event.element) - this.isExpandedNode.delete(event.element) - }) - } - - public async isRepository(selectedUri: string): Promise { - const isRepo = [...this.fs.allRepositoryUris()].includes(selectedUri) - this.selectedRepository = isRepo ? selectedUri : undefined - await vscode.commands.executeCommand('setContext', 'sourcegraph.removeRepository', isRepo) - } - - public async getParent(uriString?: string): Promise { - // log.appendLine(`getParent(${uriString})`) - try { - // Implementation note: this method is not implemented as - // `SourcegraphUri.parse(uri).parentUri()` because that would return - // URIs to directories that don't exist because they have no siblings - // and are therefore automatically merged with their parent. For example, - // imagine the following folder structure: - // .gitignore - // .github/workflows/ci.yml - // src/command.ts - // src/browse.ts - // The parent of `.github/workflows/ci.yml` is `.github/` because the `workflows/` - // directory has no sibling. - if (!uriString) { - return undefined - } - const uri = SourcegraphUri.parse(uriString) - if (!uri.path) { - return undefined - } - let ancestor: string | undefined = uri.repositoryUri() - let children = await this.getChildren(ancestor) - while (ancestor) { - const isParent = children?.includes(uriString) - if (isParent) { - break - } - ancestor = children?.find(childUri => { - const child = SourcegraphUri.parse(childUri) - return child.path && uri.path?.startsWith(child.path + '/') - }) - if (!ancestor) { - log.errorAndThrow(`getParent(${uriString || 'undefined'}) nothing startsWith`) - } - children = await this.getChildren(ancestor) - } - return ancestor - } catch (error) { - log.errorAndThrow(`getParent(${uriString || 'undefined'})`, error) - return undefined - } - } - - public async getChildren(uriString?: string): Promise { - try { - if (!uriString) { - const repos = [...this.fs.allRepositoryUris()] - return repos.map(repo => repo.replace('https://', 'sourcegraph://')) - } - const uri = SourcegraphUri.parse(uriString) - const tree = await this.fs.getFileTree(uri) - const directChildren = tree.directChildren(uri.path || '') - for (const child of directChildren) { - this.treeItemCache.set(child, this.newTreeItem(SourcegraphUri.parse(child), uri, directChildren.length)) - } - return directChildren - } catch (error) { - return log.errorAndThrow(`getChildren(${uriString || ''})`, error) - } - } - - public async focusActiveFile(): Promise { - await vscode.commands.executeCommand('sourcegraph.files.focus') - await this.didFocus(this.activeUri) - } - - public async didFocus(vscodeUri: vscode.Uri | undefined): Promise { - this.didFocusToken.cancel() - this.didFocusToken = new vscode.CancellationTokenSource() - this.activeUri = vscodeUri - await vscode.commands.executeCommand( - 'setContext', - 'sourcegraph.canFocusActiveDocument', - vscodeUri?.scheme === 'sourcegraph' - ) - if (vscodeUri && vscodeUri.scheme === 'sourcegraph' && this.treeView && this._isViewVisible) { - const uri = this.fs.sourcegraphUri(vscodeUri) - if (uri.uri === this.fs.emptyFileUri()) { - return - } - await this.fs.downloadFiles(uri) - await this.didFocusString(uri, true, this.didFocusToken.token) - } - } - - public isSourcegrapeRemoteFile(vscodeUri: vscode.Uri | undefined): boolean { - if (vscodeUri && vscodeUri.scheme === 'sourcegraph' && this.treeView && this._isViewVisible) { - return true - } - return false - } - - public async getTreeItem(uriString: string): Promise { - try { - const fromCache = this.treeItemCache.get(uriString) - if (fromCache) { - return fromCache - } - const uri = SourcegraphUri.parse(uriString) - const parentUri = await this.getParent(uri.uri) - return this.newTreeItem(uri, parentUri ? SourcegraphUri.parse(parentUri) : undefined, 0) - } catch (error) { - log.errorAndThrow(`getTreeItem(${uriString})`, error) - } - return {} - } - - private async didFocusString( - uri: SourcegraphUri, - isDestinationNode: boolean, - token: vscode.CancellationToken - ): Promise { - try { - if (this.treeView) { - const parent = await this.getParent(uri.uri) - if (parent) { - await this.didFocusString(SourcegraphUri.parse(parent), false, token) - } else { - await this.getChildren(undefined) - } - if (token.isCancellationRequested) { - return - } - await this.treeView.reveal(uri.uri, { - focus: true, - select: isDestinationNode, - expand: !isDestinationNode, - }) - } - } catch (error) { - log.error(`didFocusString(${uri.uri})`, error) - } - } - - // Remove selected repo from tree - public async removeTreeItem(): Promise { - if (this.selectedRepository) { - this.fs.removeRepository(this.selectedRepository) - } - this.selectedRepository = undefined - await vscode.commands.executeCommand('setContext', 'sourcegraph.removeRepository', false) - return this.didChangeTreeData.fire(undefined) - } - - private newTreeItem( - uri: SourcegraphUri, - parent: SourcegraphUri | undefined, - parentChildrenCount: number - ): vscode.TreeItem { - const command = uri.isFile() - ? { - command: 'sourcegraph.openFile', - title: 'Open file', - toolbar: 'test', - arguments: [uri.uri], - } - : undefined - // Check if this is a currently selected file - let selectedFile = false - if ( - vscode.window.activeTextEditor?.document && - uri.path === SourcegraphUri.parse(vscode.window.activeTextEditor?.document.uri.toString()).path - ) { - selectedFile = true - } - return { - id: uri.uri, - label: uri.treeItemLabel(parent), - tooltip: uri.uri.replace('sourcegraph://', 'https://'), - collapsibleState: uri.isFile() - ? vscode.TreeItemCollapsibleState.None - : parentChildrenCount === 0 - ? vscode.TreeItemCollapsibleState.Expanded - : vscode.TreeItemCollapsibleState.Collapsed, - command, - resourceUri: vscode.Uri.parse(uri.uri), - contextValue: !uri.isFile() ? 'directory' : selectedFile ? 'selected' : 'file', - } - } -} diff --git a/client/vscode/src/file-system/SourcegraphFileSystemProvider.ts b/client/vscode/src/file-system/SourcegraphFileSystemProvider.ts deleted file mode 100644 index 77a4f9b913e..00000000000 --- a/client/vscode/src/file-system/SourcegraphFileSystemProvider.ts +++ /dev/null @@ -1,316 +0,0 @@ -import * as vscode from 'vscode' - -import { getBlobContent } from '../backend/blobContent' -import { getFiles } from '../backend/files' -import { getRepositoryMetadata, type RepositoryMetadata } from '../backend/repositoryMetadata' -import type { LocationNode } from '../code-intel/location' -import { log } from '../log' -import { endpointHostnameSetting } from '../settings/endpointSetting' - -import { FileTree } from './FileTree' -import { SourcegraphUri } from './SourcegraphUri' - -export interface RepositoryFileNames { - repositoryUri: string - repositoryName: string - fileNames: string[] -} - -export interface Blob { - uri: string - repositoryName: string - revision: string - path: string - content: Uint8Array - isBinaryFile: boolean - byteSize: number - time: number - type: vscode.FileType -} - -export class SourcegraphFileSystemProvider implements vscode.FileSystemProvider { - constructor(private instanceURL: string) {} - - private fileNamesByRepository: Map> = new Map() - private metadata: Map = new Map() - private didDownloadFilenames = new vscode.EventEmitter() - - // ====================== - // FileSystemProvider API - // ====================== - - // We don't implement this because Sourcegraph files are read-only. - private didChangeFile = new vscode.EventEmitter() // Never used. - public readonly onDidChangeFile: vscode.Event = this.didChangeFile.event - public async stat(vscodeUri: vscode.Uri): Promise { - const uri = this.sourcegraphUri(vscodeUri) - const now = Date.now() - if (uri.uri === this.emptyFileUri()) { - return { mtime: now, ctime: now, size: 0, type: vscode.FileType.File } - } - const files = await this.downloadFiles(uri) - const isFile = uri.path && files.includes(uri.path) - const type = isFile ? vscode.FileType.File : vscode.FileType.Directory - // log.appendLine( - // `stat(${uri.uri}) path=${uri.path || '""'} files.length=${files.length} type=${vscode.FileType[type]}` - // ) - return { - // It seems to be OK to return hardcoded values for the timestamps - // and the byte size. If it turns out the byte size needs to be - // correct for some reason, then we can use - // `this.fetchBlob(uri).byteSize` to get the value for files. - mtime: now, - ctime: now, - size: 1337, - type, - } - } - - public emptyFileUri(): string { - return 'sourcegraph://sourcegraph.com/empty-file.txt' - } - - public async readFile(vscodeUri: vscode.Uri): Promise { - const uri = this.sourcegraphUri(vscodeUri) - if (uri.uri === this.emptyFileUri()) { - return new Uint8Array() - } - const blob = await this.fetchBlob(uri) - return blob.content - } - - public async readDirectory(vscodeUri: vscode.Uri): Promise<[string, vscode.FileType][]> { - const uri = this.sourcegraphUri(vscodeUri) - if (uri.uri.endsWith('/-')) { - return [] - } - const tree = await this.getFileTree(uri) - const children = tree.directChildren(uri.path || '') - return children.map(childUri => { - const child = SourcegraphUri.parse(childUri) - const type = child.isDirectory() ? vscode.FileType.Directory : vscode.FileType.File - return [child.basename(), type] - }) - } - - public createDirectory(uri: vscode.Uri): void { - throw new Error('Method not supported in read-only file system.') - } - public writeFile( - _uri: vscode.Uri, - _content: Uint8Array, - _options: { create: boolean; overwrite: boolean } - ): void | Thenable { - throw new Error('Method not supported in read-only file system.') - } - public delete(_uri: vscode.Uri, _options: { recursive: boolean }): void { - throw new Error('Method not supported in read-only file system.') - } - public rename(_oldUri: vscode.Uri, _newUri: vscode.Uri, _options: { overwrite: boolean }): void { - throw new Error('Method not supported in read-only file system.') - } - public watch(_uri: vscode.Uri, _options: { recursive: boolean; excludes: string[] }): vscode.Disposable { - throw new Error('Method not supported in read-only file system.') - } - - // =============================== - // Helper methods for external use - // =============================== - - public onDidDownloadRepositoryFilenames: vscode.Event = this.didDownloadFilenames.event - - public allRepositoryUris(): string[] { - return [...this.fileNamesByRepository.keys()] - } - - public resetFileTree(): void { - return this.fileNamesByRepository.clear() - } - - // Remove Currently Selected Repository from Tree - public removeRepository(uriString: string): void { - this.fileNamesByRepository.delete(uriString) - } - - public async allFilesFromOpenRepositories(folder?: SourcegraphUri): Promise { - const promises: RepositoryFileNames[] = [] - const folderRepositoryUri = folder?.repositoryUri() - for (const [repositoryUri, downloadingFileNames] of this.fileNamesByRepository.entries()) { - if (folderRepositoryUri && repositoryUri !== folderRepositoryUri) { - continue - } - try { - const fileNames = await downloadingFileNames - const uri = SourcegraphUri.parse(repositoryUri) - promises.push({ - repositoryUri: uri.repositoryUri(), - repositoryName: `${uri.repositoryName}${uri.revisionPart()}`, - fileNames, - }) - } catch { - log.error(`failed to download files for repository '${repositoryUri}'`) - } - } - return promises - } - - public toVscodeLocation(node: LocationNode): vscode.Location { - const metadata = this.metadata.get(node.resource.repositoryName) - let revision = node.resource.revision - if (metadata?.defaultBranch && revision === metadata?.defaultOID) { - revision = metadata.defaultBranch - } - - let rangeOrPosition: vscode.Range | vscode.Position - if (node.range) { - rangeOrPosition = new vscode.Range( - new vscode.Position(node.range.start.line, node.range.start.character), - new vscode.Position(node.range.end.line, node.range.end.character) - ) - } else { - rangeOrPosition = new vscode.Position(0, 0) - } - - return new vscode.Location( - vscode.Uri.parse( - SourcegraphUri.fromParts(endpointHostnameSetting(), node.resource.repositoryName, { - revision, - path: node.resource.path, - }).uri - ), - rangeOrPosition - ) - } - - /** - * @returns the URI of a file in the given repository. The file is the - * toplevel readme file if it exists, otherwise it's the file with the - * shortest name in the repository. - */ - public async defaultFileUri(repositoryName: string): Promise { - const defaultBranch = (await this.repositoryMetadata(repositoryName))?.defaultBranch - if (!defaultBranch) { - log.errorAndThrow(`repository '${repositoryName}' has no default branch`) - } - const uri = SourcegraphUri.fromParts(endpointHostnameSetting(), repositoryName, { revision: defaultBranch }) - const files = await this.downloadFiles(uri) - const readmes = files.filter(name => name.match(/readme/i)) - const candidates = readmes.length > 0 ? readmes : files - let readme: string | undefined - for (const candidate of candidates) { - if (candidate === '' || candidate === 'lsif-java.json') { - // Skip auto-generated file for JVM packages - continue - } - if (!readme) { - readme = candidate - } else if (candidate.length < readme.length) { - readme = candidate - } - } - const defaultFile = readme || files[0] - return SourcegraphUri.fromParts(endpointHostnameSetting(), repositoryName, { - revision: defaultBranch, - path: defaultFile, - }) - } - - public async fetchBlob(uri: SourcegraphUri): Promise { - await this.repositoryMetadata(uri.repositoryName) - if (!uri.revision) { - log.errorAndThrow(`missing revision for URI '${uri.uri}'`) - } - const path = uri.path || '' - const content = await getBlobContent({ - repository: uri.repositoryName, - revision: uri.revision, - path, - }) - - if (content) { - const toCacheResult: Blob = { - uri: uri.uri, - repositoryName: uri.repositoryName, - revision: uri.revision, - content: content.content, - isBinaryFile: content.isBinary, - byteSize: content.byteSize, - path, - time: new Date().getMilliseconds(), - type: vscode.FileType.File, - } - - // Start downloading the repository files in the background. - this.downloadFiles(uri).then( - () => {}, - () => {} - ) - - return toCacheResult - } - return log.errorAndThrow(`fetchBlob(${uri.uri}) not found`) - } - - public async repositoryMetadata(repositoryName: string): Promise { - let metadata = this.metadata.get(repositoryName) - if (metadata) { - return metadata - } - metadata = await getRepositoryMetadata({ repositoryName }) - if (metadata) { - this.metadata.set(repositoryName, metadata) - } - return metadata - } - - public downloadFiles(uri: SourcegraphUri): Promise { - const key = uri.repositoryUri() - const fileNamesByRepository = this.fileNamesByRepository - let downloadingFiles = this.fileNamesByRepository.get(key) - if (!downloadingFiles) { - downloadingFiles = getFiles({ repository: uri.repositoryName, revision: uri.revision }) - vscode.window - .withProgress( - { - location: vscode.ProgressLocation.Window, - title: `Loading ${uri.repositoryName}`, - }, - async progress => { - try { - await downloadingFiles - this.didDownloadFilenames.fire(key) - } catch (error) { - log.error(`downloadFiles(${key})`, error) - fileNamesByRepository.delete(key) - } - progress.report({ increment: 100 }) - } - ) - .then( - () => {}, - () => {} - ) - - this.fileNamesByRepository.set(key, downloadingFiles) - } - return downloadingFiles - } - - public sourcegraphUri(uri: vscode.Uri): SourcegraphUri { - const sourcegraphUri = SourcegraphUri.parse(uri.toString(true)) - if (sourcegraphUri.host !== new URL(this.instanceURL).host) { - const message = 'Sourcegraph instance URL has changed. Close files opened through the previous instance.' - vscode.window.showWarningMessage(message).then( - () => {}, - () => {} - ) - throw new Error(message) - } - return sourcegraphUri - } - - public async getFileTree(uri: SourcegraphUri): Promise { - const files = await this.downloadFiles(uri) - return new FileTree(uri, files) - } -} diff --git a/client/vscode/src/file-system/SourcegraphUri.ts b/client/vscode/src/file-system/SourcegraphUri.ts deleted file mode 100644 index 4701007ceae..00000000000 --- a/client/vscode/src/file-system/SourcegraphUri.ts +++ /dev/null @@ -1,225 +0,0 @@ -import type { Position } from '@sourcegraph/extension-api-types' -import { parseQueryAndHash, parseRepoRevision } from '@sourcegraph/shared/src/util/url' - -export interface SourcegraphUriOptionals { - revision?: string - path?: string - position?: Position - isDirectory?: boolean - isCommit?: boolean - compareRange?: CompareRange -} - -export interface CompareRange { - base: string - head: string -} - -/** - * SourcegraphUri encodes a URI like `sourcegraph://HOST/REPOSITORY@REVISION/-/blob/PATH?L1337`. - * - * This class is used in both webviews and extensions, so try to avoid state management in this class or module. - */ -export class SourcegraphUri { - private constructor( - public readonly uri: string, - public readonly host: string, - public readonly repositoryName: string, - public readonly revision: string, - public readonly path: string | undefined, - public readonly position: Position | undefined, - public readonly compareRange: CompareRange | undefined - ) {} - - public withRevision(newRevision: string | undefined): SourcegraphUri { - const newRevisionPath = newRevision ? `@${newRevision}` : '' - return SourcegraphUri.parse( - `sourcegraph://${this.host}/${this.repositoryName}${newRevisionPath}/-/blob/${ - this.path || '' - }${this.positionSuffix()}` - ) - } - - public with(optionals: SourcegraphUriOptionals): SourcegraphUri { - return SourcegraphUri.fromParts(this.host, this.repositoryName, { - path: this.path, - revision: this.revision, - compareRange: this.compareRange, - position: this.position, - ...optionals, - }) - } - - public withPath(newPath: string): SourcegraphUri { - return SourcegraphUri.parse(`${this.repositoryUri()}/-/blob/${newPath}${this.positionSuffix()}`) - } - - public basename(): string { - const parts = (this.path || '').split('/') - return parts.at(-1)! - } - - public dirname(): string { - const parts = (this.path || '').split('/') - return parts.slice(0, -1).join('/') - } - - public parentUri(): string | undefined { - if (typeof this.path === 'string') { - const slash = this.uri.lastIndexOf('/') - if (slash < 0 || !this.path.includes('/')) { - return `sourcegraph://${this.host}/${this.repositoryName}${this.revisionPart()}` - } - const parent = this.uri.slice(0, slash).replace('/-/blob/', '/-/tree/') - return parent - } - return undefined - } - - public withIsDirectory(isDirectory: boolean): SourcegraphUri { - return SourcegraphUri.fromParts(this.host, this.repositoryName, { - isDirectory, - path: this.path, - revision: this.revision, - position: this.position, - }) - } - - public isCommit(): boolean { - return this.uri.includes('/-/commit/') - } - - public isCompare(): boolean { - return this.uri.includes('/-/compare/') && this.compareRange !== undefined - } - - public isDirectory(): boolean { - return this.uri.includes('/-/tree/') - } - - public isFile(): boolean { - return this.uri.includes('/-/blob/') - } - - public static fromParts(host: string, repositoryName: string, optional?: SourcegraphUriOptionals): SourcegraphUri { - const revisionPart = optional?.revision ? `@${optional.revision}` : '' - const directoryPart = optional?.isDirectory - ? 'tree' - : optional?.isCommit - ? 'commit' - : optional?.compareRange - ? 'compare' - : 'blob' - const pathPart = optional?.compareRange - ? `/-/compare/${optional.compareRange.base}...${optional.compareRange.head}` - : optional?.isCommit && optional.revision - ? `/-/commit/${optional.revision}` - : optional?.path - ? `/-/${directoryPart}/${optional?.path}` - : '' - const uri = `sourcegraph://${host}/${repositoryName}${revisionPart}${pathPart}` - return new SourcegraphUri( - uri, - host, - repositoryName, - optional?.revision || '', - optional?.path, - optional?.position, - optional?.compareRange - ) - } - public repositoryUri(): string { - return `sourcegraph://${this.host}/${this.repositoryName}${this.revisionPart()}` - } - public treeItemLabel(parent?: SourcegraphUri): string { - if (this.path) { - if (parent?.path) { - return this.path.slice(parent.path.length + 1) - } - return this.path - } - return `${this.repositoryName}` - } - public revisionPart(): string { - return this.revision ? `@${this.revision}` : '' - } - public positionSuffix(): string { - return this.position === undefined ? '' : `?L${this.position.line}:${this.position.character}` - } - - // Debt: refactor and use shared functions. Below is based on parseBrowserRepoURL - // https://sourcegraph.com/github.com/sourcegraph/sourcegraph@56dfaaa3e3172f9afd4a29a4780a7f1a34198238/-/blob/client/shared/src/util/url.ts?L287 - // In the browser, pass in window.URL. When we use the shared implementation, pass in the URL module from Node. - public static parse(uri: string, URLModule = URL): SourcegraphUri { - uri = uri.replace('https://', 'sourcegraph://') - const url = new URLModule(uri.replace('sourcegraph://', 'https://')) - let pathname = url.pathname.slice(1) // trim leading '/' - if (pathname.endsWith('/')) { - pathname = pathname.slice(0, -1) // trim trailing '/' - } - - const indexOfSeparator = pathname.indexOf('/-/') - - // examples: - // - 'github.com/gorilla/mux' - // - 'github.com/gorilla/mux@revision' - // - 'foo/bar' (from 'sourcegraph.mycompany.com/foo/bar') - // - 'foo/bar@revision' (from 'sourcegraph.mycompany.com/foo/bar@revision') - // - 'foobar' (from 'sourcegraph.mycompany.com/foobar') - // - 'foobar@revision' (from 'sourcegraph.mycompany.com/foobar@revision') - let repoRevision: string - if (indexOfSeparator === -1) { - repoRevision = pathname // the whole string - } else { - repoRevision = pathname.slice(0, indexOfSeparator) // the whole string leading up to the separator (allows revision to be multiple path parts) - } - let { repoName, revision } = parseRepoRevision(repoRevision) - - let path: string | undefined - let compareRange: CompareRange | undefined - const treeSeparator = pathname.indexOf('/-/tree/') - const blobSeparator = pathname.indexOf('/-/blob/') - const commitSeparator = pathname.indexOf('/-/commit/') - const comparisonSeparator = pathname.indexOf('/-/compare/') - if (treeSeparator !== -1) { - path = decodeURIComponent(pathname.slice(treeSeparator + '/-/tree/'.length)) - } - if (blobSeparator !== -1) { - path = decodeURIComponent(pathname.slice(blobSeparator + '/-/blob/'.length)) - } - if (commitSeparator !== -1) { - path = decodeURIComponent(pathname.slice(commitSeparator + '/-/commit/'.length)) - } - if (comparisonSeparator !== -1) { - const range = pathname.slice(comparisonSeparator + '/-/compare/'.length) - const parts = range.split('...') - if (parts.length === 2) { - const [base, head] = parts - compareRange = { base, head } - } - } - let position: Position | undefined - - const parsedHash = parseQueryAndHash(url.search, url.hash) - if (parsedHash.line) { - position = { - line: parsedHash.line, - character: parsedHash.character || 0, - } - } - const isDirectory = uri.includes('/-/tree/') - const isCommit = uri.includes('/-/commit/') - if (isCommit) { - revision = url.pathname.replace(new RegExp('.*/-/commit/([^/]+).*'), (_unused, oid: string) => oid) - path = path?.slice(`${revision}/`.length) - } - return SourcegraphUri.fromParts(url.host, repoName, { - revision, - path, - position, - isDirectory, - isCommit, - compareRange, - }) - } -} diff --git a/client/vscode/src/file-system/commands.ts b/client/vscode/src/file-system/commands.ts deleted file mode 100644 index 72f0981bc40..00000000000 --- a/client/vscode/src/file-system/commands.ts +++ /dev/null @@ -1,105 +0,0 @@ -import * as vscode from 'vscode' - -import type { SourcegraphFileSystemProvider } from './SourcegraphFileSystemProvider' -import type { SourcegraphUri } from './SourcegraphUri' - -/** - * Try to find local copy of the search result file first - * Remote copy will be opened instead if basePath is not set or local copy cannot be found - **/ -export async function openSourcegraphUriCommand(fs: SourcegraphFileSystemProvider, uri: SourcegraphUri): Promise { - if (uri.compareRange) { - // noop. v2 Debt: implement. Open in browser for v1 - return - } - let textDocument - try { - textDocument = await getLocalCopy(uri) - } catch (error) { - console.error('Failed to get local copy:', error) - if (!uri.revision) { - const metadata = await fs.repositoryMetadata(uri.repositoryName) - uri = uri.withRevision(metadata?.defaultBranch || 'HEAD') - } - // Load Remote Copy instead - textDocument = await vscode.workspace.openTextDocument(vscode.Uri.parse(uri.uri)) - } - const selection = getSelection(uri, textDocument) - await vscode.window.showTextDocument(textDocument, { - selection, - viewColumn: vscode.ViewColumn.Active, - preview: false, - }) -} - -async function getLocalCopy(remoteUri: SourcegraphUri): Promise { - const repoName = remoteUri.repositoryName.split('/').pop() || '' // ex: github.com/sourcegraph/sourcegraph => sourcegraph - const filePath = remoteUri.path || '' // ex: "client/vscode/package.json" - // Get basePath from configuration - const basePath = vscode.workspace.getConfiguration('sourcegraph').get('basePath') || null - const workspaceFilePath = await vscode.workspace - .findFiles(filePath, null, 1) - .then(result => result[0]?.path || null) - // If basePath is not configured, we will try to find file in the current workspace - const absolutePath = basePath - ? vscode.Uri.file(vscode.Uri.joinPath(vscode.Uri.parse(basePath), repoName, filePath).path) - : workspaceFilePath - ? vscode.Uri.file(workspaceFilePath) - : null - // if both basePath and workspaceFilePath are null, the operation will fail - if (!absolutePath) { - throw new Error('Try to configure your basePath to open this file.') - } - // Set current workspace folder path as basePath if it doesn't exist - if (!basePath && workspaceFilePath) { - // get current workspace folder uri - const workspaceFolderUri = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(workspaceFilePath))?.uri - if (workspaceFolderUri) { - // go one level up and set that as the new basePath - const newBasePath = vscode.Uri.file(vscode.Uri.joinPath(workspaceFolderUri, '../').fsPath).path - await vscode.workspace - .getConfiguration('sourcegraph') - .update('basePath', newBasePath, vscode.ConfigurationTarget.Global) - } - } - const textDocument = await vscode.workspace.openTextDocument(absolutePath) - return textDocument -} - -function getSelection(uri: SourcegraphUri, textDocument: vscode.TextDocument): vscode.Range | undefined { - if (uri?.position?.line !== undefined && uri?.position?.character !== undefined) { - return offsetRange(uri.position.line, uri.position.character) - } - if (uri?.position?.line !== undefined) { - return offsetRange(uri.position.line, 0) - } - // There's no explicitly provided line number. Instead of focusing on the - // first line (which usually contains lots of imports), we use a heuristic - // to guess the location where the "main symbol" is defined (a - // function/class/struct/interface with the same name as the filename). - if (uri.path && isFilenameThatMayDefineSymbols(uri.path)) { - const fileNames = uri.path.split('/') - const fileName = fileNames.at(-1)! - const symbolName = fileName.split('.')[0] - const text = textDocument.getText() - const symbolMatches = new RegExp(` ${symbolName}\\b`).exec(text) - if (symbolMatches) { - const position = textDocument.positionAt(symbolMatches.index + 1) - return new vscode.Range(position, position) - } - } - return undefined -} - -function offsetRange(line: number, character: number): vscode.Range { - const position = new vscode.Position(line, character) - return new vscode.Range(position, position) -} - -/** - * @returns true if this file may contain code from a programming language that - * defines symbol. - */ -function isFilenameThatMayDefineSymbols(path: string): boolean { - return !(path.endsWith('.md') || path.endsWith('.markdown') || path.endsWith('.txt') || path.endsWith('.log')) -} diff --git a/client/vscode/src/file-system/initialize.ts b/client/vscode/src/file-system/initialize.ts deleted file mode 100644 index fc2bc85a095..00000000000 --- a/client/vscode/src/file-system/initialize.ts +++ /dev/null @@ -1,48 +0,0 @@ -import vscode from 'vscode' - -import { log } from '../log' - -import { openSourcegraphUriCommand } from './commands' -import { FilesTreeDataProvider } from './FilesTreeDataProvider' -import { SourcegraphFileSystemProvider } from './SourcegraphFileSystemProvider' -import { SourcegraphUri } from './SourcegraphUri' - -export function initializeSourcegraphFileSystem({ - context, - initialInstanceURL, -}: { - context: vscode.ExtensionContext - initialInstanceURL: string -}): { fs: SourcegraphFileSystemProvider } { - const fs = new SourcegraphFileSystemProvider(initialInstanceURL) - context.subscriptions.push(vscode.workspace.registerFileSystemProvider('sourcegraph', fs, { isReadonly: true })) - - const files = new FilesTreeDataProvider(fs) - - const filesTreeView = vscode.window.createTreeView('sourcegraph.files', { - treeDataProvider: files, - showCollapseAll: true, - }) - files.setTreeView(filesTreeView) - context.subscriptions.push(filesTreeView) - - // Open remote Sourcegraph file from remote file tree - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.openFile', async uri => { - if (typeof uri === 'string') { - await openSourcegraphUriCommand(fs, SourcegraphUri.parse(uri)) - } else { - log.error(`extension.openRemoteFile(${uri}) argument is not a string`) - } - }) - ) - - // Remove Selected Repository from File Tree - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.removeRepoTree', async () => { - await files.removeTreeItem() - }) - ) - - return { fs } -} diff --git a/client/vscode/src/log.ts b/client/vscode/src/log.ts deleted file mode 100644 index cf922443570..00000000000 --- a/client/vscode/src/log.ts +++ /dev/null @@ -1,34 +0,0 @@ -import vscode from 'vscode' - -const outputChannel = vscode.window.createOutputChannel('Sourcegraph') - -export const log = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error: (what: string, error?: any): void => { - outputChannel.appendLine(`ERROR ${errorMessage(what, error)}`) - }, - errorAndThrow: (what: string, error?: any): never => { - log.error(what, error) - throw new Error(errorMessage(what, error)) - }, - debug: (what: any): void => { - for (const key of Object.keys(what)) { - const value = JSON.stringify(what[key]) - outputChannel.appendLine(`${key}=${value}`) - } - }, - appendLine: (message: string): void => { - outputChannel.appendLine(message) - }, -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function errorMessage(what: string, error?: any): string { - const errorMessage = - error instanceof Error - ? ` ${error.message} ${error.stack || ''}` - : error !== undefined - ? ` ${JSON.stringify(error)}` - : '' - return what + errorMessage -} diff --git a/client/vscode/src/settings/LocalStorageService.ts b/client/vscode/src/settings/LocalStorageService.ts deleted file mode 100644 index c8ed178a7a3..00000000000 --- a/client/vscode/src/settings/LocalStorageService.ts +++ /dev/null @@ -1,25 +0,0 @@ -// VS Code Docs https://code.visualstudio.com/api/references/vscode-api#Memento -// A memento represents a storage utility. It can store and retrieve values. -import type { Memento } from 'vscode' - -export class LocalStorageService { - constructor(private storage: Memento) {} - - public getValue(key: string): string { - return this.storage.get(key, '') - } - - public async setValue(key: string, value: string): Promise { - try { - await this.storage.update(key, value) - return true - } catch { - return false - } - } -} - -export const SELECTED_SEARCH_CONTEXT_SPEC_KEY = 'selected-search-context-spec' -export const INSTANCE_VERSION_NUMBER_KEY = 'sourcegraphVersionNumber' -export const ANONYMOUS_USER_ID_KEY = 'sourcegraphAnonymousUid' -export const DISMISS_WORKSPACERECS_CTA_KEY = 'sourcegraphWorkspaceRecsCtaDismissed' diff --git a/client/vscode/src/settings/accessTokenSetting.ts b/client/vscode/src/settings/accessTokenSetting.ts deleted file mode 100644 index 7cb7f502074..00000000000 --- a/client/vscode/src/settings/accessTokenSetting.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as vscode from 'vscode' - -import { isOlderThan, observeInstanceVersionNumber } from '../backend/instanceVersion' -import { scretTokenKey } from '../webview/platform/AuthProvider' - -import { endpointHostnameSetting, endpointProtocolSetting } from './endpointSetting' -import { readConfiguration } from './readConfiguration' - -// IMPORTANT: Call this function only once when extention is first activated -export async function processOldToken(secretStorage: vscode.SecretStorage): Promise { - // Process the token that lives in user configuration - // Move them to secrets and then remove them by setting it as undefined - const storageToken = await secretStorage.get(scretTokenKey) - const oldToken = vscode.workspace.getConfiguration().get('sourcegraph.accessToken') || '' - if (!storageToken && oldToken.length > 8) { - await secretStorage.store(scretTokenKey, oldToken) - await removeOldAccessTokenSetting() - } - return -} - -export async function accessTokenSetting(secretStorage: vscode.SecretStorage): Promise { - const currentToken = await secretStorage.get(scretTokenKey) - return currentToken || '' -} - -export async function removeOldAccessTokenSetting(): Promise { - await readConfiguration().update('accessToken', undefined, vscode.ConfigurationTarget.Global) - await readConfiguration().update('accessToken', undefined, vscode.ConfigurationTarget.Workspace) - return -} - -// Ensure that only one access token error message is shown at a time. -let showingAccessTokenErrorMessage = false - -export async function handleAccessTokenError(badToken: string, endpointURL: string): Promise { - if (badToken !== undefined && !showingAccessTokenErrorMessage) { - showingAccessTokenErrorMessage = true - - const message = !badToken - ? `A valid access token is required to connect to ${endpointURL}` - : `Connection to ${endpointURL} failed. Please try reloading VS Code if your Sourcegraph instance URL has been updated.` - - const version = await observeInstanceVersionNumber(badToken, endpointURL).toPromise() - const supportsTokenCallback = version && isOlderThan(version, { major: 3, minor: 41 }) - const action = await vscode.window.showErrorMessage(message, 'Get Token', 'Reload Window') - - if (action === 'Reload Window') { - await vscode.commands.executeCommand('workbench.action.reloadWindow') - } else if (action === 'Get Token') { - const path = supportsTokenCallback ? '/user/settings/tokens/new/callback' : '/user/settings/' - const query = supportsTokenCallback ? 'requestFrom=VSCEAUTH' : '' - - await vscode.env.openExternal( - vscode.Uri.from({ - scheme: endpointProtocolSetting().slice(0, -1), - authority: endpointHostnameSetting(), - path, - query, - }) - ) - } - showingAccessTokenErrorMessage = false - } -} diff --git a/client/vscode/src/settings/displayWarnings.ts b/client/vscode/src/settings/displayWarnings.ts deleted file mode 100644 index 4b8e53abf94..00000000000 --- a/client/vscode/src/settings/displayWarnings.ts +++ /dev/null @@ -1,5 +0,0 @@ -import vscode from 'vscode' - -export async function displayWarning(warning: string): Promise { - await vscode.window.showErrorMessage(warning) -} diff --git a/client/vscode/src/settings/endpointSetting.ts b/client/vscode/src/settings/endpointSetting.ts deleted file mode 100644 index f845454664f..00000000000 --- a/client/vscode/src/settings/endpointSetting.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as vscode from 'vscode' - -import { readConfiguration } from './readConfiguration' - -export function endpointSetting(): string { - const url = vscode.workspace.getConfiguration().get('sourcegraph.url') || 'https://sourcegraph.com' - return removeEndingSlash(url) -} - -export async function setEndpoint(newEndpoint: string): Promise { - const newEndpointURL = newEndpoint ? removeEndingSlash(newEndpoint) : 'https://sourcegraph.com' - const currentEndpointHostname = new URL(endpointSetting()).hostname - const newEndpointHostname = new URL(newEndpointURL).hostname - if (currentEndpointHostname !== newEndpointHostname) { - await readConfiguration().update('url', newEndpointURL) - } - return -} - -export function endpointHostnameSetting(): string { - return new URL(endpointSetting()).hostname -} - -export function endpointPortSetting(): number { - const port = new URL(endpointSetting()).port - return port ? parseInt(port, 10) : 443 -} - -export function endpointProtocolSetting(): string { - return new URL(endpointSetting()).protocol -} - -export function endpointRequestHeadersSetting(): object { - return vscode.workspace.getConfiguration().get('sourcegraph.requestHeaders') || {} -} - -function removeEndingSlash(uri: string): string { - if (uri.endsWith('/')) { - return uri.slice(0, -1) - } - return uri -} - -export function isSourcegraphDotCom(): boolean { - const hostname = new URL(endpointSetting()).hostname - return hostname === 'sourcegraph.com' || hostname === 'www.sourcegraph.com' -} diff --git a/client/vscode/src/settings/invalidation.ts b/client/vscode/src/settings/invalidation.ts deleted file mode 100644 index 768078746e4..00000000000 --- a/client/vscode/src/settings/invalidation.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as vscode from 'vscode' - -import { invalidateClient } from '../backend/requestGraphQl' -import type { VSCEStateMachine } from '../state' - -/** - * Listens for Sourcegraph URL and invalidates the GraphQL client - * to prevent data "contamination" (e.g. sending private repo names to Cloud instance). - */ -export function invalidateContextOnSettingsChange({ - context, - stateMachine, -}: { - context: vscode.ExtensionContext - stateMachine: VSCEStateMachine -}): void { - function disposeAllResources(): void { - for (const subscription of context.subscriptions) { - subscription.dispose() - } - } - - context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration(async config => { - if (config.affectsConfiguration('sourcegraph.url')) { - invalidateClient() - disposeAllResources() - stateMachine.emit({ type: 'sourcegraph_url_change' }) - // Swallow errors since if `showInformationMessage` fails, we assume that something is wrong - // with the VS Code extension host and don't retry. - await vscode.window.showInformationMessage( - 'Restart VS Code to use the Sourcegraph extension after URL change.' - ) - } - }) - ) -} diff --git a/client/vscode/src/settings/readConfiguration.ts b/client/vscode/src/settings/readConfiguration.ts deleted file mode 100644 index e49ee368021..00000000000 --- a/client/vscode/src/settings/readConfiguration.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as vscode from 'vscode' - -export function readConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration('sourcegraph') -} diff --git a/client/vscode/src/settings/recommendations.ts b/client/vscode/src/settings/recommendations.ts deleted file mode 100644 index 4649296a7ce..00000000000 --- a/client/vscode/src/settings/recommendations.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Disabled due to violation of the VS Code's UX guidelines for notifications - * To be revaluated in the future: https://code.visualstudio.com/api/ux-guidelines/notifications - * This functions add Sourcegraph to workspace recommendations if haven't already - * eg: recommendSourcegraph(localStorageService).catch(() => {}) - */ - -import * as vscode from 'vscode' - -import { DISMISS_WORKSPACERECS_CTA_KEY, type LocalStorageService } from './LocalStorageService' - -/** - * Ask if user wants to add Sourcegraph to their Workspace Recommendations list by displaying built-in popup - * It will not show popup if the user already has Sourcegraph added to their recommendations list - * or has dismissed the message previously - */ -export async function recommendSourcegraph(localStorageService: LocalStorageService): Promise { - // Check if user has clicked on the 'Don't show again' button previously - // Reset stored value for testing: await localStorageService.setValue(DISMISS_WORKSPACERECS_CTA_KEY, '') - const isDismissed = localStorageService.getValue(DISMISS_WORKSPACERECS_CTA_KEY) - if (isDismissed === 'true') { - return - } - const rootPath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : null - if (!rootPath) { - return - } - // File path of the workspace recommendations file - const filePath = vscode.Uri.file(`${rootPath}/.vscode/extensions.json`) - // Check if sourcegraph is already added to their Workspace Recommendations - const bytes = await vscode.workspace.fs.readFile(filePath) - const decoded = new TextDecoder('utf-8').decode(bytes) - // Assume sourcegraph is in the recommendation list if 'sourcegraph.sourcegraph' exists in the file - if (decoded.includes('sourcegraph.sourcegraph')) { - return - } - // Display Cta - await vscode.window - .showInformationMessage('Add Sourcegraph to your workspace recommendations', '👍 Yes', "Don't show again") - .then(async answer => { - if (answer === '👍 Yes') { - await vscode.commands.executeCommand( - 'workbench.extensions.action.addExtensionToWorkspaceRecommendations', - 'sourcegraph.sourcegraph' - ) - } - // Store as dismissed so it won't show user again when answer No or after being added - await localStorageService.setValue(DISMISS_WORKSPACERECS_CTA_KEY, 'true') - }) -} diff --git a/client/vscode/src/settings/uninstall.ts b/client/vscode/src/settings/uninstall.ts deleted file mode 100644 index 18b97036c0e..00000000000 --- a/client/vscode/src/settings/uninstall.ts +++ /dev/null @@ -1,69 +0,0 @@ -import vscode from 'vscode' - -import type { EventSource } from '@sourcegraph/shared/src/graphql-operations' - -import { version } from '../../package.json' -import { logEvent } from '../backend/eventLogger' - -import { ANONYMOUS_USER_ID_KEY, type LocalStorageService } from './LocalStorageService' - -// This function allows us to watch for uninstall event while still having access to the VS Code API -export function watchUninstall(eventSourceType: EventSource, localStorageService: LocalStorageService): void { - const extensionName = 'sourcegraph.sourcegraph' - try { - const extensionPath = vscode.extensions.getExtension(extensionName)?.extensionPath - const pathComponents = extensionPath?.split('/').slice(0, -1) - const extensionsDirectoryPath = pathComponents?.join('/') - // All upgrades, downgrades, and uninstalls will be logged in the .obsolete file - pathComponents?.push('.obsolete') - const uninstalledPath = pathComponents?.join('/') - if (extensionsDirectoryPath && uninstalledPath) { - // Watch the .obsolete file - it does not exist when VS Code is started - // Check if uninstall has happened when the file was created or when changes are made - const watchPattern = new vscode.RelativePattern(extensionsDirectoryPath, '.obsolete') - const watchFileListener = vscode.workspace.createFileSystemWatcher(watchPattern) - watchFileListener.onDidCreate(() => checkUninstall(uninstalledPath, extensionsDirectoryPath)) - watchFileListener.onDidChange(() => checkUninstall(uninstalledPath, extensionsDirectoryPath)) - } - } catch (error) { - console.error('failed to invoke uninstall:', error) - } - - /** - * Assume the extension has been uninstalled if the count of all the versions listed in - * the .obsolete file is equal to the count of all the version-divided directories. - * For example, if there are 5 versions of the extension were installed while there - * are 4 versions of the extension listed in the .obsolete file pending to be deleted, - * it means 1 version of the extension is still installed, therefore no uninstallation - * has happened - **/ - function checkUninstall(uninstalledPath: string, extensionsDirectoryPath: string): void { - Promise.all([ - // .obsolete file includes all extensions versions that need to be remove at restart - vscode.workspace.fs.readFile(vscode.Uri.file(uninstalledPath)), - // Each versions of the extension has its own directory - vscode.workspace.fs.readDirectory(vscode.Uri.parse(extensionsDirectoryPath)), - ]) - .then(([obsoleteExtensionsRaw, extensionsDirectory]) => { - const obsoleteExtensionsCount = Object.keys(JSON.parse(obsoleteExtensionsRaw.toString())).filter(id => - id.includes(extensionName) - ).length - const downloadedExtensionsCount = extensionsDirectory - .map(([name]) => name) - .filter(id => id.includes(extensionName)).length - // Compare count of extension name in .obsolete file vs count of directories with the same extension name - if (downloadedExtensionsCount === obsoleteExtensionsCount) { - logEvent({ - event: 'IDEUninstalled', - userCookieID: localStorageService.getValue(ANONYMOUS_USER_ID_KEY), - referrer: 'VSCE', - url: '', - source: eventSourceType, - argument: JSON.stringify({ editor: 'vscode', version }), - publicArgument: JSON.stringify({ editor: 'vscode', version }), - }) - } - }) - .catch(error => console.error(error)) - } -} diff --git a/client/vscode/src/state.ts b/client/vscode/src/state.ts deleted file mode 100644 index 0a019f53283..00000000000 --- a/client/vscode/src/state.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { cloneDeep } from 'lodash' -import { BehaviorSubject, type Observable } from 'rxjs' - -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import type { SearchQueryState } from '@sourcegraph/shared/src/search' -import type { AggregateStreamingSearchResults } from '@sourcegraph/shared/src/search/stream' - -import { type LocalStorageService, SELECTED_SEARCH_CONTEXT_SPEC_KEY } from './settings/LocalStorageService' - -/** - * One state machine that lives in Core - * See CONTRIBUTING docs to learn how state management works in this extension - */ -export interface VSCEStateMachine { - state: VSCEState - /** - * Returns an Observable that emits the current state and - * on subsequent state updates. - */ - observeState: () => Observable - emit: (event: VSCEEvent) => void -} -export type VSCEState = SearchHomeState | SearchResultsState | RemoteBrowsingState | IdleState | ContextInvalidatedState - -export interface SearchHomeState { - status: 'search-home' - context: CommonContext -} - -export interface SearchResultsState { - status: 'search-results' - context: CommonContext & { - submittedSearchQueryState: Pick - } -} -export interface RemoteBrowsingState { - status: 'remote-browsing' - context: CommonContext -} - -export interface IdleState { - status: 'idle' - context: CommonContext -} - -export interface ContextInvalidatedState { - status: 'context-invalidated' - context: CommonContext -} - -/** - * Subset of SearchQueryState that's necessary and clone-able (`postMessage`) for the VS Code extension. - */ -export type VSCEQueryState = Pick< - SearchQueryState, - 'queryState' | 'searchCaseSensitivity' | 'searchPatternType' | 'searchMode' -> | null - -interface CommonContext { - authenticatedUser: AuthenticatedUser | null - - submittedSearchQueryState: VSCEQueryState - - searchSidebarQueryState: { - proposedQueryState: VSCEQueryState - /** - * The current query state as known to the sidebar. - * Used to "anchor" query state updates to the correct state - * in case the panel's search query state has changed since - * the sidebar event. - * - * Debt: we don't use this yet. - */ - currentQueryState: VSCEQueryState - } - - searchResults: AggregateStreamingSearchResults | null - - selectedSearchContextSpec: string | undefined -} - -function createInitialState({ localStorageService }: { localStorageService: LocalStorageService }): VSCEState { - return { - status: 'search-home', - context: { - authenticatedUser: null, - submittedSearchQueryState: null, - searchResults: null, - selectedSearchContextSpec: localStorageService.getValue(SELECTED_SEARCH_CONTEXT_SPEC_KEY) || undefined, - searchSidebarQueryState: { - proposedQueryState: null, - currentQueryState: null, - }, - }, - } -} - -// Temporary placeholder events. We will replace these with the actual events as we implement the webviews. - -export type VSCEEvent = SearchEvent | TabsEvent | SettingsEvent - -type SearchEvent = - | { type: 'set_query_state' } - | { - type: 'submit_search_query' - submittedSearchQueryState: NonNullable - } - | { type: 'received_search_results'; searchResults: AggregateStreamingSearchResults } - | { type: 'set_selected_search_context_spec'; spec: string } // TODO see how this handles instance change - | { type: 'sidebar_query_update'; proposedQueryState: VSCEQueryState; currentQueryState: VSCEQueryState } - -type TabsEvent = - | { type: 'search_panel_disposed' } - | { type: 'search_panel_unfocused' } - | { type: 'search_panel_focused' } - | { type: 'remote_file_focused' } - | { type: 'remote_file_unfocused' } - -interface SettingsEvent { - type: 'sourcegraph_url_change' -} - -export function createVSCEStateMachine({ - localStorageService, -}: { - localStorageService: LocalStorageService -}): VSCEStateMachine { - const states = new BehaviorSubject(createInitialState({ localStorageService })) - - function reducer(state: VSCEState, event: VSCEEvent): VSCEState { - // End state. - if (state.status === 'context-invalidated') { - return state - } - - // Events with the same behavior regardless of current state - if (event.type === 'sourcegraph_url_change') { - return { - status: 'context-invalidated', - context: { - ...createInitialState({ localStorageService }).context, - }, - } - } - if (event.type === 'set_selected_search_context_spec') { - return { - ...state, - context: { - ...state.context, - selectedSearchContextSpec: event.spec, - }, - } as VSCEState - // Type assertion is safe since existing context should be assignable to the existing state. - // debt: refactor switch statement to elegantly handle this event safely. - } - if (event.type === 'sidebar_query_update') { - return { - ...state, - context: { - ...state.context, - searchSidebarQueryState: { - proposedQueryState: event.proposedQueryState, - currentQueryState: event.currentQueryState, - }, - }, - } as VSCEState - // Type assertion is safe since existing context should be assignable to the existing state. - // debt: refactor switch statement to elegantly handle this event safely. - } - if (event.type === 'submit_search_query') { - return { - status: 'search-results', - context: { - ...state.context, - submittedSearchQueryState: event.submittedSearchQueryState, - searchResults: null, // Null out previous results. - }, - } - } - if (event.type === 'received_search_results' && state.context.submittedSearchQueryState) { - return { - status: 'search-results', - context: { - ...state.context, - submittedSearchQueryState: state.context.submittedSearchQueryState, - searchResults: event.searchResults, - }, - } - } - - switch (state.status) { - case 'search-home': - case 'search-results': { - switch (event.type) { - case 'search_panel_disposed': { - return { - ...state, - status: 'search-home', - context: { - ...state.context, - submittedSearchQueryState: null, - searchResults: null, - }, - } - } - - case 'search_panel_unfocused': { - return { - ...state, - status: 'idle', - } - } - - case 'remote_file_focused': { - return { - ...state, - status: 'remote-browsing', - } - } - } - return state - } - - case 'remote-browsing': { - switch (event.type) { - case 'search_panel_focused': { - if (state.context.submittedSearchQueryState) { - return { - status: 'search-results', - context: { - ...state.context, - submittedSearchQueryState: state.context.submittedSearchQueryState, - }, - } - } - - return { - ...state, - status: 'search-home', - } - } - case 'remote_file_unfocused': { - return { - ...state, - status: 'idle', - } - } - } - - return state - } - - case 'idle': { - switch (event.type) { - case 'search_panel_focused': { - if (state.context.submittedSearchQueryState) { - return { - status: 'search-results', - context: { - ...state.context, - submittedSearchQueryState: state.context.submittedSearchQueryState, - }, - } - } - - return { - ...state, - status: 'search-home', - } - } - - case 'remote_file_focused': { - return { - ...state, - status: 'remote-browsing', - } - } - } - - return state - } - } - } - - return { - get state() { - return cloneDeep(states.value) - }, - observeState: () => states.asObservable(), - emit: event => { - const nextState = reducer(states.value, event) - states.next(nextState) - }, - } -} diff --git a/client/vscode/src/vsCodeApi.ts b/client/vscode/src/vsCodeApi.ts deleted file mode 100644 index 0e99d6252ad..00000000000 --- a/client/vscode/src/vsCodeApi.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Built-in VS Code API exposed to webviews to communicate with the "Core" extension. - * We typically use this as a low-level building block for the APIs used in our webviews - * (wrapped w/ Comlink). - */ -export interface VsCodeApi { - postMessage: (message: any) => void - getState: () => State | undefined - setState: (state: State) => void -} diff --git a/client/vscode/src/webview/comlink/extensionEndpoint.ts b/client/vscode/src/webview/comlink/extensionEndpoint.ts deleted file mode 100644 index bdc061b8f68..00000000000 --- a/client/vscode/src/webview/comlink/extensionEndpoint.ts +++ /dev/null @@ -1,147 +0,0 @@ -import * as Comlink from 'comlink' -import type vscode from 'vscode' - -import type { EndpointPair } from '@sourcegraph/shared/src/platform/context' - -import { - generateUUID, - isNestedConnection, - isProxyMarked, - isUnsubscribable, - type NestedConnectionData, - type RelationshipType, -} from '.' - -// Used to scope message to panel (and `connectionId` further scopes to function call). -let nextPanelId = 1 - -const endpointFactories = new Map Comlink.Endpoint) | undefined>() - -const vscodeExtensionProxyTransferHandler: Comlink.TransferHandler< - object, - // Receive panelId (in deserialize), no need to send it. - // Return proxyMarkedValue in serialize so we can expose it in postMessage, but - // only end up sending the nestedConnectionId - { nestedConnectionId: string; proxyMarkedValue?: object; panelId?: string; relationshipType: RelationshipType } -> = { - canHandle: isProxyMarked, - serialize: proxyMarkedValue => { - const nestedConnectionId = generateUUID() - // Defer endpoint creation/object exposition to `postMessage` (to scope it to panel) - - return [{ nestedConnectionId, proxyMarkedValue, relationshipType: 'webToNode' }, []] - }, - deserialize: serialized => { - // Create endpoint, return wrapped proxy. - const endpointFactory = endpointFactories.get(serialized.panelId!)! - const endpoint = endpointFactory(serialized.nestedConnectionId) - const proxy = Comlink.wrap(endpoint) - - return proxy - }, -} - -Comlink.transferHandlers.set('proxy', vscodeExtensionProxyTransferHandler) - -export function createEndpointsForWebview( - panel: Pick -): EndpointPair & { panelId: string } { - const listenerDisposables = new WeakMap() - const panelId = nextPanelId.toString() - nextPanelId++ - let disposed = false - - // Keep track of proxied unsubscribables to clean up when a webview is closed. In that case, - // the webview will likely be unable to send an unsubscribe message. - const proxiedUnsubscribables = new Set<{ unsubscribe: () => unknown }>() - - /** - * Handles values sent to webviews that are marked to be proxied. - */ - function toWireValue(value: NestedConnectionData): void { - const proxyMarkedValue = value.proxyMarkedValue! - // The proxyMarkedValue is probably not cloneable, so don't - // send it "over the wire" - delete value.proxyMarkedValue - - if (isUnsubscribable(proxyMarkedValue)) { - proxiedUnsubscribables.add(proxyMarkedValue) - // Debt: ideally remove unsubscribable from set when we receive a unsubscribe message. - } - - const endpoint = createEndpoint(value.nestedConnectionId) - Comlink.expose(proxyMarkedValue, endpoint) - } - - function createEndpoint(connectionId: string): Comlink.Endpoint { - return { - postMessage: (message: any) => { - const value = message.value - const argumentList = message.argumentList - - if (isNestedConnection(value)) { - toWireValue(value) - } - if (Array.isArray(argumentList)) { - for (const argument of argumentList) { - const value = argument.value - if (isNestedConnection(value)) { - toWireValue(value) - } - } - } - - if (!disposed) { - panel.webview.postMessage({ ...message, connectionId, panelId }).then( - () => {}, - error => console.error('postMessage error', error) - ) - } - }, - addEventListener: (type, listener) => { - // This event listener will be called for all proxy method calls. - // Comlink will send the message to the appropriate caller - // based on UUID (generated internally). - function onMessage(message: any): void { - if (message?.connectionId === connectionId) { - // Comlink is listening for a message event, only uses the `data` property. - const messageEvent = { - data: message, - } as MessageEvent - - return typeof listener === 'function' - ? listener(messageEvent) - : listener.handleEvent(messageEvent) - } - } - - const disposable = panel.webview.onDidReceiveMessage(onMessage) - listenerDisposables.set(listener, disposable) - }, - removeEventListener: (type, listener) => { - const disposable = listenerDisposables.get(listener) - disposable?.dispose() - listenerDisposables.delete(listener) - }, - } - } - - endpointFactories.set(panelId, createEndpoint) - panel.onDidDispose(() => { - disposed = true - endpointFactories.delete(panelId) - - for (const unsubscribable of proxiedUnsubscribables) { - unsubscribable.unsubscribe() - } - }) - - const webviewEndpoint = createEndpoint('webview') - const extensionEndpoint = createEndpoint('extension') - - return { - proxy: webviewEndpoint, - expose: extensionEndpoint, - panelId, - } -} diff --git a/client/vscode/src/webview/comlink/index.ts b/client/vscode/src/webview/comlink/index.ts deleted file mode 100644 index dc8e48c2b6b..00000000000 --- a/client/vscode/src/webview/comlink/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as Comlink from 'comlink' -import { isObject } from 'lodash' - -import { hasProperty } from '@sourcegraph/common' - -// Sourcegraph VS Code extension Comlink "communication layer" documentation -// ----- -// NOTE: This is a working work-in-progress. We should be able to swap out this -// "communication layer" with either our own VS Code webview RPC solution -// or by applying this adapter to a fork of Comlink. - -// MOTIVATION: -// Comlink is a great library that makes it easy to work with Web Workers: https://github.com/GoogleChromeLabs/comlink. -// We use it to implement a bi-directional communication channel between our web application/browser extension and -// our Sourcegraph extension host. Given the need to sync state between {search panel <- extension "Core" -> search webview}, -// Comlink was a natural fit. However, we needed to implement some hacky adapters to get it to work for our needs: - -// web <-> web: Default Comlink use case. Depends on ability to transfer `MessageChannel`s, which -// is built into browsers. -// web <-> node: Cannot transfer `MessageChannel`s between VS Code webviews and Node.js extension "Core", -// so we have to hijack Comlink's Proxy transfer handler to manage nested Proxied objects ourselves. -// Since the transfer handler registry is global, a web context that needs to communicate with Node.js will -// have pushed out the default Proxy transfer handler out and therefore needs to manage web to web messages as well. - -// Consequently, there are three endpoint generators (all return endpoints for both directions): -// - extension <-> webview (extension context) -// - web <-> extension (webview context) -// - web <-> web (webview context, used for Sourcegraph extension host Web Worker) - -export function generateUUID(): string { - return new Array(4) - .fill(0) - .map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)) - .join('-') -} - -export type RelationshipType = 'webToWeb' | 'webToNode' - -export interface NestedConnectionData { - nestedConnectionId: string - proxyMarkedValue?: object - panelId: string - relationshipType?: RelationshipType -} - -export function isNestedConnection(value: unknown): value is NestedConnectionData { - return isObject(value) && hasProperty('nestedConnectionId')(value) && hasProperty('proxyMarkedValue')(value) -} - -export function isProxyMarked(value: unknown): value is Comlink.ProxyMarked { - return isObject(value) && (value as Comlink.ProxyMarked)[Comlink.proxyMarker] -} - -export function isUnsubscribable(value: object): value is { unsubscribe: () => unknown } { - return hasProperty('unsubscribe')(value) && typeof value.unsubscribe === 'function' -} diff --git a/client/vscode/src/webview/comlink/webviewEndpoint.ts b/client/vscode/src/webview/comlink/webviewEndpoint.ts deleted file mode 100644 index a3da87c7c29..00000000000 --- a/client/vscode/src/webview/comlink/webviewEndpoint.ts +++ /dev/null @@ -1,199 +0,0 @@ -import * as Comlink from 'comlink' -import { isObject } from 'lodash' - -import type { EndpointPair } from '@sourcegraph/shared/src/platform/context' - -import type { VsCodeApi } from '../../vsCodeApi' - -import { generateUUID, isNestedConnection, type NestedConnectionData, type RelationshipType } from '.' - -const panelId = self.document ? self.document.documentElement.dataset.panelId! : 'web-worker' - -const endpointFactories: { - webToWeb?: (connectionId: string) => Comlink.Endpoint - webToNode?: (connectionId: string) => Comlink.Endpoint -} = {} - -const vscodeWebviewProxyTransferHandler: Comlink.TransferHandler< - object, - // Send panelId (in serialize), no need to receive it. - // Return proxyMarkedValue in serialize so we can expose it in postMessage, but - // only end up sending the nestedConnectionId (and relationshipType for `webToWeb`) - { nestedConnectionId: string; proxyMarkedValue?: object; relationshipType?: RelationshipType; panelId?: string } -> = { - canHandle: (value): value is Comlink.ProxyMarked => - isObject(value) && (value as Comlink.ProxyMarked)[Comlink.proxyMarker], - serialize: proxyMarkedValue => { - const nestedConnectionId = generateUUID() - - // Add relationshipType in `postMessage` - return [{ nestedConnectionId, proxyMarkedValue, panelId }, []] - }, - deserialize: serialized => { - // Get endpoint factory based on relationship type - const endpointFactory = - endpointFactories[serialized.relationshipType === 'webToWeb' ? 'webToWeb' : 'webToNode']! - - // Create endpoint, return wrapped proxy. - const endpoint = endpointFactory(serialized.nestedConnectionId) - const proxy = Comlink.wrap(endpoint) - - return proxy - }, -} - -Comlink.transferHandlers.set('proxy', vscodeWebviewProxyTransferHandler) - -/** - * - * @param target Typically a WebWorker or `self` from a WebWorker (`Endpoint` for main thread). - */ -export function createEndpointsForWebToWeb(target: Comlink.Endpoint): { - webview: Comlink.Endpoint - worker: Comlink.Endpoint -} { - const onMessages = new WeakMap() - - /** - * Handles values sent to webviews that are marked to be proxied. - */ - function toWireValue(value: NestedConnectionData): void { - const proxyMarkedValue = value.proxyMarkedValue! - // The proxyMarkedValue is probably not cloneable, so don't - // send it "over the wire" - delete value.proxyMarkedValue - - value.relationshipType = 'webToWeb' - - const endpoint = createEndpoint(value.nestedConnectionId) - Comlink.expose(proxyMarkedValue, endpoint) - } - - function createEndpoint(connectionId: string): Comlink.Endpoint { - return { - postMessage: message => { - // Add relationship type to all nested connection values (i.e values to be proxied). - // TODO is the above necessary for this endpoint type? - const value = message.value - const argumentList = message.argumentList - - if (isNestedConnection(value)) { - toWireValue(value) - } - if (Array.isArray(argumentList)) { - for (const argument of argumentList) { - const value = argument.value - if (isNestedConnection(value)) { - toWireValue(value) - } - } - } - - target.postMessage({ ...message, connectionId, panelId }) - }, - - addEventListener: (type, listener) => { - // This event listener will be called for all proxy method calls. - // Comlink will send the message to the appropriate caller - // based on UUID (generated internally). - - function onMessage(event: MessageEvent): void { - if (event.data?.connectionId === connectionId) { - return typeof listener === 'function' ? listener(event) : listener.handleEvent(event) - } - } - - onMessages.set(listener, onMessage as EventListener) - - target.addEventListener('message', onMessage as EventListener) - }, - removeEventListener: (type, listener) => { - const onMessage = onMessages.get(listener) - target.removeEventListener('message', onMessage ?? listener) - }, - } - } - - endpointFactories.webToWeb = createEndpoint - - const webview = createEndpoint('webview') - const worker = createEndpoint('worker') - - return { - webview, - worker, - } -} - -export function createEndpointsForWebToNode(vscodeApi: VsCodeApi): EndpointPair { - const onMessages = new WeakMap() - - /** - * Handles values sent to the VS Code extension that are marked to be proxied. - */ - function toWireValue(value: NestedConnectionData): void { - const proxyMarkedValue = value.proxyMarkedValue! - // The proxyMarkedValue is probably not cloneable, so don't - // send it "over the wire" - delete value.proxyMarkedValue - - value.relationshipType = 'webToNode' - - const endpoint = createEndpoint(value.nestedConnectionId) - Comlink.expose(proxyMarkedValue, endpoint) - } - - function createEndpoint(connectionId: string): Comlink.Endpoint { - return { - postMessage: message => { - // Add relationship type to all nested connection values (i.e values to be proxied). - // TODO is the above necessary for this endpoint type? - const value = message.value - const argumentList = message.argumentList - - if (isNestedConnection(value)) { - toWireValue(value) - } - if (Array.isArray(argumentList)) { - for (const argument of argumentList) { - const value = argument.value - if (isNestedConnection(value)) { - toWireValue(value) - } - } - } - - vscodeApi.postMessage({ ...message, connectionId, panelId }) - }, - - addEventListener: (type, listener) => { - // This event listener will be called for all proxy method calls. - // Comlink will send the message to the appropriate caller - // based on UUID (generated internally). - function onMessage(event: MessageEvent): void { - if (event.data?.connectionId === connectionId) { - return typeof listener === 'function' ? listener(event) : listener.handleEvent(event) - } - } - - onMessages.set(listener, onMessage as EventListener) - - window.addEventListener('message', onMessage) - }, - removeEventListener: (type, listener) => { - const onMessage = onMessages.get(listener) - window.removeEventListener('message', onMessage ?? listener) - }, - } - } - - endpointFactories.webToNode = createEndpoint - - const extensionEndpoint = createEndpoint('extension') - const webviewEndpoint = createEndpoint('webview') - - return { - proxy: extensionEndpoint, - expose: webviewEndpoint, - } -} diff --git a/client/vscode/src/webview/commands.ts b/client/vscode/src/webview/commands.ts deleted file mode 100644 index 531409035b5..00000000000 --- a/client/vscode/src/webview/commands.ts +++ /dev/null @@ -1,256 +0,0 @@ -import type { Observable } from 'rxjs' -import * as vscode from 'vscode' - -import polyfillEventSource from '@sourcegraph/shared/src/polyfills/vendor/eventSource' -import { LATEST_VERSION } from '@sourcegraph/shared/src/search/stream' - -import { getProxyAgent } from '../backend/fetch' -import type { initializeSourcegraphSettings } from '../backend/sourcegraphSettings' -import { initializeCodeIntel } from '../code-intel/initialize' -import type { ExtensionCoreAPI } from '../contract' -import type { SourcegraphFileSystemProvider } from '../file-system/SourcegraphFileSystemProvider' -import { SearchPatternType } from '../graphql-operations' -import { endpointRequestHeadersSetting, endpointSetting } from '../settings/endpointSetting' - -import { - initializeHelpSidebarWebview, - initializeSearchPanelWebview, - initializeSearchSidebarWebview, -} from './initialize' -import { scretTokenKey } from './platform/AuthProvider' - -// Track current active webview panel to make sure only one panel exists at a time -let currentSearchPanel: vscode.WebviewPanel | 'initializing' | undefined -let searchSidebarWebviewView: vscode.WebviewView | 'initializing' | undefined - -export function registerWebviews({ - context, - extensionCoreAPI, - initializedPanelIDs, - sourcegraphSettings, - fs, - instanceURL, -}: { - context: vscode.ExtensionContext - extensionCoreAPI: ExtensionCoreAPI - initializedPanelIDs: Observable - sourcegraphSettings: ReturnType - fs: SourcegraphFileSystemProvider - instanceURL: string -}): void { - // TODO if remote files are open from previous session, we need - // to focus search sidebar to activate code intel (load extension host) - - // Register URI Handler to resolve data sending back from Browser - const handleUri = async (uri: vscode.Uri): Promise => { - const token = new URLSearchParams(uri.query).get('code') - // TODO: Decrypt token - // TODO: Match returnedNonce to stored nonce - if (token && token.length > 8) { - await context.secrets.store(scretTokenKey, token) - await vscode.window.showInformationMessage('Token has been retreived and updated successfully') - } - } - - context.subscriptions.push( - vscode.window.registerUriHandler({ - handleUri, - }) - ) - - // Update `EventSource` Authorization header on access token / headers change. - // It will also be changed when the token has been changed --handled by Auth Provider - context.subscriptions.push( - vscode.workspace.onDidChangeConfiguration(async config => { - const session = await vscode.authentication.getSession(endpointSetting(), [], { forceNewSession: false }) - if (config.affectsConfiguration('sourcegraph.requestHeaders') && session) { - const newCustomHeaders = endpointRequestHeadersSetting() - polyfillEventSource( - session.accessToken ? { Authorization: `token ${session.accessToken}`, ...newCustomHeaders } : {}, - getProxyAgent() - ) - } - }) - ) - - // Open Sourcegraph search tab on `sourcegraph.search` command. - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.search', async () => { - // If text selected, submit search for it. Capture selection first. - const activeEditor = vscode.window.activeTextEditor - const selection = activeEditor?.selection - const selectedQuery = activeEditor?.document.getText(selection) - - // Focus search sidebar in case this command was the activation event, - // as opposed to visibiilty of sidebar. - if (!searchSidebarWebviewView) { - focusSearchSidebar() - } - - if (currentSearchPanel && currentSearchPanel !== 'initializing') { - currentSearchPanel.reveal() - } else if (!currentSearchPanel) { - sourcegraphSettings.refreshSettings() - - currentSearchPanel = 'initializing' - - const { webviewPanel, searchPanelAPI } = await initializeSearchPanelWebview({ - extensionUri: context.extensionUri, - extensionCoreAPI, - initializedPanelIDs, - }) - - currentSearchPanel = webviewPanel - - webviewPanel.onDidChangeViewState(() => { - if (webviewPanel.active) { - extensionCoreAPI.emit({ type: 'search_panel_focused' }) - focusSearchSidebar() - searchPanelAPI.focusSearchBox().catch(() => {}) - } - - if (webviewPanel.visible) { - searchPanelAPI.focusSearchBox().catch(() => {}) - } - - if (!webviewPanel.visible) { - // TODO emit event (should go to idle state if not remote browsing) - extensionCoreAPI.emit({ type: 'search_panel_unfocused' }) - } - }) - - webviewPanel.onDidDispose(() => { - currentSearchPanel = undefined - // Ideally focus last used sidebar tab on search panel close. In lieu of that (for v1), - // just focus the file explorer if the search sidebar is currently focused. - if (searchSidebarWebviewView !== 'initializing' && searchSidebarWebviewView?.visible) { - focusFileExplorer() - } - // Clear search result - extensionCoreAPI.emit({ type: 'search_panel_disposed' }) - }) - } - - if (selectedQuery) { - extensionCoreAPI.streamSearch(selectedQuery, { - patternType: SearchPatternType.standard, - caseSensitive: false, - version: LATEST_VERSION, - trace: undefined, - sourcegraphURL: instanceURL, - }) - } - }) - ) - - context.subscriptions.push( - vscode.window.registerWebviewViewProvider( - 'sourcegraph.searchSidebar', - { - // This typically will be called only once since `retainContextWhenHidden` is set to `true`. - resolveWebviewView: (webviewView, _context, _token) => { - const { searchSidebarAPI } = initializeSearchSidebarWebview({ - extensionUri: context.extensionUri, - extensionCoreAPI, - webviewView, - }) - searchSidebarWebviewView = webviewView - // Initialize search panel. - openSearchPanelCommand() - - initializeCodeIntel({ context, fs, searchSidebarAPI }) - - // Bring search panel back if it was previously closed on sidebar visibility change - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible) { - openSearchPanelCommand() - } - }) - }, - }, - { webviewOptions: { retainContextWhenHidden: true } } - ) - ) - - context.subscriptions.push( - vscode.window.registerWebviewViewProvider( - 'sourcegraph.helpSidebar', - { - // This typically will be called only once since `retainContextWhenHidden` is set to `true`. - resolveWebviewView: (webviewView, _context, _token) => { - initializeHelpSidebarWebview({ - extensionUri: context.extensionUri, - extensionCoreAPI, - webviewView, - }) - }, - }, - { webviewOptions: { retainContextWhenHidden: true } } - ) - ) - - // Clone Remote Git Repos Locally using VS Code Git API - // https://github.com/microsoft/vscode/issues/48428 - context.subscriptions.push( - vscode.commands.registerCommand('sourcegraph.gitClone', async () => { - const editor = vscode.window.activeTextEditor - if (!editor) { - throw new Error('No active editor') - } - const uri = editor.document.uri.path - const gitUrl = `https:/${uri.split('@')[0]}.git` - const vsCodeCloneUrl = `vscode://vscode.git/clone?url=${gitUrl}` - await vscode.env.openExternal(vscode.Uri.parse(vsCodeCloneUrl)) - // vscode://vscode.git/clone?url=${gitUrl} - }) - ) -} - -function openSearchPanelCommand(): void { - vscode.commands.executeCommand('sourcegraph.search').then( - () => {}, - error => { - console.error(error) - } - ) -} - -function focusSearchSidebar(): void { - vscode.commands.executeCommand('sourcegraph.searchSidebar.focus').then( - () => {}, - error => { - console.error(error) - } - ) -} - -export function focusSearchPanel(): void { - if (currentSearchPanel && currentSearchPanel !== 'initializing') { - currentSearchPanel.reveal() - } -} - -function focusFileExplorer(): void { - vscode.commands.executeCommand('workbench.view.explorer').then( - () => {}, - error => { - console.error(error) - } - ) -} - -export async function openSourcegraphLinks(uri: string): Promise { - await vscode.env.openExternal(vscode.Uri.parse(uri)) - return -} - -export async function copySourcegraphLinks(uri: string): Promise { - try { - await vscode.env.clipboard.writeText(uri) - await vscode.window.showInformationMessage('Link Copied!') - } catch (error) { - console.error('Error copying search link to clipboard:', error) - } - - return -} diff --git a/client/vscode/src/webview/index.scss b/client/vscode/src/webview/index.scss deleted file mode 100644 index 10995437441..00000000000 --- a/client/vscode/src/webview/index.scss +++ /dev/null @@ -1,131 +0,0 @@ -// Global styles provided by @reach packages. Should be imported once in the global scope. -@import '@reach/tabs/styles'; - -@import 'wildcard/src/global-styles/base'; -@import './theming/highlight'; - -:root { - // v2/debt: redefine our CSS variables using VS Code's CSS variables - // instead of hackily overriding the necessary classes' properties. - .theme-light, - .theme-dark { - --body-color: var(--vscode-foreground); - --code-bg: var(--vscode-editor-background); - --color-bg-1: var(--vscode-editor-background); - --color-bg-2: var(--vscode-editorWidget-background); - --border-color: var(--vscode-editor-lineHighlightBorder); - --border-color-2: var(--vscode-editor-lineHighlightBorder); - - // VS Code themes cannot change border radius, so we can safely hardcode it. - --border-radius: 0; - --popover-border-radius: 0; - --dropdown-bg: var(--vscode-dropdown-background); - --dropdown-border-color: var(--vscode-dropdown-border); - --dropdown-header-color: var(--vscode-panelTitle-activeForeground); - - .dropdown-menu { - --body-color: var(--vscode-dropdown-foreground); - --primary: var(--vscode-textLink-foreground); // hover background - --color-bg-3: var(--color-bg-3); // active background - - /* stylelint-disable-next-line scss/selector-no-redundant-nesting-selector */ - & input { - background-color: var(--vscode-input-background) !important; - } - } - - --input-bg: var(--vscode-editorWidget-background); - --input-border-color: var(--vscode-input-border); - --border-active-color: var(--vscode-focusBorder); - --link-color: var(--vscode-textLink-foreground); - --search-filter-keyword-color: var(--vscode-textLink-foreground); - --body-bg: var(--vscode-editor-background); - --text-muted: var(--vscode-descriptionForeground); - --primary: var(--vscode-button-background); - - // Debt: alert overrides - --info-3: var(--vscode-inputValidation-infoBorder); - --danger: var(--vscode-inputValidation-errorBackground); - } - - .theme-dark { - .sourcegraph-tooltip { - --tooltip-bg: var(--vscode-input-background); - --tooltip-color: var(--vscode-editorWidget-foreground); - } - } - - .theme-light { - // Ensure tooltip always has a dark background with light text. - .sourcegraph-tooltip { - --tooltip-bg: var(--vscode-editorWidget-foreground); - --tooltip-color: var(--vscode-input-background); - } - } - - --max-homepage-container-width: 65rem; - - // Media breakpoints - --media-sm: 576px; - --media-md: 768px; - --media-lg: 992px; - --media-xl: 1200px; -} - -body { - font-family: var(--vscode-font-family); - font-weight: var(--vscode-font-weight); - font-size: var(--vscode-editor-font-size); - - --body-color: var(--vscode-dropdown-foreground); - - &.search-sidebar { - background-color: transparent !important; - } -} - -code { - font-family: var(--vscode-editor-font-family) !important; - font-weight: var(--vscode-editor-font-weight) !important; - font-size: var(--vscode-editor-font-size) !important; - color: var(--vscode-editor-foreground); -} - -.btn-primary { - color: var(--vscode-button-foreground) !important; - - &:hover { - background-color: var(--vscode-button-hoverBackground) !important; - } - - &:disabled { - background-color: var(--vscode-button-secondaryBackground) !important; - } -} - -.btn-text-link { - color: var(--vscode-textLink-foreground) !important; - padding: 0 !important; - margin: 0 !important; - font-weight: var(--vscode-editor-font-weight) !important; - font-size: var(--vscode-editor-font-size) !important; - background-color: transparent !important; - text-align: left; - - &:hover, - &:focus { - color: var(--vscode-foreground) !important; - } -} - -.input, -.form-control { - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); - padding: 0.5rem; - - &:focus { - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); - } -} diff --git a/client/vscode/src/webview/initialize.ts b/client/vscode/src/webview/initialize.ts deleted file mode 100644 index dc75b611a16..00000000000 --- a/client/vscode/src/webview/initialize.ts +++ /dev/null @@ -1,197 +0,0 @@ -import * as Comlink from 'comlink' -import type { Observable } from 'rxjs' -import { filter, first } from 'rxjs/operators' -import * as vscode from 'vscode' - -import type { ExtensionCoreAPI, HelpSidebarAPI, SearchPanelAPI, SearchSidebarAPI } from '../contract' -import { endpointSetting } from '../settings/endpointSetting' - -import { createEndpointsForWebview } from './comlink/extensionEndpoint' - -interface SourcegraphWebviewConfig { - extensionUri: vscode.Uri - extensionCoreAPI: ExtensionCoreAPI -} - -export async function initializeSearchPanelWebview({ - extensionUri, - extensionCoreAPI, - initializedPanelIDs, -}: SourcegraphWebviewConfig & { - initializedPanelIDs: Observable -}): Promise<{ - searchPanelAPI: Comlink.Remote - webviewPanel: vscode.WebviewPanel -}> { - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - const extensionsDistributionPath = vscode.Uri.joinPath(extensionUri, 'dist', 'extensions') - - const panel = vscode.window.createWebviewPanel('sourcegraphSearch', 'Sourcegraph', vscode.ViewColumn.One, { - enableScripts: true, - retainContextWhenHidden: true, - enableFindWidget: true, - localResourceRoots: [webviewPath, extensionsDistributionPath], - }) - - const extensionsDistributionWebviewPath = panel.webview.asWebviewUri(extensionsDistributionPath) - const scriptSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchPanel.js')) - const cssModuleSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchPanel.css')) - const styleSource = panel.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) - - const { proxy, expose, panelId } = createEndpointsForWebview(panel) - - // Wait for the webview to initialize or else messages will be dropped - const hasInitialized = initializedPanelIDs - .pipe( - filter(initializedPanelId => initializedPanelId === panelId), - first() - ) - .toPromise() - - // Get a proxy for the search panel API to communicate with the Webview. - const searchPanelAPI = Comlink.wrap(proxy) - - // Expose the "Core" extension API to the Webview. - Comlink.expose(extensionCoreAPI, expose) - - panel.iconPath = vscode.Uri.joinPath(extensionUri, 'images', 'logo.svg') - - // Apply Content-Security-Policy - // panel.webview.cspSource comes from the webview object - panel.webview.html = ` - - - - - - Sourcegraph Search - - - - -
- - - ` - - await hasInitialized - - return { - searchPanelAPI, - webviewPanel: panel, - } -} - -// TODO expand CSP for Sourcegraph extension loading -export function initializeSearchSidebarWebview({ - extensionUri, - extensionCoreAPI, - webviewView, -}: SourcegraphWebviewConfig & { - webviewView: vscode.WebviewView -}): { - searchSidebarAPI: Comlink.Remote -} { - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - const extensionsDistributionPath = vscode.Uri.joinPath(extensionUri, 'dist', 'extensions') - const extensionsDistributionWebviewPath = webviewView.webview.asWebviewUri(extensionsDistributionPath) - - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [webviewPath, extensionsDistributionPath], - } - - const scriptSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchSidebar.js')) - const cssModuleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'searchSidebar.css')) - const styleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) - - const { proxy, expose, panelId } = createEndpointsForWebview(webviewView) - - // Get a proxy for the Sourcegraph Webview API to communicate with the Webview. - const searchSidebarAPI = Comlink.wrap(proxy) - - // Expose the Sourcegraph VS Code Extension API to the Webview. - Comlink.expose(extensionCoreAPI, expose) - - // Apply Content-Security-Policy - webviewView.webview.html = ` - - - - - - Sourcegraph Search - - - - -
- - - ` - - return { - searchSidebarAPI, - } -} - -export function initializeHelpSidebarWebview({ - extensionUri, - extensionCoreAPI, - webviewView, -}: SourcegraphWebviewConfig & { - webviewView: vscode.WebviewView -}): { - helpSidebarAPI: Comlink.Remote -} { - const webviewPath = vscode.Uri.joinPath(extensionUri, 'dist', 'webview') - - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [webviewPath], - } - - const scriptSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'helpSidebar.js')) - const cssModuleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'helpSidebar.css')) - const styleSource = webviewView.webview.asWebviewUri(vscode.Uri.joinPath(webviewPath, 'style.css')) - - const { proxy, expose, panelId } = createEndpointsForWebview(webviewView) - - // Get a proxy for the Sourcegraph Webview API to communicate with the Webview. - const helpSidebarAPI = Comlink.wrap(proxy) - - // Expose the Sourcegraph VS Code Extension API to the Webview. - Comlink.expose(extensionCoreAPI, expose) - - // Apply Content-Security-Policy - webviewView.webview.html = ` - - - - - - Help and Feedback - - - -
- - - ` - - return { - helpSidebarAPI, - } -} diff --git a/client/vscode/src/webview/platform/AuthProvider.ts b/client/vscode/src/webview/platform/AuthProvider.ts deleted file mode 100644 index dfa644d4ce7..00000000000 --- a/client/vscode/src/webview/platform/AuthProvider.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - authentication, - type AuthenticationProvider, - type AuthenticationProviderAuthenticationSessionsChangeEvent, - type AuthenticationSession, - commands, - Disposable, - type Event, - EventEmitter, - type SecretStorage, -} from 'vscode' - -import polyfillEventSource from '@sourcegraph/shared/src/polyfills/vendor/eventSource' - -import { getProxyAgent } from '../../backend/fetch' -import { endpointRequestHeadersSetting, endpointSetting, setEndpoint } from '../../settings/endpointSetting' - -export const scretTokenKey = 'SOURCEGRAPH_AUTH' - -class SourcegraphAuthSession implements AuthenticationSession { - public readonly account = { - id: SourcegraphAuthProvider.id, - label: SourcegraphAuthProvider.label, - } - public readonly id = SourcegraphAuthProvider.id - public readonly scopes = [] - - constructor(public readonly accessToken: string) {} -} - -export class SourcegraphAuthProvider implements AuthenticationProvider, Disposable { - public static id = endpointSetting() - private static secretKey = scretTokenKey - public static label = scretTokenKey - - // Kept track of token changes through out the session - private currentToken: string | undefined - private initializedDisposable: Disposable | undefined - private _onDidChangeSessions = new EventEmitter() - public get onDidChangeSessions(): Event { - return this._onDidChangeSessions.event - } - - constructor(private readonly secretStorage: SecretStorage) {} - - public dispose(): void { - this.initializedDisposable?.dispose() - } - - private async ensureInitialized(): Promise { - if (this.initializedDisposable === undefined) { - await this.cacheTokenFromStorage() - this.initializedDisposable = Disposable.from( - this.secretStorage.onDidChange(async event => { - if (event.key === SourcegraphAuthProvider.secretKey) { - await this.checkForUpdates() - } - }), - authentication.onDidChangeSessions(async event => { - if (event.provider.id === SourcegraphAuthProvider.id) { - await this.checkForUpdates() - } - }) - ) - } - } - - // Check if token has been updated across VS Code - private async checkForUpdates(): Promise { - const added: AuthenticationSession[] = [] - const removed: AuthenticationSession[] = [] - const changed: AuthenticationSession[] = [] - const previousToken = this.currentToken - const session = (await this.getSessions())[0] - if (session?.accessToken && !previousToken) { - added.push(session) - } else if (!session?.accessToken && previousToken) { - removed.push(session) - } else if (session?.accessToken !== previousToken) { - changed.push(session) - } else { - return - } - await this.cacheTokenFromStorage() - // Update the polyfillEventSource on token changes - polyfillEventSource( - this.currentToken - ? { Authorization: `token ${this.currentToken}`, ...endpointRequestHeadersSetting() } - : {}, - getProxyAgent() - ) - this._onDidChangeSessions.fire({ added, removed, changed }) - } - - // Get token from Storage - private async cacheTokenFromStorage(): Promise { - const token = await this.secretStorage.get(SourcegraphAuthProvider.secretKey) - this.currentToken = token - return this.currentToken - } - - // This is called first when `vscode.authentication.getSessions` is called. - public async getSessions(_scopes?: string[]): Promise { - await this.ensureInitialized() - const token = await this.cacheTokenFromStorage() - return token ? [new SourcegraphAuthSession(token)] : [] - } - - // This is called after `this.getSessions` is called, - // and only when `createIfNone` or `forceNewSession` are set to true - public async createSession(_scopes: string[]): Promise { - await this.ensureInitialized() - // Get token from scret storage - let token = await this.secretStorage.get(SourcegraphAuthProvider.secretKey) - if (token) { - console.log('Successfully logged in to Sourcegraph', token) - await this.secretStorage.store(SourcegraphAuthProvider.secretKey, token) - } - if (!token) { - token = '' - } - return new SourcegraphAuthSession(token) - } - - // To sign out - public async removeSession(_sessionId: string): Promise { - await this.secretStorage.delete(SourcegraphAuthProvider.secretKey) - console.log('Successfully logged out of Sourcegraph') - } -} - -export class SourcegraphAuthActions { - private currentEndpoint = endpointSetting() - - constructor(private readonly secretStorage: SecretStorage) {} - - public async login(newtoken: string, newuri: string): Promise { - try { - await this.secretStorage.store(scretTokenKey, newtoken) - if (this.currentEndpoint !== newuri) { - await setEndpoint(newuri) - } - return - } catch (error) { - console.error(error) - } - } - - public async logout(): Promise { - await this.secretStorage.delete(scretTokenKey) - await commands.executeCommand('workbench.action.reloadWindow') - return - } -} diff --git a/client/vscode/src/webview/platform/EventLogger.ts b/client/vscode/src/webview/platform/EventLogger.ts deleted file mode 100644 index 89a81db944b..00000000000 --- a/client/vscode/src/webview/platform/EventLogger.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type * as Comlink from 'comlink' -import * as uuid from 'uuid' - -import { EventSource, type Event as EventType } from '@sourcegraph/shared/src/graphql-operations' - -import { version } from '../../../package.json' -import type { ExtensionCoreAPI } from '../../contract' -import { ANONYMOUS_USER_ID_KEY } from '../../settings/LocalStorageService' - -import type { VsceTelemetryService } from './telemetryService' - -// Event Logger for VS Code Extension -export class EventLogger implements VsceTelemetryService { - private anonymousUserID = '' - private evenSourceType = EventSource.BACKEND || EventSource.IDEEXTENSION - private eventID = 0 - private listeners: Set<(eventName: string) => void> = new Set() - private vsceAPI: Comlink.Remote - private newInstall = false - private editorInfo = { editor: 'vscode', version } - - constructor(extensionAPI: Comlink.Remote) { - this.vsceAPI = extensionAPI - this.initializeLogParameters() - .then(() => {}) - .catch(() => {}) - } - - /** - * @deprecated use logPageView instead - * - * Log a pageview event (by sending it to the server). - */ - public logViewEvent(pageTitle: string, eventProperties?: any, publicArgument?: any, url?: string): void { - if (pageTitle) { - this.tracker( - `View${pageTitle}`, - { ...eventProperties, ...this.editorInfo }, - { ...publicArgument, ...this.editorInfo }, - url - ) - } - } - - /** - * Log a pageview. - * Page titles should be specific and human-readable in pascal case, e.g. "SearchResults" or "Blob" or "NewOrg" - */ - public logPageView(eventName: string, eventProperties?: any, publicArgument?: any, url?: string): void { - if (eventName) { - this.tracker( - `${eventName}Viewed`, - { ...eventProperties, ...this.editorInfo }, - { ...publicArgument, ...this.editorInfo }, - url - ) - } - } - - /** - * Log a user action or event. - * Event labels should be specific and follow a ${noun}${verb} structure in pascal case, e.g. "ButtonClicked" or "SignInInitiated" - * - * @param eventLabel: the event name. - * @param eventProperties: event properties. These get logged to our database, but do not get - * sent to our analytics systems. This may contain private info such as repository names or search queries. - * @param publicArgument: event properties that include only public information. Do NOT - * include any private information, such as full URLs that may contain private repo names or - * search queries. The contents of this parameter are sent to our analytics systems. - */ - public log(eventLabel: string, eventProperties?: any, publicArgument?: any, uri?: string): void { - if (!eventLabel) { - return - } - switch (eventLabel) { - case 'DynamicFilterClicked': { - eventLabel = 'VSCESidebarDynamicFiltersClick' - break - } - case 'SearchSnippetClicked': { - eventLabel = 'VSCESidebarRepositoriesClick' - break - } - case 'SearchReferenceOpened': { - eventLabel = 'VSCESidebarSearchReferenceClick' - break - } - } - for (const listener of this.listeners) { - listener(eventLabel) - } - this.tracker( - eventLabel, - { ...eventProperties, ...this.editorInfo }, - { ...publicArgument, ...this.editorInfo }, - uri - ) - } - - /** - * Gets the anonymous user ID and cohort ID of the user from VSCE storage utility. - * If user doesn't have an anonymous user ID yet, a new one is generated - * And a new ide install event will be logged - */ - private async initializeLogParameters(): Promise { - let anonymousUserID = await this.vsceAPI.getLocalStorageItem(ANONYMOUS_USER_ID_KEY) - const source = await this.vsceAPI.getEventSource - if (!anonymousUserID) { - anonymousUserID = uuid.v4() - this.newInstall = true - await this.vsceAPI.setLocalStorageItem(ANONYMOUS_USER_ID_KEY, anonymousUserID) - } - this.anonymousUserID = anonymousUserID - this.evenSourceType = source - if (this.newInstall) { - this.log('IDEInstalled') - this.newInstall = false - } - } - - /** - * Get the anonymous identifier for this user (used to allow site admins - * on a Sourcegraph instance to see a count of unique users on a daily, - * weekly, and monthly basis). - */ - public getAnonymousUserID(): string { - return this.anonymousUserID - } - - /** - * Regular instance version format: 3.38.2 - * Insider version format: 134683_2022-03-02_5188fes0101 - */ - public getEventSourceType(): EventSource { - return this.evenSourceType - } - - /** - * Event ID is used to deduplicate events in Amplitude. - * This is used in the case that multiple events with the same userID and timestamp - * are sent. https://developers.amplitude.com/docs/http-api-v2#optional-keys - */ - public getEventID(): number { - this.eventID++ - return this.eventID - } - - public addEventLogListener(callback: (eventName: string) => void): () => void { - this.listeners.add(callback) - return () => this.listeners.delete(callback) - } - - public tracker(eventName: string, eventProperties?: unknown, publicArgument?: unknown, uri?: string): void { - const userEventVariables: EventType = { - event: eventName, - userCookieID: this.getAnonymousUserID(), - referrer: 'VSCE', - url: uri || '', - source: this.getEventSourceType(), - argument: eventProperties ? JSON.stringify(eventProperties) : null, - publicArgument: JSON.stringify(publicArgument), - deviceID: this.getAnonymousUserID(), - eventID: this.getEventID(), - } - this.vsceAPI - .logEvents(userEventVariables) - .then(() => {}) - .catch(error => console.log(error)) - } -} diff --git a/client/vscode/src/webview/platform/context.ts b/client/vscode/src/webview/platform/context.ts deleted file mode 100644 index 37c5715fc42..00000000000 --- a/client/vscode/src/webview/platform/context.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { createContext, useContext } from 'react' - -import type * as Comlink from 'comlink' -import { print } from 'graphql' -import { from, type Observable } from 'rxjs' - -import { checkOk, type GraphQLResult } from '@sourcegraph/http-client' -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import type { ExecutableExtension } from '@sourcegraph/shared/src/api/extension/activation' -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import type { PlatformContext } from '@sourcegraph/shared/src/platform/context' -import type { ExtensionManifest } from '@sourcegraph/shared/src/schema/extensionSchema' -import type { SettingsCascadeOrError } from '@sourcegraph/shared/src/settings/settings' - -// list of extensionID generated by build-inline-extensions script -import extensions from '../../../code-intel-extensions.json' -import type { ExtensionCoreAPI } from '../../contract' - -import { EventLogger } from './EventLogger' -import type { VsceTelemetryService } from './telemetryService' - -export interface VSCodePlatformContext - extends Pick< - PlatformContext, - | 'updateSettings' - | 'settings' - | 'getGraphQLClient' - | 'getStaticExtensions' - | 'telemetryService' - | 'clientApplication' - > { - // Ensure telemetryService is non-nullable. - telemetryService: VsceTelemetryService - requestGraphQL: (options: { - request: string - variables: V - mightContainPrivateInfo: boolean - overrideAccessToken?: string - overrideSourcegraphURL?: string - }) => Observable> -} - -export function createPlatformContext(extensionCoreAPI: Comlink.Remote): VSCodePlatformContext { - const context: VSCodePlatformContext = { - requestGraphQL({ request, variables, overrideAccessToken, overrideSourcegraphURL }) { - return from( - extensionCoreAPI.requestGraphQL(request, variables, overrideAccessToken, overrideSourcegraphURL) - ) - }, - // TODO add true Apollo Client support for v2 - getGraphQLClient: () => - Promise.resolve({ - watchQuery: ({ variables, query }) => - from(extensionCoreAPI.requestGraphQL(print(query), variables)) as any, - }), - settings: wrapRemoteObservable(extensionCoreAPI.observeSourcegraphSettings()), - // TODO: implement GQL mutation, settings refresh (called by extensions, impl w/ ext. host). - updateSettings: () => Promise.resolve(), - telemetryService: new EventLogger(extensionCoreAPI), - clientApplication: 'other', // TODO add 'vscode-extension' to `clientApplication`, - getStaticExtensions: () => getInlineExtensions(), - } - - return context -} - -export interface WebviewPageProps { - extensionCoreAPI: Comlink.Remote - platformContext: VSCodePlatformContext - authenticatedUser: AuthenticatedUser | null - settingsCascade: SettingsCascadeOrError - instanceURL: string -} - -// Webview page context. Used to pass to aliased components. -export const WebviewPageContext = createContext(undefined) - -export function useWebviewPageContext(): WebviewPageProps { - const context = useContext(WebviewPageContext) - - if (context === undefined) { - throw new Error('useWebviewPageContext must be used within a WebviewPageContextProvider') - } - - return context -} - -function getInlineExtensions(): Observable { - const promises: Promise[] = [] - - for (const extensionID of extensions) { - const { manifestURL, scriptURL } = getURLsForInlineExtension(extensionID) - promises.push( - fetch(manifestURL) - .then(response => checkOk(response).json()) - .then( - (manifest: ExtensionManifest): ExecutableExtension => ({ - id: extensionID, - manifest, - scriptURL, - }) - ) - ) - } - - return from(Promise.all(promises)) -} - -/** - * Get the manifest URL and script URL for a Sourcegraph extension which is inline (bundled with the browser add-on). - */ -function getURLsForInlineExtension(extensionID: string): { manifestURL: string; scriptURL: string } { - const extensionsDistributionPath = document.documentElement.dataset.extensionsDistPath! - const kebabCaseExtensionID = extensionID.replace(/^sourcegraph\//, 'sourcegraph-') - return { - manifestURL: `${extensionsDistributionPath}/${kebabCaseExtensionID}/package.json`, - scriptURL: `${extensionsDistributionPath}/${kebabCaseExtensionID}/extension.js`, - } -} diff --git a/client/vscode/src/webview/platform/polyfills/index.ts b/client/vscode/src/webview/platform/polyfills/index.ts deleted file mode 100644 index 69c60d36bb4..00000000000 --- a/client/vscode/src/webview/platform/polyfills/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '@sourcegraph/shared/src/polyfills/configure-core-js' -import './polyfill' diff --git a/client/vscode/src/webview/platform/polyfills/polyfill.ts b/client/vscode/src/webview/platform/polyfills/polyfill.ts deleted file mode 100644 index 10db08d3330..00000000000 --- a/client/vscode/src/webview/platform/polyfills/polyfill.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Polyfill URL because Chrome and Firefox are not spec-compliant -// Hostnames of URIs with custom schemes (e.g. git) are not parsed out -import 'core-js/web/url' diff --git a/client/vscode/src/webview/platform/telemetryService.ts b/client/vscode/src/webview/platform/telemetryService.ts deleted file mode 100644 index 2a9f96d339f..00000000000 --- a/client/vscode/src/webview/platform/telemetryService.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { noop } from 'lodash' - -import type { TelemetryService } from '@sourcegraph/shared/src/telemetry/telemetryService' - -/** - * Props interface that can be extended by React components depending on the TelemetryService. - */ -export interface VsceTelemetryProps { - /** - * A telemetry service implementation to log events. - */ - telemetryService: VsceTelemetryService -} - -/** - * The telemetry service logs events. - */ -export interface VsceTelemetryService extends TelemetryService { - /** - * Log an event (by sending it to the server). - * Provide uri manually for some events (e.g ViewRepository, ViewBlob) as webview does not provide link location - */ - log(eventName: string, eventProperties?: any, publicArgument?: any, uri?: string): void - /** - * @deprecated use logPageView instead - * - * Log a pageview event (by sending it to the server). - */ - logViewEvent(eventName: string, eventProperties?: any, publicArgument?: any, uri?: string): void - /** - * Log a pageview event (by sending it to the server). - * Adheres to the new event naming policy - */ - logPageView(eventName: string, eventProperties?: any, publicArgument?: any, uri?: string): void - /** - * Listen for event logs - * - * @returns a cleanup/removeEventListener function - */ - addEventLogListener?(callback: (eventName: string) => void): () => void -} - -/** - * A noop telemetry service. - * * Provide uri manually for some events - */ -export const NOOP_TELEMETRY_SERVICE: VsceTelemetryService = { - log: noop, - logViewEvent: noop, - logPageView: noop, -} diff --git a/client/vscode/src/webview/search-panel/MatchHandlersContext.ts b/client/vscode/src/webview/search-panel/MatchHandlersContext.ts deleted file mode 100644 index 03255c075bb..00000000000 --- a/client/vscode/src/webview/search-panel/MatchHandlersContext.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { createContext, useContext, useMemo } from 'react' - -import type * as Comlink from 'comlink' -import { noop } from 'lodash' - -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' -import type { RepositoryMatch } from '@sourcegraph/shared/src/search/stream' - -import type { ExtensionCoreAPI } from '../../contract' -import { SourcegraphUri, type SourcegraphUriOptionals } from '../../file-system/SourcegraphUri' -import type { VSCodePlatformContext } from '../platform/context' - -type MinimalRepositoryMatch = Pick - -export interface MatchHandlersContext { - openRepo: (repository: MinimalRepositoryMatch) => void - openFile: (repositoryName: string, optional?: SourcegraphUriOptionals) => void - openSymbol: (symbolUrl: string) => void - openCommit: (commitUrl: string) => void - instanceURL: string -} -export const MatchHandlersContext = createContext({ - // Initialize in `SearchResultsView` (via `useMatchHandlers`) - openRepo: noop, - openFile: noop, - openSymbol: noop, - openCommit: noop, - instanceURL: '', -}) - -export function useMatchHandlers({ - platformContext, - extensionCoreAPI, - onRepoSelected, - authenticatedUser, - instanceURL, -}: { - platformContext: VSCodePlatformContext - extensionCoreAPI: Comlink.Remote - onRepoSelected: (repositoryMatch: MinimalRepositoryMatch) => void - authenticatedUser: AuthenticatedUser | null - instanceURL: string -}): Omit { - const host = useMemo(() => new URL(instanceURL).host, [instanceURL]) - - const matchHandlers: Omit = useMemo( - () => ({ - openRepo: repositoryMatch => { - // noop, implementation in SearchResultsView component since the repo page depends on its state. - // nvm, pass "onRepoSelected" prop - onRepoSelected(repositoryMatch) - - extensionCoreAPI - .openSourcegraphFile(`sourcegraph://${host}/${repositoryMatch.repository}`) - .catch(error => { - console.error('Error opening Sourcegraph repository', error) - }) - // Log View Event to sync search history - // URL must be provided to render Recent Searches on Web - platformContext.telemetryService.logPageView( - 'Repository', - null, - authenticatedUser !== null, - `https://${host}/${repositoryMatch.repository}` - ) - }, - openFile: (repositoryName, optionals) => { - // Create sourcegraph URI - const sourcegraphUri = SourcegraphUri.fromParts(host, repositoryName, optionals) - - const uri = sourcegraphUri.uri + sourcegraphUri.positionSuffix() - - // Log View Event to sync search history - platformContext.telemetryService.logPageView( - 'Blob', - null, - authenticatedUser !== null, - sourcegraphUri.uri.replace('sourcegraph://', 'https://') - ) - - extensionCoreAPI - .openSourcegraphFile(uri) - .catch(error => console.error('Error opening Sourcegraph file', error)) - }, - openSymbol: (symbolUrl: string) => { - const { - path, - position, - revision, - repositoryName, - host: codeHost, - } = SourcegraphUri.parse(`https:/${symbolUrl}`, window.URL) - const sourcegraphUri = SourcegraphUri.fromParts(host, `${codeHost}/${repositoryName}`, { - revision, - path, - position: position - ? { - line: position.line - 1, // Convert to 1-based - character: position.character - 1, - } - : undefined, - }) - const uri = sourcegraphUri.uri + sourcegraphUri.positionSuffix() - - extensionCoreAPI.openSourcegraphFile(uri).catch(error => { - console.error('Error opening Sourcegraph file', error) - }) - }, - openCommit: commitUrl => { - const commitURL = new URL(commitUrl, instanceURL) - extensionCoreAPI.openLink(commitURL.href).catch(error => { - console.error('Error opening commit in browser', error) - }) - - // Roadmap: open diff in VS Code instead of Sourcegraph Web. - }, - }), - [extensionCoreAPI, platformContext, authenticatedUser, onRepoSelected, host, instanceURL] - ) - - return matchHandlers -} - -export function useOpenSearchResultsContext(): MatchHandlersContext { - return useContext(MatchHandlersContext) -} diff --git a/client/vscode/src/webview/search-panel/RepoView.module.scss b/client/vscode/src/webview/search-panel/RepoView.module.scss deleted file mode 100644 index 14adda22377..00000000000 --- a/client/vscode/src/webview/search-panel/RepoView.module.scss +++ /dev/null @@ -1,51 +0,0 @@ -.tree-entries-section { - // To avoid having empty columns (and thus the items appearing not flush with the left margin), - // the component only applies this class when there are >= 6 items. This number is chosen - // because it is greater than the maximum number of columns that will be shown and ensures that - // at least 1 column has more than 1 item. - // See also MIN_ENTRIES_FOR_COLUMN_LAYOUT. - &--columns { - column-gap: 1.5rem; - column-width: 13rem; - column-rule: 1px solid var(--border-color); - border-right: solid 1px var(--border-color); - - @media (--sm-breakpoint-up) { - column-count: 1; - } - @media (--md-breakpoint-up) { - column-count: 3; - } - @media (--md-breakpoint-down) { - column-count: 4; - } - } -} - -.tree-entry { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - margin-left: -0.25rem; - margin-right: -0.25rem; - padding: 0.125rem 0.25rem; - - break-inside: avoid-column; - - --focus-box-shadow: none; - - &:hover { - background-color: var(--color-bg-1); - } - - &--no-columns { - max-width: 18rem; - } -} - -.section { - width: 100%; - max-width: var(--media-xl); -} diff --git a/client/vscode/src/webview/search-panel/RepoView.tsx b/client/vscode/src/webview/search-panel/RepoView.tsx deleted file mode 100644 index 7aecdb40c29..00000000000 --- a/client/vscode/src/webview/search-panel/RepoView.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useMemo, useState } from 'react' - -import { mdiArrowLeft, mdiFileDocumentOutline, mdiFolderOutline, mdiSourceRepository } from '@mdi/js' -import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react' -import classNames from 'classnames' -import { catchError } from 'rxjs/operators' - -import { fetchTreeEntries } from '@sourcegraph/shared/src/backend/repo' -import { displayRepoName } from '@sourcegraph/shared/src/components/RepoLink' -import type { QueryState } from '@sourcegraph/shared/src/search' -import type { RepositoryMatch } from '@sourcegraph/shared/src/search/stream' -import { Icon, PageHeader, useObservable, H4, Text, Button } from '@sourcegraph/wildcard' - -import type { WebviewPageProps } from '../platform/context' - -import styles from './RepoView.module.scss' - -interface RepoViewProps extends Pick { - onBackToSearchResults: () => void - // Debt: just use repository name and make GraphQL Repository query to get metadata. - // This will enable more info (like description) when navigating here from file matches. - repositoryMatch: Pick - setQueryState: (query: QueryState) => void -} - -export const RepoView: React.FunctionComponent> = ({ - extensionCoreAPI, - platformContext, - repositoryMatch, - onBackToSearchResults, - instanceURL, - setQueryState, -}) => { - const [directoryStack, setDirectoryStack] = useState([]) - - // File tree results are memoized, so going back isn't expensive. - const treeEntries = useObservable( - useMemo( - () => - fetchTreeEntries({ - repoName: repositoryMatch.repository, - commitID: '', - revision: repositoryMatch.branches?.[0] ?? 'HEAD', - filePath: directoryStack.length > 0 ? directoryStack.at(-1)! : '', - requestGraphQL: platformContext.requestGraphQL, - }).pipe( - catchError(error => { - console.error(error, { repositoryMatch }) - // TODO: remove and add error boundary in searchresultsview - return [] - }) - ), - [platformContext, repositoryMatch, directoryStack] - ) - ) - - const onPreviousDirectory = (): void => { - const newDirectoryStack = directoryStack.slice(0, -1) - setQueryState({ - query: `repo:^${repositoryMatch.repository}$ ${ - newDirectoryStack.length > 0 ? `file:^${newDirectoryStack.at(-1)}` : '' - }`, - }) - setDirectoryStack(newDirectoryStack) - } - - const onSelect = (isDirectory: boolean, path: string, url: string): void => { - const host = new URL(instanceURL).host - if (isDirectory) { - setQueryState({ query: `repo:^${repositoryMatch.repository}$ file:^${path}` }) - setDirectoryStack([...directoryStack, path]) - } else { - extensionCoreAPI.openSourcegraphFile(`sourcegraph://${host}${url}`).catch(error => { - console.error('Error opening Sourcegraph file', error) - }) - } - } - - return ( -
- - {directoryStack.length > 0 && ( - - )} - - {repositoryMatch.description && {repositoryMatch.description}} -
-

Files and directories

- {treeEntries === undefined ? ( - - ) : ( -
- {treeEntries.entries.map(entry => ( - - ))} -
- )} -
-
- ) -} diff --git a/client/vscode/src/webview/search-panel/SearchHomeView.tsx b/client/vscode/src/webview/search-panel/SearchHomeView.tsx deleted file mode 100644 index 1fc7d52bcb7..00000000000 --- a/client/vscode/src/webview/search-panel/SearchHomeView.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react' - -import classNames from 'classnames' -import type { Observable } from 'rxjs' -import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect' - -import { SearchBox } from '@sourcegraph/branded' -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { getUserSearchContextNamespaces, type QueryState, SearchMode } from '@sourcegraph/shared/src/search' -import { collectMetrics } from '@sourcegraph/shared/src/search/query/metrics' -import { appendContextFilter, sanitizeQueryForTelemetry } from '@sourcegraph/shared/src/search/query/transformer' -import { LATEST_VERSION, type SearchMatch } from '@sourcegraph/shared/src/search/stream' -import { useIsLightTheme } from '@sourcegraph/shared/src/theme' - -import { SearchPatternType } from '../../graphql-operations' -import type { SearchHomeState } from '../../state' -import type { WebviewPageProps } from '../platform/context' - -import { fetchSearchContexts } from './alias/fetchSearchContext' -import { BrandHeader } from './components/BrandHeader' -import { HomeFooter } from './components/HomeFooter' - -import styles from './index.module.scss' - -export interface SearchHomeViewProps extends WebviewPageProps { - context: SearchHomeState['context'] -} - -export const SearchHomeView: React.FunctionComponent> = ({ - extensionCoreAPI, - authenticatedUser, - platformContext, - settingsCascade, - context, - instanceURL, -}) => { - const isLightTheme = useIsLightTheme() - - // Toggling case sensitivity or pattern type does NOT trigger a new search on home view. - const [caseSensitive, setCaseSensitivity] = useState(false) - const [patternType, setPatternType] = useState(SearchPatternType.standard) - const [searchMode, setSearchMode] = useState(SearchMode.SmartSearch) - - const [userQueryState, setUserQueryState] = useState({ - query: '', - }) - - const isSourcegraphDotCom = useMemo(() => { - const hostname = new URL(instanceURL).hostname - return hostname === 'sourcegraph.com' || hostname === 'www.sourcegraph.com' - }, [instanceURL]) - - const onSubmit = useCallback(() => { - extensionCoreAPI - .streamSearch(userQueryState.query, { - caseSensitive, - patternType, - searchMode, - version: LATEST_VERSION, - trace: undefined, - }) - .catch(error => { - // TODO surface error to users? Errors will typically be caught and - // surfaced throught streaming search reuls. - console.error(error) - }) - - extensionCoreAPI - .setSidebarQueryState({ - queryState: { query: userQueryState.query }, - searchCaseSensitivity: caseSensitive, - searchPatternType: patternType, - searchMode, - }) - .catch(error => { - // TODO surface error to users - console.error('Error updating sidebar query state from panel', error) - }) - - // Log Search History - const hostname = new URL(instanceURL).hostname - let queryString = `${userQueryState.query}${caseSensitive ? ' case:yes' : ''}` - if (context.selectedSearchContextSpec) { - queryString = appendContextFilter(queryString, context.selectedSearchContextSpec) - } - const metrics = queryString ? collectMetrics(queryString) : undefined - platformContext.telemetryService.log( - 'SearchResultsQueried', - { - code_search: { - query_data: { - query: metrics, - combined: queryString, - empty: !queryString, - }, - }, - }, - { - code_search: { - query_data: { - // 🚨 PRIVACY: never provide any private query data in the - // { code_search: query_data: query } property, - // which is also potentially exported in pings data. - query: metrics, - - // 🚨 PRIVACY: Only collect the full query string for unauthenticated users - // on Sourcegraph.com, and only after sanitizing to remove certain filters. - combined: - !authenticatedUser && isSourcegraphDotCom - ? sanitizeQueryForTelemetry(queryString) - : undefined, - empty: !queryString, - }, - }, - }, - `https://${hostname}/search?q=${encodeURIComponent(queryString)}&patternType=${patternType}` - ) - }, [ - extensionCoreAPI, - userQueryState.query, - caseSensitive, - patternType, - searchMode, - instanceURL, - context.selectedSearchContextSpec, - platformContext.telemetryService, - authenticatedUser, - isSourcegraphDotCom, - ]) - - // Update local query state on sidebar query state updates. - useDeepCompareEffectNoCheck(() => { - if (context.searchSidebarQueryState.proposedQueryState?.queryState) { - setUserQueryState(context.searchSidebarQueryState.proposedQueryState?.queryState) - } - }, [context.searchSidebarQueryState.proposedQueryState?.queryState]) - - const setSelectedSearchContextSpec = useCallback( - (spec: string) => { - extensionCoreAPI.setSelectedSearchContextSpec(spec).catch(error => { - console.error('Error persisting search context spec.', error) - }) - }, - [extensionCoreAPI] - ) - - const fetchStreamSuggestions = useCallback( - (query: string): Observable => - wrapRemoteObservable(extensionCoreAPI.fetchStreamSuggestions(query, instanceURL)), - [extensionCoreAPI, instanceURL] - ) - - return ( -
- - -
- {/* eslint-disable-next-line react/forbid-elements */} -
{ - event.preventDefault() - onSubmit() - }} - > - - - - -
-
- ) -} diff --git a/client/vscode/src/webview/search-panel/SearchResultsView.tsx b/client/vscode/src/webview/search-panel/SearchResultsView.tsx deleted file mode 100644 index 16f564bdb5f..00000000000 --- a/client/vscode/src/webview/search-panel/SearchResultsView.tsx +++ /dev/null @@ -1,420 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' - -import classNames from 'classnames' -import type { Observable } from 'rxjs' -import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect' - -import { type IEditor, SearchBox, StreamingProgress, StreamingSearchResultsList } from '@sourcegraph/branded' -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { type FetchFileParameters, fetchHighlightedFileLineRanges } from '@sourcegraph/shared/src/backend/file' -import { getUserSearchContextNamespaces, type QueryState, type SearchMode } from '@sourcegraph/shared/src/search' -import { collectMetrics } from '@sourcegraph/shared/src/search/query/metrics' -import { - appendContextFilter, - sanitizeQueryForTelemetry, - updateFilters, -} from '@sourcegraph/shared/src/search/query/transformer' -import { LATEST_VERSION, type RepositoryMatch, type SearchMatch } from '@sourcegraph/shared/src/search/stream' -import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url' - -import type { SearchPatternType } from '../../graphql-operations' -import type { SearchResultsState } from '../../state' -import type { WebviewPageProps } from '../platform/context' - -import { fetchSearchContexts } from './alias/fetchSearchContext' -import { setFocusSearchBox } from './api' -import { SearchResultsInfoBar } from './components/SearchResultsInfoBar' -import { MatchHandlersContext, useMatchHandlers } from './MatchHandlersContext' -import { RepoView } from './RepoView' - -import styles from './index.module.scss' - -export interface SearchResultsViewProps extends WebviewPageProps { - context: SearchResultsState['context'] -} - -export const SearchResultsView: React.FunctionComponent> = ({ - extensionCoreAPI, - authenticatedUser, - platformContext, - settingsCascade, - context, - instanceURL, -}) => { - const [userQueryState, setUserQueryState] = useState(context.submittedSearchQueryState.queryState) - const [repoToShow, setRepoToShow] = useState | null>(null) - - const isSourcegraphDotCom = useMemo(() => { - const hostname = new URL(instanceURL).hostname - return hostname === 'sourcegraph.com' || hostname === 'www.sourcegraph.com' - }, [instanceURL]) - - // Editor focus. - const editorReference = useRef() - const setEditor = useCallback((editor: IEditor) => { - editorReference.current = editor - setTimeout(() => editor.focus(), 0) - }, []) - - // TODO explain - useEffect(() => { - setFocusSearchBox(() => editorReference.current?.focus()) - - return () => { - setFocusSearchBox(null) - } - }, []) - - const onChange = useCallback( - (newState: QueryState) => { - setUserQueryState(newState) - - extensionCoreAPI - .setSidebarQueryState({ - queryState: newState, - searchCaseSensitivity: context.submittedSearchQueryState?.searchCaseSensitivity, - searchPatternType: context.submittedSearchQueryState?.searchPatternType, - searchMode: context.submittedSearchQueryState?.searchMode, - }) - .catch(error => { - // TODO surface error to users - console.error('Error updating sidebar query state from panel', error) - }) - }, - [ - extensionCoreAPI, - context.submittedSearchQueryState.searchCaseSensitivity, - context.submittedSearchQueryState.searchPatternType, - context.submittedSearchQueryState.searchMode, - ] - ) - - const [allExpanded, setAllExpanded] = useState(false) - const onExpandAllResultsToggle = useCallback(() => { - setAllExpanded(oldValue => !oldValue) - platformContext.telemetryService.log(allExpanded ? 'allResultsExpanded' : 'allResultsCollapsed') - }, [allExpanded, platformContext]) - - // Update local query state on sidebar query state updates. - useDeepCompareEffectNoCheck(() => { - if (context.searchSidebarQueryState.proposedQueryState?.queryState) { - setUserQueryState(context.searchSidebarQueryState.proposedQueryState?.queryState) - } - }, [context.searchSidebarQueryState.proposedQueryState?.queryState]) - - // Update local search query state on sidebar search submission. - useDeepCompareEffectNoCheck(() => { - setUserQueryState(context.submittedSearchQueryState.queryState) - // It's a whole new object on each state update, so we need - // to compare (alternatively, construct full query TODO) - - // Clear repo view - setRepoToShow(null) - }, [context.submittedSearchQueryState.queryState]) - - // Track sidebar + keyboard shortcut search submissions - useEffect(() => { - platformContext.telemetryService.log('IDESearchSubmitted') - }, [platformContext, context.submittedSearchQueryState.queryState.query]) - - const onSubmit = useCallback( - (options?: { - caseSensitive?: boolean - patternType?: SearchPatternType - newQuery?: string - searchMode?: SearchMode - }) => { - const previousSearchQueryState = context.submittedSearchQueryState - - const query = options?.newQuery ?? userQueryState.query - const caseSensitive = options?.caseSensitive ?? previousSearchQueryState.searchCaseSensitivity - const patternType = options?.patternType ?? previousSearchQueryState.searchPatternType - const searchMode = options?.searchMode ?? previousSearchQueryState.searchMode - - extensionCoreAPI - .streamSearch(query, { - caseSensitive, - patternType, - searchMode, - version: LATEST_VERSION, - trace: undefined, - chunkMatches: true, - }) - .then(() => { - editorReference.current?.focus() - }) - .catch(error => { - // TODO surface error to users? Errors will typically be caught and - // surfaced throught streaming search reuls. - console.error(error) - }) - - extensionCoreAPI - .setSidebarQueryState({ - queryState: { query }, - searchCaseSensitivity: caseSensitive, - searchPatternType: patternType, - searchMode, - }) - .catch(error => { - // TODO surface error to users - console.error('Error updating sidebar query state from panel', error) - }) - - // Log Search History - const hostname = new URL(instanceURL).hostname - let queryString = `${userQueryState.query}${caseSensitive ? ' case:yes' : ''}` - if (context.selectedSearchContextSpec) { - queryString = appendContextFilter(queryString, context.selectedSearchContextSpec) - } - const metrics = queryString ? collectMetrics(queryString) : undefined - platformContext.telemetryService.log( - 'SearchResultsQueried', - { - code_search: { - query_data: { - query: metrics, - combined: queryString, - empty: !queryString, - }, - }, - }, - { - code_search: { - query_data: { - // 🚨 PRIVACY: never provide any private query data in the - // { code_search: query_data: query } property, - // which is also potentially exported in pings data. - query: metrics, - - // 🚨 PRIVACY: Only collect the full query string for unauthenticated users - // on Sourcegraph.com, and only after sanitizing to remove certain filters. - combined: - !authenticatedUser && isSourcegraphDotCom - ? sanitizeQueryForTelemetry(queryString) - : undefined, - empty: !queryString, - }, - }, - }, - `https://${hostname}/search?q=${encodeURIComponent(queryString)}&patternType=${patternType}` - ) - // Clear repo view - setRepoToShow(null) - }, - [ - context.submittedSearchQueryState, - context.selectedSearchContextSpec, - userQueryState.query, - extensionCoreAPI, - instanceURL, - platformContext.telemetryService, - authenticatedUser, - isSourcegraphDotCom, - ] - ) - - // Submit new search on change - const setCaseSensitivity = useCallback( - (caseSensitive: boolean) => { - onSubmit({ caseSensitive }) - }, - [onSubmit] - ) - - // Submit new search on change - const setPatternType = useCallback( - (patternType: SearchPatternType) => { - onSubmit({ patternType }) - }, - [onSubmit] - ) - - const setSearchMode = useCallback( - (searchMode: SearchMode) => { - onSubmit({ searchMode }) - }, - [onSubmit] - ) - - const fetchHighlightedFileLineRangesWithContext = useCallback( - (parameters: FetchFileParameters) => fetchHighlightedFileLineRanges({ ...parameters, platformContext }), - [platformContext] - ) - - const fetchStreamSuggestions = useCallback( - (query: string): Observable => - wrapRemoteObservable(extensionCoreAPI.fetchStreamSuggestions(query, instanceURL)), - [extensionCoreAPI, instanceURL] - ) - - const setSelectedSearchContextSpec = useCallback( - (spec: string) => { - extensionCoreAPI - .setSelectedSearchContextSpec(spec) - .catch(error => { - console.error('Error persisting search context spec.', error) - }) - .finally(() => { - // Execute search with new context state - onSubmit() - }) - }, - [extensionCoreAPI, onSubmit] - ) - - const onSearchAgain = useCallback( - (additionalFilters: string[]) => { - platformContext.telemetryService.log('SearchSkippedResultsAgainClicked') - onSubmit({ - newQuery: applyAdditionalFilters(context.submittedSearchQueryState.queryState.query, additionalFilters), - }) - }, - [context.submittedSearchQueryState.queryState, platformContext, onSubmit] - ) - - const onShareResultsClick = useCallback(async (): Promise => { - const queryState = context.submittedSearchQueryState - - const path = `/search?${buildSearchURLQuery( - queryState.queryState.query, - queryState.searchPatternType, - queryState.searchCaseSensitivity, - context.selectedSearchContextSpec - )}&utm_campaign=vscode-extension&utm_medium=direct_traffic&utm_source=vscode-extension&utm_content=save-search` - await extensionCoreAPI.copyLink(new URL(path, instanceURL).href) - platformContext.telemetryService.log('VSCEShareLinkClick') - }, [context, instanceURL, extensionCoreAPI, platformContext]) - - const fullQuery = useMemo( - () => - appendContextFilter( - context.submittedSearchQueryState.queryState.query ?? '', - context.selectedSearchContextSpec - ), - [context] - ) - - const matchHandlers = useMatchHandlers({ - platformContext, - extensionCoreAPI, - authenticatedUser, - onRepoSelected: setRepoToShow, - instanceURL, - }) - - const clearRepositoryToShow = (): void => setRepoToShow(null) - - return ( -
- {/* eslint-disable-next-line react/forbid-elements */} -
{ - event.preventDefault() - onSubmit() - }} - > - - - - {!repoToShow ? ( -
- - } - allExpanded={allExpanded} - onExpandAllResultsToggle={onExpandAllResultsToggle} - instanceURL={instanceURL} - fullQuery={fullQuery} - /> - - - -
- ) : ( -
- -
- )} -
- ) -} - -const applyAdditionalFilters = (query: string, additionalFilters: string[]): string => { - let newQuery = query - for (const filter of additionalFilters) { - const fieldValue = filter.split(':', 2) - newQuery = updateFilters(newQuery, fieldValue[0], fieldValue[1]) - } - return newQuery -} diff --git a/client/vscode/src/webview/search-panel/alias/CommitSearchResult.tsx b/client/vscode/src/webview/search-panel/alias/CommitSearchResult.tsx deleted file mode 100644 index 8f5034a3ac0..00000000000 --- a/client/vscode/src/webview/search-panel/alias/CommitSearchResult.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react' - -import VisuallyHidden from '@reach/visually-hidden' - -import { SearchResultStyles as styles, LegacyResultContainer, CommitSearchResultMatch } from '@sourcegraph/branded' -import { Timestamp } from '@sourcegraph/branded/src/components/Timestamp' -import { displayRepoName } from '@sourcegraph/shared/src/components/RepoLink' -import type { PlatformContextProps } from '@sourcegraph/shared/src/platform/context' -import { type CommitMatch, getCommitMatchUrl } from '@sourcegraph/shared/src/search/stream' -import { Button, Code } from '@sourcegraph/wildcard' - -import { useOpenSearchResultsContext } from '../MatchHandlersContext' - -interface Props extends PlatformContextProps<'requestGraphQL'> { - result: CommitMatch - repoName: string - icon: React.ComponentType<{ className?: string }> - onSelect: () => void - openInNewTab?: boolean - containerClassName?: string - as?: React.ElementType - index: number -} - -export const CommitSearchResult: React.FunctionComponent = ({ - result, - icon, - platformContext, - onSelect, - openInNewTab, - containerClassName, - as, - index, -}) => { - /** - * Use the custom hook useIsTruncated to check if overflow: ellipsis is activated for the element - * We want to do it on mouse enter as browser window size might change after the element has been - * loaded initially - */ - const { openRepo, openCommit, instanceURL } = useOpenSearchResultsContext() - - const renderTitle = (): JSX.Element => ( -
- - <> - - {' › '} - - {': '} - - - - - {result.type === 'commit' && ( - - )} - {result.repoStars &&
} -
- ) - - const renderBody = (): JSX.Element => ( - - ) - - return ( - - ) -} diff --git a/client/vscode/src/webview/search-panel/alias/FileMatchChildren.tsx b/client/vscode/src/webview/search-panel/alias/FileMatchChildren.tsx deleted file mode 100644 index fe128350ca8..00000000000 --- a/client/vscode/src/webview/search-panel/alias/FileMatchChildren.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import React, { type MouseEvent, type KeyboardEvent, useCallback } from 'react' - -import classNames from 'classnames' -import type * as H from 'history' -import type { Observable } from 'rxjs' -import { map } from 'rxjs/operators' - -import { FileMatchChildrenStyles as styles, CodeExcerpt } from '@sourcegraph/branded' -import type { HoverMerged } from '@sourcegraph/client-api' -import type { Hoverifier } from '@sourcegraph/codeintellify' -import { appendLineRangeQueryParameter, toPositionOrRangeQueryParameter } from '@sourcegraph/common' -import type { ActionItemAction } from '@sourcegraph/shared/src/actions/ActionItem' -import type { FetchFileParameters } from '@sourcegraph/shared/src/backend/file' -import type { MatchGroup } from '@sourcegraph/shared/src/components/ranking/PerFileResultRanking' -import type { Controller as ExtensionsController } from '@sourcegraph/shared/src/extensions/controller' -import type { HoverContext } from '@sourcegraph/shared/src/hover/HoverOverlay.types' -import { - type ContentMatch, - type SymbolMatch, - type PathMatch, - getFileMatchUrl, -} from '@sourcegraph/shared/src/search/stream' -import { isSettingsValid, type SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings' -import { SymbolKind } from '@sourcegraph/shared/src/symbols/SymbolKind' -import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { Button, Code } from '@sourcegraph/wildcard' - -import type { HighlightLineRange } from '../../../graphql-operations' -import { useOpenSearchResultsContext } from '../MatchHandlersContext' - -interface FileMatchProps extends SettingsCascadeProps, TelemetryProps { - location?: H.Location - result: ContentMatch | SymbolMatch | PathMatch - grouped: MatchGroup[] - /* Clicking on a match opens the link in a new tab */ - openInNewTab?: boolean - fetchHighlightedFileLineRanges: (parameters: FetchFileParameters, force?: boolean) => Observable - extensionsController?: Pick - hoverifier?: Hoverifier -} - -/** - * This helper function determines whether a mouse/click event was triggered as - * a result of selecting text in search results. - * There are at least to ways to do this: - * - * - Tracking `mouseup`, `mousemove` and `mousedown` events. The occurrence of - * a `mousemove` event would indicate a text selection. However, users - * might slightly move the mouse while clicking, and solutions that would - * take this into account seem fragile. - * - (implemented here) Inspect the Selection object returned by - * `window.getSelection()`. - * - * CAVEAT: Chromium and Firefox (and maybe other browsers) behave - * differently when a search result is clicked *after* text selection was - * made: - * - * - Firefox will clear the selection before executing the click event - * handler, i.e. the search result will be opened. - * - Chrome will only clear the selection if the click happens *outside* - * of the selected text (in which case the search result will be - * opened). If the click happens inside the selected text the selection - * will be cleared only *after* executing the click event handler. - */ -function isTextSelectionEvent(event: MouseEvent): boolean { - const selection = window.getSelection() - - // Text selections are always ranges. Should the type not be set, verify - // that the selection is not empty. - if (selection && (selection.type === 'Range' || selection.toString() !== '')) { - // Firefox specific: Because our code excerpts are implemented as tables, - // CTRL+click would select the table cell. Since users don't know that we - // use tables, the most likely wanted to open the search results in a new - // tab instead though. - if ((event.ctrlKey || event.metaKey) && selection.anchorNode?.nodeName === 'TR') { - // Ugly side effect: We don't want the table cell to be highlighted. - // The focus style that Firefox uses doesn't seem to be affected by - // CSS so instead we clear the selection. - selection.empty() - return false - } - - return true - } - - return false -} - -/** - * A helper function to replicate browser behavior when clicking on links. - * A very common interaction is to open links in a new in the _background_ via - * CTRL/CMD + click or middle click. - * Unfortunately `window.open` doesn't give us much control over how the new - * window/tab should be opened, and the behavior is inconcistent between - * browsers. - * In order to replicate the standard behvior as much as possible this function - * dynamically creates an `` element and triggers a click event on it. - */ -function openLinkInNewTab( - url: string, - event: Pick, - button: 'primary' | 'middle' -): void { - const link = document.createElement('a') - link.href = url - link.style.display = 'none' - link.target = '_blank' - link.rel = 'noopener noreferrer' - const clickEvent = new window.MouseEvent('click', { - bubbles: false, - altKey: event.altKey, - shiftKey: event.shiftKey, - // Regarding middle click: Setting "button: 1:" doesn't seem to suffice: - // Firefox doesn't react to the event at all, Chromium opens the tab in - // the foreground. So in order to simulate a middle click, we set - // ctrlKey and metaKey to `true` instead. - ctrlKey: button === 'middle' ? true : event.ctrlKey, - metaKey: button === 'middle' ? true : event.metaKey, - view: window, - }) - - // It looks the link has to be part of the document, otherwise Firefox won't - // trigger the default behavior (it works without appending in Chromium). - document.body.append(link) - link.dispatchEvent(clickEvent) - link.remove() -} - -/** - * Since we are not using a real link anymore, we have to simulate opening - * the file in a new tab when the search result is clicked on with the - * middle mouse button. - * This handler is bound to the `mouseup` event because the `auxclick` - * (https://w3c.github.io/uievents/#event-type-auxclick) event is not - * support by all browsers yet (https://caniuse.com/?search=auxclick) - */ -function navigateToFileOnMiddleMouseButtonClick(event: MouseEvent): void { - const href = event.currentTarget.getAttribute('data-href') - if (href && event.button === 1) { - openLinkInNewTab(href, event, 'middle') - } -} - -export const FileMatchChildren: React.FunctionComponent> = props => { - const { result, grouped, fetchHighlightedFileLineRanges, telemetryService } = props - - const { openFile, openSymbol } = useOpenSearchResultsContext() - - const fetchHighlightedFileRangeLines = React.useCallback( - (startLine: number, endLine: number) => { - const startTime = Date.now() - return fetchHighlightedFileLineRanges( - { - repoName: result.repository, - commitID: result.commit || '', - filePath: result.path, - disableTimeout: false, - ranges: grouped.map( - (group): HighlightLineRange => ({ - startLine: group.startLine, - endLine: group.endLine, - }) - ), - }, - false - ).pipe( - map(lines => { - telemetryService.log( - 'search.latencies.frontend.code-load', - { durationMs: Date.now() - startTime }, - { durationMs: Date.now() - startTime } - ) - return lines[grouped.findIndex(group => group.startLine === startLine && group.endLine === endLine)] - }) - ) - }, - [result, fetchHighlightedFileLineRanges, grouped, telemetryService] - ) - - const createCodeExcerptLink = (group: MatchGroup): string => { - const positionOrRangeQueryParameter = toPositionOrRangeQueryParameter({ position: group.position }) - return appendLineRangeQueryParameter(getFileMatchUrl(result), positionOrRangeQueryParameter) - } - - /** - * This handler implements the logic to simulate the click/keyboard - * activation behavior of links, while also allowing the selection of text - * inside the element. - * Because a click event is dispatched in both cases (clicking the search - * result to open it as well as selecting text within it), we have to be - * able to distinguish between those two actions. - * If we detect a text selection action, we don't have to do anything. - * - * CAVEATS: - * - In Firefox, Shift+click will open the URL in a new tab instead of - * a window (unlike Chromium which seems to show the same behavior as with - * native links). - * - Firefox will insert \t\n in between table rows, causing the copied - * text to be different from what is in the file/search result. - */ - const navigateToFile = useCallback( - ( - event: KeyboardEvent | MouseEvent, - { line, character }: { line: number; character: number } - ): void => { - // Testing for text selection is only necessary for mouse/click - // events. Middle-click (event.button === 1) is already handled in the `onMouseUp` callback. - if ( - (event.type === 'click' && - !isTextSelectionEvent(event as MouseEvent) && - (event as MouseEvent).button !== 1) || - (event as KeyboardEvent).key === 'Enter' - ) { - const href = event.currentTarget.getAttribute('data-href') - if (!event.defaultPrevented && href) { - event.preventDefault() - - openFile(result.repository, { - path: result.path, - revision: result.commit, - position: { - line: line - 1, - character: character - 1, - }, - }) - } - } - }, - [openFile, result] - ) - - return ( -
- {/* Path */} - {result.type === 'path' && ( -
- Path match -
- )} - - {/* Symbols */} - {((result.type === 'symbol' && result.symbols) || []).map(symbol => ( - - ))} - - {/* Line matches */} - {grouped && ( -
- {grouped.map((group, index) => ( -
-
- navigateToFile(event, { - line: group.position.line, - character: group.position.character, - }) - } - onMouseUp={navigateToFileOnMiddleMouseButtonClick} - onKeyDown={event => - navigateToFile(event, { - line: group.position.line, - character: group.position.character, - }) - } - data-testid="file-match-children-item" - tabIndex={0} - role="link" - > - -
-
- ))} -
- )} -
- ) -} diff --git a/client/vscode/src/webview/search-panel/alias/ModalVideo.module.scss b/client/vscode/src/webview/search-panel/alias/ModalVideo.module.scss deleted file mode 100644 index ba1f743466a..00000000000 --- a/client/vscode/src/webview/search-panel/alias/ModalVideo.module.scss +++ /dev/null @@ -1,73 +0,0 @@ -@import 'wildcard/src/global-styles/breakpoints'; - -.wrapper { - &:hover button { - text-decoration: underline; - } - - figure { - margin: 0; - } -} - -.thumbnail-button { - width: 100%; - position: relative; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - background-color: transparent; - padding: 0; -} - -.thumbnail-image { - width: 100%; - // Note: This is to reduce cumulative layout shift - // It should be as close as possible to the source image aspect ratio - aspect-ratio: 176/115; -} - -.play-icon-wrapper { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.modal { - width: 70vw; - @media (--lg-breakpoint-down) { - width: 90vw; - } -} - -.modal-content { - display: flex; - align-items: stretch; - flex-direction: column; - height: 100%; -} - -.modal-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.75rem; -} - -.iframe-video-wrapper { - position: relative; - // stylelint-disable-next-line declaration-property-unit-allowed-list - padding-top: 56.25%; -} - -.iframe-video { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} diff --git a/client/vscode/src/webview/search-panel/alias/ModalVideo.tsx b/client/vscode/src/webview/search-panel/alias/ModalVideo.tsx deleted file mode 100644 index 41f7dbe5cc3..00000000000 --- a/client/vscode/src/webview/search-panel/alias/ModalVideo.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react' - -import classNames from 'classnames' - -import { Button } from '@sourcegraph/wildcard' - -import { useWebviewPageContext } from '../../platform/context' - -import styles from './ModalVideo.module.scss' - -// We can't play video in VS Code Desktop: https://stackoverflow.com/a/57512681 -// Open video in YouTube instead. - -interface ModalVideoProps { - id: string - title: string - src: string - thumbnail?: { src: string; alt: string } - onToggle?: (isOpen: boolean) => void - showCaption?: boolean - className?: string - assetsRoot?: string -} - -export const ModalVideo: React.FunctionComponent> = ({ - title, - src, - thumbnail, - onToggle, - showCaption = false, - className, - assetsRoot = '', -}) => { - const { extensionCoreAPI } = useWebviewPageContext() - - const onClick = (): void => { - onToggle?.(false) - extensionCoreAPI.openLink(src).catch(error => { - console.error(`Error opening video at ${src}`, error) - }) - } - - let thumbnailElement = thumbnail ? ( - - ) : null - - if (showCaption) { - thumbnailElement = ( -
- {thumbnailElement} -
- -
-
- ) - } - - return
{thumbnailElement}
-} - -const PlayIcon = React.memo(() => ( - - - - - - - - - - - - - - - - - - - - - -)) diff --git a/client/vscode/src/webview/search-panel/alias/README.md b/client/vscode/src/webview/search-panel/alias/README.md deleted file mode 100644 index 1c30b92b256..00000000000 --- a/client/vscode/src/webview/search-panel/alias/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Aliased files - -Lots of shared client code used in the initial implementation of the VS Code -To expedite the implementation and review processes, we decided to "fork" -code that would require significant refactoring to work in the VS Code -extension context. - -Our plan is remove the need for these aliased/forked files (method TBD). -Resolving this divergence will reduce the risk of regressions introduced -by changes in the way that base code interacts with forked code. - -- Search result handling - - We can't use relative links like in the web app, for most search result types (and eventually all), - we need click handlers that call VS Code extension APIs. - - Forked components: `FileMatchChildren`, `SearchResult`, `RepoFileLink` - - What's changed: - - Create a React context to wrap around `StreamingSearchResultsList` (shared) to pass - VS Code extension APIs to forked search result components. - - Change links to buttons, call VS Code file handlers from context on click. diff --git a/client/vscode/src/webview/search-panel/alias/RepoFileLink.tsx b/client/vscode/src/webview/search-panel/alias/RepoFileLink.tsx deleted file mode 100644 index c97dfa96038..00000000000 --- a/client/vscode/src/webview/search-panel/alias/RepoFileLink.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react' - -import { displayRepoName } from '@sourcegraph/shared/src/components/RepoLink' -import { parseRepoRevision } from '@sourcegraph/shared/src/util/url' -import { Button, Tooltip, useIsTruncated } from '@sourcegraph/wildcard' - -import { useOpenSearchResultsContext } from '../MatchHandlersContext' - -/** - * Splits the repository name into the dir and base components. - */ -export function splitPath(path: string): [string, string] { - const components = path.split('/') - return [components.slice(0, -1).join('/'), components.at(-1)!] -} - -interface Props { - repoName: string - repoURL: string - filePath: string - fileURL: string - repoDisplayName?: string - className?: string -} - -/** - * A link to a repository or a file within a repository, formatted as "repo" or "repo > file". Unless you - * absolutely need breadcrumb-like behavior, use this instead of FilePathBreadcrumb. - */ -export const RepoFileLink: React.FunctionComponent> = ({ - repoDisplayName, - repoName, - repoURL, - filePath, - className, -}) => { - /** - * Use the custom hook useIsTruncated to check if overflow: ellipsis is activated for the element - * We want to do it on mouse enter as browser window size might change after the element has been - * loaded initially - */ - const [titleReference, truncated, checkTruncation] = useIsTruncated() - - const [fileBase, fileName] = splitPath(filePath) - - const { openRepo, openFile } = useOpenSearchResultsContext() - - const getRepoAndRevision = (): { repoName: string; revision: string | undefined } => { - // Example: `/github.com/sourcegraph/sourcegraph@main` - const indexOfSeparator = repoURL.indexOf('/-/') - let repoRevision: string - if (indexOfSeparator === -1) { - repoRevision = repoURL // the whole string - } else { - repoRevision = repoURL.slice(0, indexOfSeparator) // the whole string leading up to the separator (allows revision to be multiple path parts) - } - let { repoName, revision } = parseRepoRevision(repoRevision) - // Remove leading slash - if (repoName.startsWith('/')) { - repoName = repoName.slice(1) - } - return { repoName, revision } - } - - const onRepoClick = (): void => { - const { repoName, revision } = getRepoAndRevision() - - openRepo({ - repository: repoName, - branches: revision ? [revision] : undefined, - }) - } - - const onFileClick = (): void => { - const { repoName, revision } = getRepoAndRevision() - openFile(repoName, { path: filePath, revision }) - } - - return ( - - -
- {' '} - ›{' '} - -
-
-
- ) -} diff --git a/client/vscode/src/webview/search-panel/alias/RepoSearchResult.tsx b/client/vscode/src/webview/search-panel/alias/RepoSearchResult.tsx deleted file mode 100644 index 5ceff3900c4..00000000000 --- a/client/vscode/src/webview/search-panel/alias/RepoSearchResult.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react' - -import { mdiSourceFork, mdiArchive, mdiLock } from '@mdi/js' -import classNames from 'classnames' -import SourceRepositoryIcon from 'mdi-react/SourceRepositoryIcon' - -import { SearchResultStyles as styles, LegacyResultContainer } from '@sourcegraph/branded' -import { displayRepoName } from '@sourcegraph/shared/src/components/RepoLink' -import { getRepoMatchLabel, type RepositoryMatch } from '@sourcegraph/shared/src/search/stream' -import { Button, Icon } from '@sourcegraph/wildcard' - -import { useOpenSearchResultsContext } from '../MatchHandlersContext' - -export interface RepoSearchResultProps { - result: RepositoryMatch - repoName: string - onSelect: () => void - containerClassName?: string - as?: React.ElementType - index: number -} - -export const RepoSearchResult: React.FunctionComponent = ({ - result, - onSelect, - containerClassName, - as, - index, -}) => { - /** - * Use the custom hook useIsTruncated to check if overflow: ellipsis is activated for the element - * We want to do it on mouse enter as browser window size might change after the element has been - * loaded initially - */ - const { openRepo } = useOpenSearchResultsContext() - - const renderTitle = (): JSX.Element => ( -
- - - -
- ) - - const renderBody = (): JSX.Element => ( -
-
-
-
- Repository match -
- {result.fork && ( - <> -
-
- -
-
- Fork -
- - )} - {result.archived && ( - <> -
-
- -
-
- Archived -
- - )} - {result.private && ( - <> -
-
- -
-
- Private -
- - )} -
- {result.description && ( - <> -
-
- - {result.description} - -
- - )} -
-
- ) - - return ( - - ) -} diff --git a/client/vscode/src/webview/search-panel/alias/SymbolSearchResult.tsx b/client/vscode/src/webview/search-panel/alias/SymbolSearchResult.tsx deleted file mode 100644 index f0fa6647b4b..00000000000 --- a/client/vscode/src/webview/search-panel/alias/SymbolSearchResult.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { useCallback } from 'react' - -import classNames from 'classnames' -import type { Observable } from 'rxjs' -import { map } from 'rxjs/operators' - -import { - SymbolSearchResultStyles as styles, - SearchResultStyles as searchResultStyles, - CodeExcerpt, - navigateToFileOnMiddleMouseButtonClick, - ResultContainer, - CopyPathAction, -} from '@sourcegraph/branded' -import type { FetchFileParameters } from '@sourcegraph/shared/src/backend/file' -import { getFileMatchUrl, getRepositoryUrl, getRevision, type SymbolMatch } from '@sourcegraph/shared/src/search/stream' -import { isSettingsValid, type SettingsCascadeProps } from '@sourcegraph/shared/src/settings/settings' -import { SymbolKind } from '@sourcegraph/shared/src/symbols/SymbolKind' -import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { codeCopiedEvent } from '@sourcegraph/shared/src/tracking/event-log-creators' - -import { type HighlightLineRange, HighlightResponseFormat } from '../../../graphql-operations' -import { useOpenSearchResultsContext } from '../MatchHandlersContext' - -import { RepoFileLink } from './RepoFileLink' - -export interface SymbolSearchResultProps extends TelemetryProps, SettingsCascadeProps { - result: SymbolMatch - openInNewTab?: boolean - repoDisplayName: string - containerClassName?: string - index: number - onSelect: () => void - fetchHighlightedFileLineRanges: (parameters: FetchFileParameters, force?: boolean) => Observable -} - -export const SymbolSearchResult: React.FunctionComponent = ({ - result, - repoDisplayName, - onSelect, - containerClassName, - index, - telemetryService, - settingsCascade, - fetchHighlightedFileLineRanges, -}) => { - const enableLazyFileResultSyntaxHighlighting = - isSettingsValid(settingsCascade) && - settingsCascade.final.experimentalFeatures?.enableLazyFileResultSyntaxHighlighting - - const repoAtRevisionURL = getRepositoryUrl(result.repository, result.branches) - const revisionDisplayName = getRevision(result.branches, result.commit) - - const { openSymbol } = useOpenSearchResultsContext() - - const title = ( - - - - - ) - - const logEventOnCopy = useCallback(() => { - telemetryService.log(...codeCopiedEvent('file-match')) - }, [telemetryService]) - - const fetchSymbolMatchLineRanges = useCallback( - (startLine: number, endLine: number, format: HighlightResponseFormat) => { - const startTime = Date.now() - return fetchHighlightedFileLineRanges({ - repoName: result.repository, - commitID: result.commit || '', - filePath: result.path, - disableTimeout: false, - format, - ranges: result.symbols.map( - (symbol): HighlightLineRange => ({ - startLine: symbol.line - 1, - endLine: symbol.line, - }) - ), - }).pipe( - map(lines => { - const endTime = Date.now() - telemetryService.log( - 'search.latencies.frontend.code-load', - { durationMs: endTime - startTime }, - { durationMs: endTime - startTime } - ) - return lines[ - result.symbols.findIndex(symbol => symbol.line - 1 === startLine && symbol.line === endLine) - ] - }) - ) - }, - [result, fetchHighlightedFileLineRanges, telemetryService] - ) - - const fetchHighlightedSymbolMatchLineRanges = useCallback( - (startLine: number, endLine: number) => - fetchSymbolMatchLineRanges(startLine, endLine, HighlightResponseFormat.HTML_HIGHLIGHT), - [fetchSymbolMatchLineRanges] - ) - - const fetchPlainTextSymbolMatchLineRanges = useCallback( - (startLine: number, endLine: number) => - fetchSymbolMatchLineRanges(startLine, endLine, HighlightResponseFormat.HTML_PLAINTEXT), - [fetchSymbolMatchLineRanges] - ) - - return ( - -
- {result.symbols.map(symbol => ( -
openSymbol(symbol.url)} - onMouseUp={navigateToFileOnMiddleMouseButtonClick} - onKeyDown={() => openSymbol(symbol.url)} - > -
- -
-
- -
-
- ))} -
-
- ) -} diff --git a/client/vscode/src/webview/search-panel/alias/fetchSearchContext.ts b/client/vscode/src/webview/search-panel/alias/fetchSearchContext.ts deleted file mode 100644 index 9c9eea57b3b..00000000000 --- a/client/vscode/src/webview/search-panel/alias/fetchSearchContext.ts +++ /dev/null @@ -1,181 +0,0 @@ -import type { Observable } from 'rxjs' -import { map } from 'rxjs/operators' - -import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' -import type { PlatformContext } from '@sourcegraph/shared/src/platform/context' - -export type Exact = { [K in keyof T]: T[K] } -export type Maybe = T | null - -/** All built-in and custom scalars, mapped to their actual values */ -export interface Scalars { - ID: string - String: string - Boolean: boolean - Int: number - Float: number - /** A quadruple that represents all possible states of the published value: true, false, 'draft', or null. */ - PublishedValue: boolean | 'draft' - /** A valid JSON value. */ - JSONValue: unknown - /** A string that contains valid JSON, with additional support for //-style comments and trailing commas. */ - JSONCString: string - /** A Git object ID (SHA-1 hash, 40 hexadecimal characters). */ - GitObjectID: string - /** An arbitrarily large integer encoded as a decimal string. */ - BigInt: string - /** - * An RFC 3339-encoded UTC date string, such as 1973-11-29T21:33:09Z. This value can be parsed into a - * JavaScript Date using Date.parse. To produce this value from a JavaScript Date instance, use - * Date#toISOString. - */ - DateTime: string -} - -/** - * This is a copy of the fetchSearchContexts function from @sourcegraph/search created for VSCE use - * We have removed `query` from SearchContext to support instances below v3.36.0 - * as query does not exist in Search Context type in older instances - * More context in https://github.com/sourcegraph/sourcegraph/issues/31022 - **/ - -const searchContextFragment = gql` - fragment SearchContextFields on SearchContext { - __typename - id - name - namespace { - __typename - id - namespaceName - } - spec - description - public - autoDefined - updatedAt - viewerCanManage - viewerHasStarred - viewerHasAsDefault - repositories { - __typename - repository { - name - } - revisions - } - } -` - -export function fetchSearchContexts({ - first, - namespaces, - query, - after, - orderBy, - descending, - platformContext, -}: { - first: number - query?: string - namespaces?: Maybe[] - after?: string - orderBy?: SearchContextsOrderBy - descending?: boolean - platformContext: Pick -}): Observable { - return platformContext - .requestGraphQL({ - request: gql` - query ListSearchContexts( - $first: Int! - $after: String - $query: String - $namespaces: [ID] - $orderBy: SearchContextsOrderBy - $descending: Boolean - ) { - searchContexts( - first: $first - after: $after - query: $query - namespaces: $namespaces - orderBy: $orderBy - descending: $descending - ) { - nodes { - ...SearchContextFields - } - pageInfo { - hasNextPage - endCursor - } - totalCount - } - } - ${searchContextFragment} - `, - variables: { - first, - after: after ?? null, - query: query ?? null, - namespaces: namespaces ?? [], - orderBy: orderBy ?? SearchContextsOrderBy.SEARCH_CONTEXT_SPEC, - descending: descending ?? false, - }, - mightContainPrivateInfo: true, - }) - .pipe( - map(dataOrThrowErrors), - map(data => data.searchContexts) - ) -} - -export interface SearchContextFields { - __typename: 'SearchContext' - id: string - name: string - spec: string - description: string - public: boolean - autoDefined: boolean - updatedAt: string - viewerCanManage: boolean - viewerHasAsDefault: boolean - viewerHasStarred: boolean - query: string - namespace: Maybe< - | { __typename: 'User'; id: string; namespaceName: string } - | { __typename: 'Org'; id: string; namespaceName: string } - > - repositories: { - __typename: 'SearchContextRepositoryRevisions' - revisions: string[] - repository: { __typename?: 'Repository'; name: string } - }[] -} - -export type ListSearchContextsVariables = Exact<{ - first: Scalars['Int'] - after: Maybe - query: Maybe - namespaces: Maybe[]> - orderBy: Maybe - descending: Maybe -}> - -export interface ListSearchContextsResult { - __typename?: 'Query' - searchContexts: { - __typename?: 'SearchContextConnection' - totalCount: number - nodes: ({ __typename?: 'SearchContext' } & SearchContextFields)[] - pageInfo: { __typename?: 'PageInfo'; hasNextPage: boolean; endCursor: Maybe } - } -} - -/** SearchContextsOrderBy enumerates the ways a search contexts list can be ordered. */ -export enum SearchContextsOrderBy { - SEARCH_CONTEXT_SPEC = 'SEARCH_CONTEXT_SPEC', - SEARCH_CONTEXT_UPDATED_AT = 'SEARCH_CONTEXT_UPDATED_AT', -} diff --git a/client/vscode/src/webview/search-panel/api.ts b/client/vscode/src/webview/search-panel/api.ts deleted file mode 100644 index 60d1057f051..00000000000 --- a/client/vscode/src/webview/search-panel/api.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { of } from 'rxjs' - -import { proxySubscribable } from '@sourcegraph/shared/src/api/extension/api/common' - -import type { SearchPanelAPI } from '../../contract' - -export const searchPanelAPI: SearchPanelAPI = { - ping: () => { - console.log('ping called') - return proxySubscribable(of('pong')) - }, - focusSearchBox: () => { - // Call dynamic `focusSearchBox`. - focusSearchBox() - }, -} -let focusSearchBox = (): void => { - // Initially a noop. Waiting for search box init. -} - -// TODO move to api.ts file -export const setFocusSearchBox = (replacementFocusSearchBox: (() => void) | null): void => { - focusSearchBox = replacementFocusSearchBox || (() => {}) -} diff --git a/client/vscode/src/webview/search-panel/components/BrandHeader.tsx b/client/vscode/src/webview/search-panel/components/BrandHeader.tsx deleted file mode 100644 index 37e949de035..00000000000 --- a/client/vscode/src/webview/search-panel/components/BrandHeader.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' - -import classNames from 'classnames' - -import styles from '../index.module.scss' - -interface BrandHeaderProps { - isLightTheme: boolean -} - -export const BrandHeader: React.FunctionComponent = ({ isLightTheme }) => ( - <> - Sourcegraph logo -
- Search millions of open source repositories -
- -) diff --git a/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.module.scss b/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.module.scss deleted file mode 100644 index 74f1b009303..00000000000 --- a/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -.container { - width: 31rem; - padding: 0.75rem; -} - -.toggle { - border-radius: var(--border-radius) !important; - padding: 0.375rem 0.5rem; -} - -.title { - margin-bottom: 0.25rem; - font-size: 1rem; -} - -.copy-text { - font-size: 0.875rem; -} - -.icon { - /* stylelint-disable-next-line declaration-property-unit-allowed-list */ - padding: 15px; - color: var(--merged-3); - - svg { - margin-right: 0 !important; - } -} diff --git a/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.tsx b/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.tsx deleted file mode 100644 index e1de243f1b4..00000000000 --- a/client/vscode/src/webview/search-panel/components/ButtonDropdownCta.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react' - -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' -import classNames from 'classnames' - -import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { Button, Popover, PopoverContent, PopoverTrigger, Position } from '@sourcegraph/wildcard' - -import type { WebviewPageProps } from '../../platform/context' - -import styles from './ButtonDropdownCta.module.scss' - -// Debt: this is a fork of the web . - -export interface ButtonDropdownCtaProps extends TelemetryProps, Pick { - button: JSX.Element - icon: JSX.Element - title: string - copyText: string - source: string - viewEventName: string - returnTo: string - onToggle?: () => void - className?: string - instanceURL?: string -} - -export const ButtonDropdownCta: React.FunctionComponent> = ({ - button, - icon, - title, - copyText, - telemetryService, - source, - viewEventName, - returnTo, - onToggle, - className, - extensionCoreAPI, -}) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false) - - const toggleDropdownOpen = useCallback(() => { - setIsDropdownOpen(isOpen => !isOpen) - onToggle?.() - }, [onToggle]) - - // Whenever dropdown opens, log view event - useEffect(() => { - if (isDropdownOpen) { - telemetryService.log(viewEventName) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDropdownOpen]) - - // Use cloud url instead of instance url - // This will only display for non private uers - // Because private users are required use with token = signed up - const signUpURL = `https://sourcegraph.com/sign-up?src=${source}&returnTo=${encodeURIComponent( - returnTo - )}&utm_medium=VSCODE&utm_source=sidebar&utm_campaign=vsce-sign-up&utm_content=sign-up` - - const onClick = (): void => { - telemetryService.log(`VSCE${source}SignUpModalClick`) - extensionCoreAPI.openLink(signUpURL).catch(() => { - console.error('Error opening sign up link') - }) - } - - return ( - - - {button} - - -
-
-
{icon}
-
-
-
- {title} -
-
{copyText}
-
-
- - Sign up for Sourcegraph - -
-
- ) -} diff --git a/client/vscode/src/webview/search-panel/components/HomeFooter.module.scss b/client/vscode/src/webview/search-panel/components/HomeFooter.module.scss deleted file mode 100644 index 8bf69935f8f..00000000000 --- a/client/vscode/src/webview/search-panel/components/HomeFooter.module.scss +++ /dev/null @@ -1,148 +0,0 @@ -@import 'wildcard/src/global-styles/breakpoints'; - -.footer-container { - width: 100%; - margin-top: 3rem; - - @media (--xl-breakpoint-up) { - max-width: var(--max-homepage-container-width); - } -} - -.help-content { - display: flex; - justify-content: center; - margin-bottom: 3rem; - - @media (--xs-breakpoint-down) { - align-items: stretch; - flex-direction: column; - } -} - -.search-examples { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 1rem; - - @media (--md-breakpoint-down) { - grid-template-columns: 1fr 1fr 1fr; - } - - @media (--sm-breakpoint-down) { - grid-template-columns: 1fr 1fr; - } - - @media (--xs-breakpoint-down) { - grid-template-columns: 1fr; - } -} - -.search-example-card { - display: flex; - flex-direction: row; - align-items: stretch; - font-size: var(--vscode-editor-font-size); - text-decoration: none !important; - margin-bottom: 0.5rem; - background-color: var(--vscode-editorWidget-background); - - &:hover { - --hover-box-shadow: none; - - .search-example-icon { - color: var(--vscode-button-foreground) !important; - background-color: var(--vscode-button-background) !important; - } - } -} - -.search-example-icon { - padding: 0.5rem 0.75rem; - display: flex; - align-items: center; - border-top-left-radius: var(--border-radius); - border-bottom-left-radius: var(--border-radius); - color: var(--vscode-button-foreground) !important; - - &:hover { - color: var(--vscode-button-foreground) !important; - background-color: var(--vscode-button-background) !important; - } -} - -.search-example-query-wrapper { - padding: 0.5rem 0; - align-self: center; -} - -.search-example-query { - padding: 0 0.75rem; - border-left: 1px solid var(--border-color); - min-height: 1.5rem; - text-align: left; -} - -.search-examples-wrapper { - flex: 1; - margin-right: 1rem; - - @media (--xs-breakpoint-down) { - margin-right: 0; - } -} - -.search-examples-title-wrapper { - @media (--sm-breakpoint-down) { - flex-direction: column; - } -} - -.search-examples-title { - font-size: var(--vscode-font-size) !important; - - @media (--sm-breakpoint-down) { - margin-bottom: 0.5rem; - } -} - -.search-example-label { - font-size: var(--vscode-font-size) !important; - font-weight: 400; -} - -.search-examples-subtitle { - font-weight: 500; - color: var(--vscode-descriptionForeground) !important; - font-size: var(--vscode-font-size) !important; - - @media (--sm-breakpoint-down) { - margin-bottom: 0.5rem; - } -} - -.title { - font-size: var(--vscode-font-size); - font-weight: 700; - text-transform: uppercase; -} - -/* stylelint-disable-next-line selector-class-pattern */ -.thumbnailWrapper { - padding: 0; - - @media (--xs-breakpoint-down) { - margin-top: 1.5rem; - text-align: center; - } -} - -.thumbnail { - &:hover { - opacity: 0.8; - } - - img { - width: 9rem !important; - } -} diff --git a/client/vscode/src/webview/search-panel/components/HomeFooter.tsx b/client/vscode/src/webview/search-panel/components/HomeFooter.tsx deleted file mode 100644 index 5b5608e43f4..00000000000 --- a/client/vscode/src/webview/search-panel/components/HomeFooter.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useCallback } from 'react' - -import classNames from 'classnames' - -import { SyntaxHighlightedSearchQuery } from '@sourcegraph/branded' -import type { QueryState } from '@sourcegraph/shared/src/search' -import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' -import { Card, Text } from '@sourcegraph/wildcard' - -import { ModalVideo } from '../alias/ModalVideo' - -import { type SearchExample, exampleQueries } from './SearchExamples' - -import styles from './HomeFooter.module.scss' - -export interface HomeFooterProps extends TelemetryProps { - setQuery: (newState: QueryState) => void -} -interface SearchExamplesProps extends TelemetryProps { - title: string - subtitle: string - examples: SearchExample[] - icon: JSX.Element - setQuery: (newState: QueryState) => void -} - -const SearchExamples: React.FunctionComponent> = ({ - title, - subtitle, - examples, - icon, - telemetryService, - setQuery, -}) => { - const searchExampleClicked = useCallback( - (trackEventName: string, fullQuery: string) => (): void => { - setQuery({ query: fullQuery }) - telemetryService.log(trackEventName) - }, - [setQuery, telemetryService] - ) - return ( -
-
-
{title}
-
{subtitle}
-
-
- {examples.map(example => ( -
- -
- {icon} -
-
-
- -
-
-
- {example.label} -
- ))} -
-
- ) -} - -export const HomeFooter: React.FunctionComponent> = props => ( - <> -
-
- } - {...props} - /> -
-
Watch and learn
-
- {/* TODO: UPLOAD PREVIEW IMAGE TO SG TO USE SG AS ACCESSROOT */} - props.telemetryService.log('VSCEHomeWatch&Lean')} - // assetsRoot="https://sourcegraph.com/.assets/" - assetsRoot="https://i.ibb.co/" - /> -
-
-
-
- -) - -const MagnifyingGlassSearchIcon = React.memo(() => ( - - - -)) diff --git a/client/vscode/src/webview/search-panel/components/SearchExamples.tsx b/client/vscode/src/webview/search-panel/components/SearchExamples.tsx deleted file mode 100644 index f8c3ac31126..00000000000 --- a/client/vscode/src/webview/search-panel/components/SearchExamples.tsx +++ /dev/null @@ -1,28 +0,0 @@ -export interface SearchExample { - label: string - trackEventName: string - queryPreview: string - fullQuery: string -} - -export const exampleQueries: SearchExample[] = [ - { - label: 'Search all of your repos, without escaping or regex', - trackEventName: 'VSCEHomeSearchExamplesClick', - queryPreview: 'repo:sourcegraph/.* Sprintf("%d -file:tests', - fullQuery: 'repo:sourcegraph/.* Sprintf("%d -file:tests case:yes', - }, - { - label: 'Search and review commits faster than git log and grep', - trackEventName: 'VSCEHomeSearchExamplesClick', - queryPreview: 'type:diff before:"last week" TODO', - fullQuery: - 'repo:^github.com/sourcegraph/sourcegraph$ type:diff after:"last week" select:commit.diff.added TODO', - }, - { - label: 'Quickly filter by language and other key attributes', - trackEventName: 'VSCEHomeSearchExamplesClick', - queryPreview: 'repo:sourcegraph lang:go or lang:Typescript', - fullQuery: 'repo:sourcegraph/* -f:tests (lang:TypeScript or lang:go) Config() case:yes', - }, -] diff --git a/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.module.scss b/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.module.scss deleted file mode 100644 index 1d35ff2681e..00000000000 --- a/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.module.scss +++ /dev/null @@ -1,51 +0,0 @@ -.search-results-info-bar { - display: flex; - flex-direction: column; - justify-content: space-between; - align-self: stretch; - - :global(.alert) { - margin-bottom: 0.5rem; - } - - :global(.mdi-icon) { - margin-right: 0.125rem; - } -} - -.nav-item:last-child { - margin-right: 0 !important; -} - -.row { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - align-items: flex-start; - gap: 0.5rem; - height: 100%; - flex: 1 0 auto; - text-align: left; -} - -.notice { - display: flex; - align-items: center; - margin-right: 1rem; - padding: 0.375rem 0; -} - -.divider { - height: 1rem; - border-right: 1px solid var(--border-color); - margin: 0 0.5rem 0 0; - - &:first-child { - display: none; - } -} - -.expander { - display: block; - flex-grow: 1; -} diff --git a/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.tsx b/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.tsx deleted file mode 100644 index 20ba8dff1f2..00000000000 --- a/client/vscode/src/webview/search-panel/components/SearchResultsInfoBar.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React, { useCallback, useMemo } from 'react' - -import { mdiLink } from '@mdi/js' -import classNames from 'classnames' - -import { FilterKind, findFilter } from '@sourcegraph/shared/src/search/query/query' -import { Button, Icon, Tooltip } from '@sourcegraph/wildcard' - -import type { SearchPatternType } from '../../../graphql-operations' -import type { WebviewPageProps } from '../../platform/context' - -import { ButtonDropdownCta, type ButtonDropdownCtaProps } from './ButtonDropdownCta' -import { BookmarkRadialGradientIcon, CodeMonitoringLogo } from './icons' - -import styles from './SearchResultsInfoBar.module.scss' - -// Debt: this is a fork of the web . -export interface SearchResultsInfoBarProps - extends Pick { - stats: JSX.Element - - onShareResultsClick: () => void - fullQuery: string - patternType: SearchPatternType - - // Expand all feature - allExpanded: boolean - onExpandAllResultsToggle: () => void -} - -interface ExperimentalActionButtonProps extends ButtonDropdownCtaProps { - showExperimentalVersion: boolean - nonExperimentalLinkTo?: string - disabled?: boolean - onNonExperimentalLinkClick?: () => void - className?: string -} - -const ExperimentalActionButton: React.FunctionComponent< - React.PropsWithChildren -> = props => { - if (props.showExperimentalVersion) { - return - } - return ( - - ) -} - -export const SearchResultsInfoBar: React.FunctionComponent< - React.PropsWithChildren -> = props => { - const { - extensionCoreAPI, - platformContext, - authenticatedUser, - onShareResultsClick, - stats, - instanceURL, - fullQuery, - patternType, - } = props - - const showActionButtonExperimentalVersion = !authenticatedUser - - const onCreateCodeMonitorButtonClick = useCallback( - (event?: React.FormEvent): void => { - event?.preventDefault() - platformContext.telemetryService.log('VSCECreateCodeMonitorClick') - - const searchParameters = new URLSearchParams() - searchParameters.set('q', fullQuery) - searchParameters.set('trigger-query', `${fullQuery} patternType:${patternType}`) - const createMonitorURL = new URL(`/code-monitoring/new?${searchParameters.toString()}`, instanceURL) - extensionCoreAPI.openLink(createMonitorURL.href).catch(() => { - console.error('Error opening create code monitor link') - }) - }, - [platformContext.telemetryService, extensionCoreAPI, fullQuery, instanceURL, patternType] - ) - - const canCreateMonitorFromQuery = useMemo(() => { - if (!fullQuery) { - return false - } - const globalTypeFilterInQuery = findFilter(fullQuery, 'type', FilterKind.Global) - const globalTypeFilterValue = globalTypeFilterInQuery?.value ? globalTypeFilterInQuery.value.value : undefined - return globalTypeFilterValue === 'diff' || globalTypeFilterValue === 'commit' - }, [fullQuery]) - - const createCodeMonitorButton = useMemo(() => { - const searchParameters = new URLSearchParams() - searchParameters.set('q', fullQuery) - searchParameters.set('trigger-query', `${fullQuery} patternType:${patternType}`) - return ( -
  • - - - - Monitor - - } - icon={} - title="Monitor code for changes" - copyText="Create a monitor and get notified when your code changes. Free for registered users." - source="CodeMonitor" - viewEventName="VSCECodeMonitorCTAShown" - returnTo={`/code-monitoring/new?${searchParameters.toString()}`} - telemetryService={platformContext.telemetryService} - disabled={!canCreateMonitorFromQuery} - instanceURL={instanceURL} - /> - -
  • - ) - }, [ - fullQuery, - patternType, - extensionCoreAPI, - showActionButtonExperimentalVersion, - onCreateCodeMonitorButtonClick, - canCreateMonitorFromQuery, - platformContext.telemetryService, - instanceURL, - ]) - - const ShareLinkButton = useMemo( - () => ( - -
  • - -
  • -
    - ), - [onShareResultsClick] - ) - - return ( -
    -
    - {stats} -
    -
      - {createCodeMonitorButton} - {ShareLinkButton} -
    -
    -
    - ) -} diff --git a/client/vscode/src/webview/search-panel/components/icons.tsx b/client/vscode/src/webview/search-panel/components/icons.tsx deleted file mode 100644 index e0b14b3cfd6..00000000000 --- a/client/vscode/src/webview/search-panel/components/icons.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import * as React from 'react' - -export const BookmarkRadialGradientIcon = React.memo(() => ( - - - - - - - - - - -)) - -export const SearchBetaIcon = React.memo(() => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - -)) - -export const CodeMonitoringLogo: React.FunctionComponent>> = ( - props: React.SVGProps -) => ( - - - -) diff --git a/client/vscode/src/webview/search-panel/index.module.scss b/client/vscode/src/webview/search-panel/index.module.scss deleted file mode 100644 index 467cf864856..00000000000 --- a/client/vscode/src/webview/search-panel/index.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -@import 'wildcard/src/global-styles/breakpoints'; - -:global(body) { - padding: 0 1rem; -} - -.search-box { - background-color: var(--vscode-editorWidget-background) !important; - - &:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - // Override for search context separator - --border-color-2: var(--vscode-textLink-foreground); - // For button hover background - --color-bg-2: var(--vscode-input-background); - - .btn:hover { - background-color: var(--vscode-input-background); - } -} - -.search-box-container { - @media (--xs-breakpoint-down) { - background-color: var(--vscode-editorWidget-background); - } -} - -.logo { - flex: 0 0 auto; - display: flex; - align-items: center; - width: 14.5rem; - margin-top: 7rem; - max-width: 90%; - - /* Reserve the image's height to avoid jitter before it loads. */ - min-height: 3.375rem; -} - -.logo-text { - margin-top: 0.5rem; - font-style: italic; - font-weight: normal; - align-items: center; - color: var(--vscode-descriptionForeground) !important; - font-size: var(--vscode-font-size) !important; -} - -.feedback-button { - font-size: var(--vscode-font-size) !important; -} - -.home-search-box-container { - flex: 1 1 auto; - flex-grow: 0; - margin-top: 2rem; - margin-bottom: 4.5rem; - width: 100%; - max-width: var(--max-homepage-container-width); - - position: relative; -} - -// For fixed search box -.results-view { - &-layout { - display: flex; - width: 100%; - height: 100%; - flex-direction: column; - align-items: center; - isolation: isolate; - } - - &-scroll-container { - padding: 0 1rem 0 0.5rem; - min-height: 0; - overflow: auto; - width: 100%; - height: 100%; - } -} - -.cta-container { - background-color: var(--vscode-editorWidget-background) !important; - font-size: var(--vscode-font-size) !important; -} diff --git a/client/vscode/src/webview/search-panel/index.tsx b/client/vscode/src/webview/search-panel/index.tsx deleted file mode 100644 index 6e43202029b..00000000000 --- a/client/vscode/src/webview/search-panel/index.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import '../platform/polyfills' - -import React, { useMemo } from 'react' - -import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react' -import * as Comlink from 'comlink' -import { createRoot } from 'react-dom/client' -import { MemoryRouter } from 'react-router-dom' - -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { ShortcutProvider } from '@sourcegraph/shared/src/react-shortcuts' -import { ThemeSetting, ThemeContext } from '@sourcegraph/shared/src/theme' -import { AnchorLink, setLinkComponent, useObservable, WildcardThemeContext } from '@sourcegraph/wildcard' - -import type { ExtensionCoreAPI } from '../../contract' -import type { VsCodeApi } from '../../vsCodeApi' -import { createEndpointsForWebToNode } from '../comlink/webviewEndpoint' -import { createPlatformContext, WebviewPageContext, type WebviewPageProps } from '../platform/context' -import { adaptSourcegraphThemeToEditorTheme } from '../theming/sourcegraphTheme' - -import { searchPanelAPI } from './api' -import { SearchHomeView } from './SearchHomeView' -import { SearchResultsView } from './SearchResultsView' - -import './index.module.scss' - -declare const acquireVsCodeApi: () => VsCodeApi - -const vsCodeApi = acquireVsCodeApi() - -const { proxy, expose } = createEndpointsForWebToNode(vsCodeApi) - -Comlink.expose(searchPanelAPI, expose) - -export const extensionCoreAPI: Comlink.Remote = Comlink.wrap(proxy) - -const themes = adaptSourcegraphThemeToEditorTheme() - -extensionCoreAPI.panelInitialized(document.documentElement.dataset.panelId!).catch(() => { - // noop (TODO?) -}) - -const platformContext = createPlatformContext(extensionCoreAPI) - -setLinkComponent(AnchorLink) - -const Main: React.FC> = () => { - const theme = useObservable(themes) - const state = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.observeState()), [])) - const instanceURL = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.getInstanceURL()), [])) - const authenticatedUser = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.getAuthenticatedUser()), []) - ) - const settingsCascade = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.observeSourcegraphSettings()), []) - ) - // Do not block rendering on settings unless we observe UI jitter - - // TODO: If init is taking too long, show a message. - // Also check if anything has errored out. - - // If any of the remote values have yet to load. - const initialized = - state !== undefined && - authenticatedUser !== undefined && - instanceURL !== undefined && - theme !== undefined && - settingsCascade !== undefined - - const themeSetting = useMemo( - () => ({ themeSetting: theme === 'theme-light' ? ThemeSetting.Light : ThemeSetting.Dark }), - [theme] - ) - - if (!initialized) { - return - } - - const webviewPageProps: WebviewPageProps = { - extensionCoreAPI, - platformContext, - authenticatedUser, - settingsCascade, - instanceURL, - } - - if (state?.status === 'context-invalidated') { - // TODO context-invalidated state - return null - } - - // Render SearchHomeView until the user submits a search. - if (state.context.submittedSearchQueryState === null) { - return ( - - - - - - ) - } - - return ( - - - - - - ) -} - -const root = createRoot(document.querySelector('#root')!) - -root.render( - - - {/* Required for shared components that depend on `location`. */} - -
    - - - -) diff --git a/client/vscode/src/webview/sidebars/auth/AuthSidebarView.module.scss b/client/vscode/src/webview/sidebars/auth/AuthSidebarView.module.scss deleted file mode 100644 index 19156283752..00000000000 --- a/client/vscode/src/webview/sidebars/auth/AuthSidebarView.module.scss +++ /dev/null @@ -1,57 +0,0 @@ -.form-container { - margin-top: 2.5rem; -} - -.cta-container { - letter-spacing: 0.5px; - font-size: var(--vscode-editor-font-size) !important; - color: var(--vscode-foreground); - margin-top: 0.25rem; - margin-bottom: 0.25rem; -} - -.cta-paragraph { - margin-bottom: 1rem; - font-weight: 400; -} - -.cta-input { - font-size: var(--vscode-editor-font-size) !important; - - &:active { - border-color: var(--vscode-inputOption-activeBorder); - } - - &:focus { - border-color: var(--vscode-inputOption-activeBorder); - } -} - -.cta-title { - margin-top: 0.25rem !important; - margin-bottom: 0.5rem !important; -} - -.cta { - display: flex; - flex-direction: column; - align-items: left; - justify-content: center; - margin-bottom: 2.5rem; -} - -.cta-button { - justify-content: center; - width: 100%; -} - -.cta-link { - &:hover { - background: var(--button-primary-hover-background); - text-decoration: none !important; - } -} - -.cta-button-wrapper-with-context-below { - margin-bottom: 0.5rem; -} diff --git a/client/vscode/src/webview/sidebars/auth/AuthSidebarView.tsx b/client/vscode/src/webview/sidebars/auth/AuthSidebarView.tsx deleted file mode 100644 index e2d0a051a34..00000000000 --- a/client/vscode/src/webview/sidebars/auth/AuthSidebarView.tsx +++ /dev/null @@ -1,325 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react' - -import { VSCodeButton, VSCodeLink } from '@vscode/webview-ui-toolkit/react' -import classNames from 'classnames' - -import { currentAuthStateQuery } from '@sourcegraph/shared/src/auth' -import type { CurrentAuthStateResult, CurrentAuthStateVariables } from '@sourcegraph/shared/src/graphql-operations' -import { Alert, Text, Link, Input, H5, Form } from '@sourcegraph/wildcard' - -import { - VSCE_LINK_DOTCOM, - VSCE_LINK_MARKETPLACE, - VSCE_LINK_AUTH, - VSCE_LINK_TOKEN_CALLBACK, - VSCE_LINK_TOKEN_CALLBACK_TEST, - VSCE_LINK_USER_DOCS, - VSCE_SIDEBAR_PARAMS, -} from '../../../common/links' -import type { WebviewPageProps } from '../../platform/context' - -import styles from './AuthSidebarView.module.scss' - -interface AuthSidebarViewProps - extends Pick {} - -interface AuthSidebarCtaProps extends Pick {} - -/** - * Rendered by sidebar in search-home state when user doesn't have a valid access token. - */ -export const AuthSidebarView: React.FunctionComponent> = ({ - instanceURL, - extensionCoreAPI, - platformContext, - authenticatedUser, -}) => { - const [state, setState] = useState<'initial' | 'validating' | 'success' | 'failure'>('initial') - const [hasAccount, setHasAccount] = useState(authenticatedUser?.username !== undefined) - const [usePrivateInstance, setUsePrivateInstance] = useState(true) - const signUpURL = VSCE_LINK_AUTH('sign-up') - const instanceHostname = useMemo(() => new URL(instanceURL).hostname, [instanceURL]) - const [hostname, setHostname] = useState(instanceHostname) - const [accessToken, setAccessToken] = useState('initial') - const [endpointUrl, setEndpointUrl] = useState(instanceURL) - const sourcegraphDotCom = 'https://www.sourcegraph.com' - const isSourcegraphDotCom = useMemo(() => { - const hostname = new URL(instanceURL).hostname - if (hostname === 'sourcegraph.com' || hostname === 'www.sourcegraph.com') { - return VSCE_LINK_TOKEN_CALLBACK - } - if (hostname === 'sourcegraph.test') { - return VSCE_LINK_TOKEN_CALLBACK_TEST - } - return null - }, [instanceURL]) - - useEffect(() => { - // Get access token from setting - if (accessToken === 'initial') { - extensionCoreAPI.getAccessToken - .then(token => { - setAccessToken(token) - // If an access token and endpoint url exist at initial load, - // assumes the extension was started with a bad token because - // user should be autheticated automatically if token is valid - if (endpointUrl && token) { - extensionCoreAPI - .removeAccessToken() - .then(() => { - setAccessToken('REMOVED') - setState('failure') - }) - .catch(() => {}) - } - }) - .catch(error => console.error(error)) - } - }, [ - accessToken, - endpointUrl, - extensionCoreAPI, - extensionCoreAPI.getAccessToken, - extensionCoreAPI.removeAccessToken, - ]) - - const onTokenInputChange = useCallback((event: React.ChangeEvent) => { - setAccessToken(event.target.value) - }, []) - - const onInstanceURLInputChange = useCallback((event: React.ChangeEvent) => { - setEndpointUrl(event.target.value) - }, []) - - const onInstanceTypeChange = useCallback(() => { - setUsePrivateInstance(!usePrivateInstance) - if (!usePrivateInstance) { - setEndpointUrl(sourcegraphDotCom) - } - }, [usePrivateInstance]) - - const validateAccessToken: React.FormEventHandler = (event): void => { - event.preventDefault() - if (state !== 'validating' && accessToken) { - const authStateVariables = { - request: currentAuthStateQuery, - variables: {}, - mightContainPrivateInfo: true, - overrideAccessToken: accessToken, - overrideSourcegraphURL: endpointUrl, - } - if (usePrivateInstance) { - setHostname(new URL(endpointUrl).hostname) - authStateVariables.overrideSourcegraphURL = endpointUrl - } - try { - setState('validating') - const currentAuthStateResult = platformContext - .requestGraphQL(authStateVariables) - .toPromise() - currentAuthStateResult - .then(async ({ data }) => { - if (data?.currentUser) { - await extensionCoreAPI.setEndpointUri(accessToken, endpointUrl) - setState('success') - return - } - setState('failure') - return - }) - // v2/debt: Disambiguate network vs auth errors like we do in the browser extension. - .catch(() => { - setState('failure') - }) - } catch (error) { - setState('failure') - console.error(error) - } - } - // If successful, update setting. This form will no longer be rendered - } - - const onSignUpClick = (): void => { - setHasAccount(true) - platformContext.telemetryService.log('VSCESidebarCreateAccount') - } - - if (state === 'success') { - // This form should no longer be rendered as the extension context - // will be invalidated. We should show a notification that the accessToken - // has successfully been updated. - return null - } - - const renderCommon = (content: JSX.Element): JSX.Element => ( -
    -
    -
    Search your private code
    - {content} -
    -
    - ) - - if (!hasAccount && !accessToken) { - return renderCommon( - <> - - Create an account to search across your private repositories and access advanced features: search - multiple repositories & commit history, monitor code changes, save searches, and more. - -
    - - - Create an account - - -
    - setHasAccount(true)}> - Have an account? - - - ) - } - - enum InputStates { - initial = 'initial', - validating = 'loading', - success = 'valid', - failure = 'error', - } - - return renderCommon( - <> - - Sign in by entering an access token created through your user settings on Sourcegraph. - - - See our {/* eslint-disable-next-line react/forbid-elements */}{' '} -
    platformContext.telemetryService.log('VSCESidebarCreateToken')} - > - user docs - {' '} - for a video guide on how to create an access token. - - {/* ---------- UNRELEASED FEATURE ---------- */} - {isSourcegraphDotCom && authenticatedUser?.displayName === 'sourcegraph' && ( - - - - Continue in browser - - - - )} - - - - {usePrivateInstance && ( - - - - )} - - Authenticate account - - {state === 'failure' && ( - - Unable to verify your access token for {hostname}. Please try again with a new access token or - restart VS Code if the instance URL has been updated. - - )} - - onInstanceTypeChange()}> - {!usePrivateInstance ? 'Need to connect to a private instance?' : 'Not a private instance user?'} - - - - - Create an account - - - - ) -} - -export const AuthSidebarCta: React.FunctionComponent> = ({ - platformContext, -}) => { - const onLinkClick = (type: 'Sourcegraph' | 'Extension'): void => - platformContext.telemetryService.log(`VSCESidebarLearn${type}Click`) - - return ( -
    -
    Welcome
    - - The Sourcegraph extension allows you to search millions of open source repositories without cloning them - to your local machine. - - - Developers use Sourcegraph every day to onboard to new code bases, find code to reuse, resolve - incidents, fix security vulnerabilities, and more. - -
    - Learn more: - onLinkClick('Sourcegraph')}> - Sourcegraph.com - -
    - onLinkClick('Extension')}> - Sourcegraph VS Code extension - -
    -
    - ) -} diff --git a/client/vscode/src/webview/sidebars/help/HelpSidebarView.module.scss b/client/vscode/src/webview/sidebars/help/HelpSidebarView.module.scss deleted file mode 100644 index 2466c9c785b..00000000000 --- a/client/vscode/src/webview/sidebars/help/HelpSidebarView.module.scss +++ /dev/null @@ -1,25 +0,0 @@ -.sidebar-container { - padding: 0.125rem 0 0 0 !important; - color: var(--vscode-foreground); - font-size: var(--vscode-editor-font-size) !important; - display: flex; - flex-direction: column; - - a { - color: var(--vscode-textLink-foreground); - } -} - -.icon { - width: calc(var(--vscode-font-size) * 1.05); - height: auto; -} - -.sidebar-view-button { - background-color: transparent; - color: inherit !important; - - &::part(control) { - justify-content: flex-start; - } -} diff --git a/client/vscode/src/webview/sidebars/help/HelpSidebarView.tsx b/client/vscode/src/webview/sidebars/help/HelpSidebarView.tsx deleted file mode 100644 index 32a8c600a5b..00000000000 --- a/client/vscode/src/webview/sidebars/help/HelpSidebarView.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import { VSCodeButton } from '@vscode/webview-ui-toolkit/react' -import classNames from 'classnames' - -import { Button, Text } from '@sourcegraph/wildcard' - -import { version } from '../../../../package.json' -import { - VSCE_LINK_FEEDBACK, - VSCE_LINK_ISSUES, - VSCE_LINK_TROUBLESHOOT, - VSCE_SG_LOGOMARK_DARK, - VSCE_SG_LOGOMARK_LIGHT, - VSCE_LINK_SIGNUP, -} from '../../../common/links' -import type { WebviewPageProps } from '../../platform/context' -import { AuthSidebarView } from '../auth/AuthSidebarView' - -import styles from './HelpSidebarView.module.scss' - -interface HelpSidebarViewProps - extends Pick {} - -/** - * Rendered by sidebar in search-home state when user doesn't have a valid access token. - */ -export const HelpSidebarView: React.FunctionComponent> = ({ - platformContext, - extensionCoreAPI, - authenticatedUser, - instanceURL, -}) => { - const [openAuthPanel, setOpenAuthPanel] = useState(false) - const [isLightTheme, setIsLightTheme] = useState(undefined) - - useEffect(() => { - if (isLightTheme === undefined) { - extensionCoreAPI.getEditorTheme - .then(theme => { - setIsLightTheme(theme === 'Light') - }) - .catch(error => { - console.error(error) - setIsLightTheme(false) - }) - } - }, [extensionCoreAPI.getEditorTheme, isLightTheme]) - - const onHelpItemClick = async (url: string, item: string): Promise => { - platformContext.telemetryService.log(`VSCEHelpSidebar${item}Click`) - await extensionCoreAPI.openLink(url) - } - - const onLogoutClick = async (): Promise => { - if (authenticatedUser) { - await extensionCoreAPI.removeAccessToken() - } - } - - return ( -
    - - - - - - {openAuthPanel && ( -
    - {!authenticatedUser ? ( - - ) : ( -
    - - Click button below to sign out of {new URL(instanceURL).hostname}. VS Code will be - reloaded upon sign out. - - -
    - )} -
    - )} - -
    - ) -} diff --git a/client/vscode/src/webview/sidebars/help/index.tsx b/client/vscode/src/webview/sidebars/help/index.tsx deleted file mode 100644 index 714bd302300..00000000000 --- a/client/vscode/src/webview/sidebars/help/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import '../../platform/polyfills' - -import React, { useMemo } from 'react' - -import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react' -import * as Comlink from 'comlink' -import { createRoot } from 'react-dom/client' - -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { AnchorLink, setLinkComponent, useObservable } from '@sourcegraph/wildcard' - -import type { ExtensionCoreAPI, HelpSidebarAPI } from '../../../contract' -import type { VsCodeApi } from '../../../vsCodeApi' -import { createEndpointsForWebToNode } from '../../comlink/webviewEndpoint' -import { createPlatformContext } from '../../platform/context' - -import { HelpSidebarView } from './HelpSidebarView' - -declare const acquireVsCodeApi: () => VsCodeApi - -const vsCodeApi = acquireVsCodeApi() - -const { proxy, expose } = createEndpointsForWebToNode(vsCodeApi) - -export const extensionCoreAPI: Comlink.Remote = Comlink.wrap(proxy) - -const helpSidebarAPI: HelpSidebarAPI = {} - -const platformContext = createPlatformContext(extensionCoreAPI) - -Comlink.expose(helpSidebarAPI, expose) - -setLinkComponent(AnchorLink) - -const Main: React.FC> = () => { - const authenticatedUser = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.getAuthenticatedUser()), []) - ) - - const state = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.observeState()), [])) - - const instanceURL = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.getInstanceURL()), [])) - if (authenticatedUser === undefined || instanceURL === undefined || state === undefined) { - return - } - - return ( - - ) -} - -const root = createRoot(document.querySelector('#root')!) - -root.render(
    ) diff --git a/client/vscode/src/webview/sidebars/history/HistorySidebarView.tsx b/client/vscode/src/webview/sidebars/history/HistorySidebarView.tsx deleted file mode 100644 index 13bda9cff3c..00000000000 --- a/client/vscode/src/webview/sidebars/history/HistorySidebarView.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' - -import classNames from 'classnames' - -import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' - -import type { WebviewPageProps } from '../../platform/context' - -import { RecentFilesSection } from './components/RecentFilesSection' -import { RecentRepositoriesSection } from './components/RecentRepositoriesSection' -import { RecentSearchesSection } from './components/RecentSearchesSection' - -import styles from '../search/SearchSidebarView.module.scss' - -export interface HistorySidebarProps extends WebviewPageProps { - authenticatedUser: AuthenticatedUser -} - -/** - * Search history sidebar for "home" page for authenticated users. - */ -export const HistoryHomeSidebar: React.FunctionComponent> = props => ( -
    - - - -
    -) diff --git a/client/vscode/src/webview/sidebars/history/components/RecentFilesSection.tsx b/client/vscode/src/webview/sidebars/history/components/RecentFilesSection.tsx deleted file mode 100644 index ee5d5d91166..00000000000 --- a/client/vscode/src/webview/sidebars/history/components/RecentFilesSection.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useMemo, useState } from 'react' - -import { mdiChevronDown, mdiChevronLeft } from '@mdi/js' -import classNames from 'classnames' - -import { type EventLogResult, fetchRecentFileViews } from '@sourcegraph/shared/src/search' -import { Icon, Link, H5, useObservable, Button } from '@sourcegraph/wildcard' - -import type { HistorySidebarProps } from '../HistorySidebarView' - -import styles from '../../search/SearchSidebarView.module.scss' - -interface RecentFile { - repoName: string - filePath: string - timestamp: string - url: string -} - -export const RecentFilesSection: React.FunctionComponent> = ({ - platformContext, - authenticatedUser, - extensionCoreAPI, -}) => { - const itemsToLoad = 15 - const [collapsed, setCollapsed] = useState(false) - - // Debt: lift this shared query up to HistorySidebarView. - const recentFilesResult = useObservable( - useMemo( - () => fetchRecentFileViews(authenticatedUser.id, itemsToLoad, platformContext), - [authenticatedUser.id, itemsToLoad, platformContext] - ) - ) - - if (!recentFilesResult) { - return null - } - - const processedFiles = processRecentFiles(recentFilesResult) - - if (!processedFiles) { - return null - } - - const onRecentFileClick = (uri: string): void => { - platformContext.telemetryService.log('VSCERecentFilesClick') - extensionCoreAPI.openSourcegraphFile(uri).catch(error => { - // TODO surface to user - console.error('Error submitting search from Sourcegraph sidebar', error) - }) - } - - return ( -
    - - - {!collapsed && ( -
    - {processedFiles - ?.filter((search, index) => index < itemsToLoad) - .map((recentFile, index) => ( -
    - - onRecentFileClick(recentFile.url)} - > - {recentFile.repoName.split('@')[0]} › {recentFile.filePath} - - -
    - ))} -
    - )} -
    - ) -} - -function processRecentFiles(eventLogResult?: EventLogResult): RecentFile[] | null { - if (!eventLogResult) { - return null - } - - const recentFiles: RecentFile[] = [] - - for (const node of eventLogResult.nodes) { - if (node.argument && node.url) { - const parsedArguments = JSON.parse(node.argument) - let repoName = parsedArguments?.repoName as string - let filePath = parsedArguments?.filePath as string - - if (!repoName || !filePath) { - ;({ repoName, filePath } = extractFileInfoFromUrl(node.url)) - } - - if ( - filePath && - repoName && - !recentFiles.some(file => file.repoName === repoName && file.filePath === filePath) // Don't show the same file twice - ) { - recentFiles.push({ - url: node.url.replace('https://', 'sourcegraph://'), // So that clicking on link would open the file directly - repoName, - filePath, - timestamp: node.timestamp, - }) - } - } - } - - return recentFiles -} - -function extractFileInfoFromUrl(url: string): { repoName: string; filePath: string } { - const parsedUrl = new URL(url) - - // Remove first character as it's a '/' - const [repoName, filePath] = parsedUrl.pathname.slice(1).split('/-/blob/') - if (!repoName || !filePath) { - return { repoName: '', filePath: '' } - } - return { repoName, filePath } -} diff --git a/client/vscode/src/webview/sidebars/history/components/RecentRepositoriesSection.tsx b/client/vscode/src/webview/sidebars/history/components/RecentRepositoriesSection.tsx deleted file mode 100644 index ef573a424eb..00000000000 --- a/client/vscode/src/webview/sidebars/history/components/RecentRepositoriesSection.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { useMemo, useState } from 'react' - -import { mdiChevronDown, mdiChevronLeft } from '@mdi/js' -import classNames from 'classnames' - -import { SyntaxHighlightedSearchQuery } from '@sourcegraph/branded' -import { type EventLogResult, fetchRecentSearches } from '@sourcegraph/shared/src/search' -import { scanSearchQuery } from '@sourcegraph/shared/src/search/query/scanner' -import { isRepoFilter } from '@sourcegraph/shared/src/search/query/validate' -import { LATEST_VERSION } from '@sourcegraph/shared/src/search/stream' -import { Icon, H5, useObservable, Button } from '@sourcegraph/wildcard' - -import { SearchPatternType } from '../../../../graphql-operations' -import type { HistorySidebarProps } from '../HistorySidebarView' - -import styles from '../../search/SearchSidebarView.module.scss' - -export const RecentRepositoriesSection: React.FunctionComponent> = ({ - platformContext, - authenticatedUser, - extensionCoreAPI, -}) => { - const itemsToLoad = 15 - const [collapsed, setCollapsed] = useState(false) - - // Debt: lift this shared query up to HistorySidebarView. - const recentRepositoriesResult = useObservable( - useMemo( - () => fetchRecentSearches(authenticatedUser.id, itemsToLoad, platformContext), - [authenticatedUser.id, itemsToLoad, platformContext] - ) - ) - - if (!recentRepositoriesResult) { - return null - } - - const processedRepositories = processRepositories(recentRepositoriesResult) - - if (!processedRepositories) { - return null - } - - const onRecentRepositoryClick = (query: string): void => { - platformContext.telemetryService.log('VSCERecentRepositoryClick') - extensionCoreAPI - .streamSearch(query, { - // Debt: using defaults here. The recent repository should override these, though. - caseSensitive: false, - patternType: SearchPatternType.standard, - version: LATEST_VERSION, - trace: undefined, - }) - .catch(error => { - // TODO surface to user - console.error('Error submitting search from Sourcegraph sidebar', error) - }) - } - - return ( -
    - - - {!collapsed && ( -
    - {processedRepositories - .filter((search, index) => index < itemsToLoad) - .map((repository, index) => ( -
    - - - -
    - ))} -
    - )} -
    - ) -} - -export function parseSearchURLQuery(query: string): string | undefined { - const searchParameters = new URLSearchParams(query) - return searchParameters.get('q') || undefined -} - -function processRepositories(eventLogResult: EventLogResult): string[] | null { - if (!eventLogResult) { - return null - } - const recentlySearchedRepos: string[] = [] - for (const node of eventLogResult.nodes) { - if (node.url) { - const url = new URL(node.url) - const queryFromURL = parseSearchURLQuery(url.search) - const scannedQuery = scanSearchQuery(queryFromURL || '') - if (scannedQuery.type === 'success') { - for (const token of scannedQuery.term) { - if (isRepoFilter(token)) { - if (token.value && !recentlySearchedRepos.includes(token.value.value)) { - recentlySearchedRepos.push(token.value.value) - } - } - } - } - } - } - return recentlySearchedRepos -} diff --git a/client/vscode/src/webview/sidebars/history/components/RecentSearchesSection.tsx b/client/vscode/src/webview/sidebars/history/components/RecentSearchesSection.tsx deleted file mode 100644 index abd4feba5aa..00000000000 --- a/client/vscode/src/webview/sidebars/history/components/RecentSearchesSection.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useMemo, useState } from 'react' - -import { mdiChevronDown, mdiChevronLeft } from '@mdi/js' -import classNames from 'classnames' - -import { SyntaxHighlightedSearchQuery } from '@sourcegraph/branded' -import { type EventLogResult, fetchRecentSearches } from '@sourcegraph/shared/src/search' -import { LATEST_VERSION } from '@sourcegraph/shared/src/search/stream' -import { Icon, H5, useObservable, Button } from '@sourcegraph/wildcard' - -import { SearchPatternType } from '../../../../graphql-operations' -import type { HistorySidebarProps } from '../HistorySidebarView' - -import styles from '../../search/SearchSidebarView.module.scss' - -export const RecentSearchesSection: React.FunctionComponent> = ({ - platformContext, - extensionCoreAPI, - authenticatedUser, -}) => { - const itemsToLoad = 15 - const [collapsed, setCollapsed] = useState(false) - - const recentSearchesResult = useObservable( - useMemo( - () => fetchRecentSearches(authenticatedUser.id, itemsToLoad, platformContext), - [authenticatedUser.id, itemsToLoad, platformContext] - ) - ) - - const recentSearches: RecentSearch[] | null = useMemo( - () => processRecentSearches(recentSearchesResult ?? undefined), - [recentSearchesResult] - ) - - if (!recentSearches) { - return null - } - - const onSearchClick = (query: string): void => { - platformContext.telemetryService.log('VSCERecentSearchClick') - extensionCoreAPI - .streamSearch(query, { - // Debt: using defaults here. The recent search should override these, though. - caseSensitive: false, - patternType: SearchPatternType.standard, - version: LATEST_VERSION, - trace: undefined, - }) - .catch(error => { - // TODO surface to user - console.error('Error submitting search from Sourcegraph sidebar', error) - }) - } - - return ( -
    - - - {!collapsed && ( -
    - {recentSearches - .filter((search, index) => index < itemsToLoad) - .map(search => ( -
    - - - -
    - ))} -
    - )} -
    - ) -} - -interface RecentSearch { - count: number - searchText: string - timestamp: string - url: string -} - -function processRecentSearches(eventLogResult?: EventLogResult): RecentSearch[] | null { - if (!eventLogResult) { - return null - } - - const recentSearches: RecentSearch[] = [] - - for (const node of eventLogResult.nodes) { - if (node.argument && node.url) { - const parsedArguments = JSON.parse(node.argument) - const searchText: string | undefined = parsedArguments?.code_search?.query_data?.combined - - if (searchText) { - if (recentSearches.length > 0 && recentSearches.at(-1)!.searchText === searchText) { - recentSearches.at(-1)!.count += 1 - } else { - const parsedUrl = new URL(node.url) - recentSearches.push({ - count: 1, - url: parsedUrl.pathname + parsedUrl.search, // Strip domain from URL so clicking on it doesn't reload page - searchText, - timestamp: node.timestamp, - }) - } - } - } - } - - return recentSearches -} diff --git a/client/vscode/src/webview/sidebars/search/ContextInvalidatedSidebarView.tsx b/client/vscode/src/webview/sidebars/search/ContextInvalidatedSidebarView.tsx deleted file mode 100644 index 201189b9b3f..00000000000 --- a/client/vscode/src/webview/sidebars/search/ContextInvalidatedSidebarView.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' - -import { Button, H5, Text } from '@sourcegraph/wildcard' - -import type { WebviewPageProps } from '../../platform/context' - -export interface ContextInvalidatedSidebarViewProps extends WebviewPageProps {} - -export const ContextInvalidatedSidebarView: React.FunctionComponent< - React.PropsWithChildren -> = ({ extensionCoreAPI }) => ( -
    -
    New URL detected
    - Your Sourcegraph instance URL has changed. - Please reload VS Code to use the Sourcegraph extension. - -
    -) diff --git a/client/vscode/src/webview/sidebars/search/SearchSidebarView.module.scss b/client/vscode/src/webview/sidebars/search/SearchSidebarView.module.scss deleted file mode 100644 index 838b9c89269..00000000000 --- a/client/vscode/src/webview/sidebars/search/SearchSidebarView.module.scss +++ /dev/null @@ -1,170 +0,0 @@ -.sidebar-container { - padding: 0.125rem 0 0 0 !important; - - /* stylelint-disable-next-line scss/selector-no-redundant-nesting-selector */ - & [data-reach-tabs] { - // Override our default background color. - background: none; - } - - --brand-secondary: var(--vscode-button-hoverBackground); - --link-hover-color: var(--vscode-textLink-foreground); - - .btn-outline-secondary:hover { - background: transparent !important; - } - - a, - ul, - li, - span, - button { - font-size: var(--vscode-editor-font-size) !important; - } - - &__sticky-box { - @media (--md-breakpoint-down) { - // Sidebar shouldn't be sticky in smaller screens - position: static !important; - - display: flex; - flex-direction: row; - flex-wrap: wrap; - column-gap: 1.5rem; - } - - @media (--xs-breakpoint-down) { - display: block; - } - } -} - -.sidebar-section { - isolation: isolate; // Prevent the z-index below from leaking out of this container - margin-bottom: 1.5rem; - - &__tabs-header { - margin-top: 1.25rem; - margin-bottom: 0.5rem; - } - - &__button-link { - // The global font-weight for .btn elements is 500, which also applies to - // link-like buttons. But in this context we want it to be 400. - font-weight: 300; - color: var(--vscode-textLink-foreground) !important; - font-size: var(--vscode-editor-font-size) !important; - padding: 0; - margin: 0; - } - - &__collapse-button { - display: flex; - align-items: center; - text-align: left; - width: calc(100% + 0.5rem); // Take full width + account for negative margin - border: none; - padding: 0.25rem; - margin: 0 -0.25rem; - - // Force the button's box-shadow to always show over the sibling element's border - position: relative; - z-index: 1; - } - - &__list { - list-style-type: none; - max-height: 8rem; - overflow: auto; - // Negative margin and positive padding allows focus rings to not be cut off by overflow:auto - margin: 0 -0.125rem; - padding: 0.125rem; - } - - &__file-list { - list-style-type: none; - max-height: 10rem; - overflow: auto; - // Negative margin and positive padding allows focus rings to not be cut off by overflow:auto - margin: 0 -0.125rem; - padding: 0.125rem; - } - - &__cta { - list-style-type: none; - overflow: auto; - min-height: 8rem; - // Negative margin and positive padding allows focus rings to not be cut off by overflow:auto - margin: 0 -0.125rem; - padding: 0.125rem; - } - - &__search-box { - display: block; - margin: 0.25rem 0; - } - - &__list-item { - display: flex; - // font-size: 0.75rem; - font-size: var(--vscode-editor-font-size); - padding: 0.25rem 0.375rem; - border: 0; - border-radius: 3px; - width: 100%; - text-align: left; - overflow-wrap: anywhere; - - &:hover { - background-color: var(--color-bg-2); - text-decoration: none; - } - - &:focus { - text-decoration: none; - } - - &:active { - background-color: var(--color-bg-3); - } - - &--break-words { - word-break: break-all; - } - } - - &__icon { - margin: 0 0.125rem; - } - - &__no-results { - font-size: 0.75rem; - padding: 0.25rem 0.375rem; - } - - &__cta-link { - font-size: 0.6875rem; - padding: 0.25rem 0.375rem; - } - - // A helper class for sections that render custom content - &__footer { - border-top: 1px solid var(--border-color); - padding-top: 0.75rem; - margin: 0; - } -} - -.title { - text-transform: uppercase; - font-size: var(--vscode-font-size) !important; - color: var(--vscode-textPreformat-foreground); -} - -.button, -.button:link, -.button:visited { - font-size: var(--vscode-font-size) !important; - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); -} diff --git a/client/vscode/src/webview/sidebars/search/SearchSidebarView.tsx b/client/vscode/src/webview/sidebars/search/SearchSidebarView.tsx deleted file mode 100644 index 410175d9e6a..00000000000 --- a/client/vscode/src/webview/sidebars/search/SearchSidebarView.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import React, { type FC, type ReactElement, type ReactNode, useCallback, useMemo } from 'react' - -import { useLocation, useNavigate } from 'react-router-dom' -import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect' -import create from 'zustand' - -import { - getDynamicFilterLinks, - getFiltersOfKind, - getQuickLinks, - getRepoFilterLinks, - getSearchReferenceFactory, - getSearchSnippetLinks, - SearchSidebar, - SearchSidebarSection, -} from '@sourcegraph/branded' -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { - InitialParametersSource, - type QueryUpdate, - SearchMode, - type SearchQueryState, - type SearchQueryStateStore, - updateQuery, -} from '@sourcegraph/shared/src/search' -import { FilterType } from '@sourcegraph/shared/src/search/query/filters' -import { type Filter, LATEST_VERSION } from '@sourcegraph/shared/src/search/stream' -import { SectionID } from '@sourcegraph/shared/src/settings/temporary/searchSidebar' -import { useTemporarySetting } from '@sourcegraph/shared/src/settings/temporary/useTemporarySetting' -import { Code, useObservable } from '@sourcegraph/wildcard' - -import { SearchPatternType } from '../../../graphql-operations' -import type { WebviewPageProps } from '../../platform/context' - -import styles from './SearchSidebarView.module.scss' - -interface SearchSidebarViewProps - extends Pick { - filters?: Filter[] | undefined -} - -export const SearchSidebarView: FC = React.memo(function SearchSidebarView({ - settingsCascade, - platformContext, - extensionCoreAPI, - filters, -}) { - const navigate = useNavigate() - const location = useLocation() - const [, setCollapsed] = useTemporarySetting('search.sidebar.collapsed', false) - - // TODO: Get rid of Zustand create store since this is no longer needed after sidebar decoupling - const useSearchQueryState: SearchQueryStateStore = useMemo( - () => - create((set, get) => ({ - parametersSource: InitialParametersSource.DEFAULT, - queryState: { query: '' }, - searchCaseSensitivity: false, - searchPatternType: SearchPatternType.standard, - searchQueryFromURL: '', - searchMode: SearchMode.Precise, - - setQueryState: queryStateUpdate => { - const currentSearchQueryState = get() - const updatedQueryState = - typeof queryStateUpdate === 'function' - ? queryStateUpdate(currentSearchQueryState.queryState) - : queryStateUpdate - - extensionCoreAPI - .emit({ - type: 'sidebar_query_update', - proposedQueryState: { - queryState: updatedQueryState, - searchCaseSensitivity: currentSearchQueryState.searchCaseSensitivity, - searchPatternType: currentSearchQueryState.searchPatternType, - searchMode: currentSearchQueryState.searchMode, - }, - currentQueryState: { - // Don't spread currentSearchQueryState as it contains un-clone-able functions. - queryState: currentSearchQueryState.queryState, - searchCaseSensitivity: currentSearchQueryState.searchCaseSensitivity, - searchPatternType: currentSearchQueryState.searchPatternType, - searchMode: currentSearchQueryState.searchMode, - }, - }) - .catch(error => { - // TODO surface to user - console.error('Error updating search query from Sourcegraph sidebar', error) - }) - - extensionCoreAPI.focusSearchPanel().catch(() => { - // noop. - }) - }, - submitSearch: (_submitSearchParameters, updates = []) => { - const previousSearchQueryState = get() - const updatedQuery = updateQuery(previousSearchQueryState.queryState.query, updates) - extensionCoreAPI - .streamSearch(updatedQuery, { - caseSensitive: previousSearchQueryState.searchCaseSensitivity, - patternType: previousSearchQueryState.searchPatternType, - version: LATEST_VERSION, - trace: undefined, - }) - .catch(error => { - // TODO surface to user - console.error('Error submitting search from Sourcegraph sidebar', error) - }) - - extensionCoreAPI.focusSearchPanel().catch(() => { - // noop. - }) - }, - })), - [extensionCoreAPI] - ) - - const searchQueryStateFromPanel = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.observePanelQueryState()), [extensionCoreAPI]) - ) - - useDeepCompareEffectNoCheck(() => { - if (searchQueryStateFromPanel) { - useSearchQueryState.setState({ - queryState: searchQueryStateFromPanel.queryState, - searchCaseSensitivity: searchQueryStateFromPanel.searchCaseSensitivity, - searchPatternType: searchQueryStateFromPanel.searchPatternType, - }) - } - }, [searchQueryStateFromPanel]) - - const submitSearch = useSearchQueryState(state => state.submitSearch) - const setQueryState = useSearchQueryState(state => state.setQueryState) - - const handleSidebarSearchSubmit = useCallback( - (updates: QueryUpdate[]) => - submitSearch( - { - historyOrNavigate: navigate, - location, - source: 'filter', - }, - updates - ), - [navigate, location, submitSearch] - ) - - const onDynamicFilterClicked = useCallback( - (value: string, kind?: string) => { - platformContext.telemetryService.log('DynamicFilterClicked', { search_filter: { kind } }) - handleSidebarSearchSubmit([{ type: 'toggleSubquery', value }]) - }, - [handleSidebarSearchSubmit, platformContext.telemetryService] - ) - - const onSnippetClicked = useCallback( - (value: string) => { - platformContext.telemetryService.log('SearchSnippetClicked') - handleSidebarSearchSubmit([{ type: 'toggleSubquery', value }]) - }, - [handleSidebarSearchSubmit, platformContext.telemetryService] - ) - - const repoFilters = useMemo(() => getFiltersOfKind(filters, FilterType.repo), [filters]) - - return ( - setCollapsed(true)} className={styles.sidebarContainer}> - - {getDynamicFilterLinks( - filters, - ['lang', 'file', 'utility'], - onDynamicFilterClicked, - (label, value) => `Filter by ${value}`, - (label, value) => value - )} - - - - {getRepoFilterLinks(repoFilters, onDynamicFilterClicked)} - - - - {getSearchReferenceFactory({ - telemetryService: platformContext.telemetryService, - setQueryState, - })} - - - - {getSearchSnippetLinks(settingsCascade, onSnippetClicked)} - - - - {getQuickLinks(settingsCascade)} - - - ) -}) - -const getRepoFilterNoResultText = (repoFilterLinks: ReactElement[]): ReactNode => ( - - None of the top {repoFilterLinks.length} repositories in your results match this filter. Try a{' '} - repo: search in the main search bar instead. - -) diff --git a/client/vscode/src/webview/sidebars/search/api.ts b/client/vscode/src/webview/sidebars/search/api.ts deleted file mode 100644 index ad77e8c5c57..00000000000 --- a/client/vscode/src/webview/sidebars/search/api.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { of } from 'rxjs' - -import { proxySubscribable } from '@sourcegraph/shared/src/api/extension/api/common' - -import type { SearchSidebarAPI } from '../../../contract' -import type { WebviewPageProps } from '../../platform/context' - -import { createVSCodeExtensionsController } from './extension-host' - -export function createSearchSidebarAPI( - webviewPageProps: Pick -): SearchSidebarAPI { - return { - ping: () => { - console.log('ping called') - return proxySubscribable(of('pong')) - }, - ...createVSCodeExtensionsController(webviewPageProps), - } -} diff --git a/client/vscode/src/webview/sidebars/search/extension-host/index.ts b/client/vscode/src/webview/sidebars/search/extension-host/index.ts deleted file mode 100644 index 15605caa617..00000000000 --- a/client/vscode/src/webview/sidebars/search/extension-host/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { from } from 'rxjs' -import { switchMap } from 'rxjs/operators' -import type { Intersection } from 'utility-types' - -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import type { FlatExtensionHostAPI } from '@sourcegraph/shared/src/api/contract' -import { proxySubscribable } from '@sourcegraph/shared/src/api/extension/api/common' -import { createController as createExtensionsController } from '@sourcegraph/shared/src/extensions/createSyncLoadedController' - -import type { SearchSidebarAPI } from '../../../../contract' -import type { WebviewPageProps } from '../../../platform/context' - -import { createExtensionHost } from './worker' - -export function createVSCodeExtensionsController({ - platformContext, - instanceURL, -}: Pick): Intersection { - const extensionsController = createExtensionsController({ - ...platformContext, - sourcegraphURL: instanceURL, - createExtensionHost: () => Promise.resolve(createExtensionHost()), - }) - - return { - getDefinition: parameters => { - const definitions = from(extensionsController.extHostAPI).pipe( - switchMap(extensionHostAPI => wrapRemoteObservable(extensionHostAPI.getDefinition(parameters))) - ) - - return proxySubscribable(definitions) - }, - getReferences: (parameters, referenceContext) => { - const references = from(extensionsController.extHostAPI).pipe( - switchMap(extensionHostAPI => - wrapRemoteObservable(extensionHostAPI.getReferences(parameters, referenceContext)) - ) - ) - - return proxySubscribable(references) - }, - getHover: parameters => { - const hovers = from(extensionsController.extHostAPI).pipe( - switchMap(extensionHostAPI => wrapRemoteObservable(extensionHostAPI.getHover(parameters))) - ) - - return proxySubscribable(hovers) - }, - - addTextDocumentIfNotExists: textDocumentData => - extensionsController.extHostAPI.then(extensionHostAPI => - extensionHostAPI.addTextDocumentIfNotExists(textDocumentData) - ), - addViewerIfNotExists: viewer => - extensionsController.extHostAPI.then(extensionHostAPI => extensionHostAPI.addViewerIfNotExists(viewer)), - } -} diff --git a/client/vscode/src/webview/sidebars/search/extension-host/main.worker.ts b/client/vscode/src/webview/sidebars/search/extension-host/main.worker.ts deleted file mode 100644 index cf80b6cefa7..00000000000 --- a/client/vscode/src/webview/sidebars/search/extension-host/main.worker.ts +++ /dev/null @@ -1,25 +0,0 @@ -import '../../../platform/polyfills' - -import { startExtensionHost } from '@sourcegraph/shared/src/api/extension/extensionHost' - -import { createEndpointsForWebToWeb } from '../../../comlink/webviewEndpoint' - -async function extensionHostMain(): Promise { - try { - const { webview: proxy, worker: expose } = createEndpointsForWebToWeb({ - postMessage: message => self.postMessage(message), - addEventListener: (type, listener, options) => self.addEventListener(type, listener, options), - removeEventListener: (type, listener, options) => self.removeEventListener(type, listener, options), - }) - - const extensionHost = startExtensionHost({ proxy, expose }) - self.addEventListener('unload', () => extensionHost.unsubscribe()) - } catch (error) { - console.error('Error starting the extension host:', error) - self.close() - } - return Promise.resolve() -} - -// eslint-disable-next-line @typescript-eslint/no-floating-promises -extensionHostMain() diff --git a/client/vscode/src/webview/sidebars/search/extension-host/worker.ts b/client/vscode/src/webview/sidebars/search/extension-host/worker.ts deleted file mode 100644 index 7449cb19053..00000000000 --- a/client/vscode/src/webview/sidebars/search/extension-host/worker.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Subscription } from 'rxjs' - -import type { ClosableEndpointPair } from '@sourcegraph/shared/src/platform/context' - -import { createEndpointsForWebToWeb } from '../../../comlink/webviewEndpoint' - -/* eslint-disable import/extensions, @typescript-eslint/ban-ts-comment */ -// @ts-ignore -import ExtensionHostWorker from './main.worker.ts' - -/* eslint-enable import/extensions, @typescript-eslint/ban-ts-comment */ - -export function createExtensionHost(): ClosableEndpointPair { - const worker = new ExtensionHostWorker() - const { webview: expose, worker: proxy } = createEndpointsForWebToWeb(worker) - - return { endpoints: { expose, proxy }, subscription: new Subscription(() => worker.terminate()) } -} diff --git a/client/vscode/src/webview/sidebars/search/index.tsx b/client/vscode/src/webview/sidebars/search/index.tsx deleted file mode 100644 index d499da1a8f5..00000000000 --- a/client/vscode/src/webview/sidebars/search/index.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import '../../platform/polyfills' - -import React, { useMemo, useState } from 'react' - -import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react' -import * as Comlink from 'comlink' -import { createRoot } from 'react-dom/client' -import { createMemoryRouter, RouterProvider } from 'react-router-dom' -import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect' - -import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common' -import { ShortcutProvider } from '@sourcegraph/shared/src/react-shortcuts' -import type { Filter } from '@sourcegraph/shared/src/search/stream' -import { AnchorLink, setLinkComponent, useObservable, WildcardThemeContext } from '@sourcegraph/wildcard' - -import type { ExtensionCoreAPI } from '../../../contract' -import type { VsCodeApi } from '../../../vsCodeApi' -import { createEndpointsForWebToNode } from '../../comlink/webviewEndpoint' -import { createPlatformContext, type WebviewPageProps } from '../../platform/context' -import { adaptSourcegraphThemeToEditorTheme } from '../../theming/sourcegraphTheme' -import { AuthSidebarCta, AuthSidebarView } from '../auth/AuthSidebarView' -import { HistoryHomeSidebar } from '../history/HistorySidebarView' - -import { createSearchSidebarAPI } from './api' -import { ContextInvalidatedSidebarView } from './ContextInvalidatedSidebarView' -import { SearchSidebarView } from './SearchSidebarView' - -declare const acquireVsCodeApi: () => VsCodeApi - -const vsCodeApi = acquireVsCodeApi() - -const { proxy, expose } = createEndpointsForWebToNode(vsCodeApi) - -export const extensionCoreAPI: Comlink.Remote = Comlink.wrap(proxy) - -const platformContext = createPlatformContext(extensionCoreAPI) - -const searchSidebarAPI = createSearchSidebarAPI({ - platformContext, - instanceURL: document.documentElement.dataset.instanceUrl!, -}) -Comlink.expose(searchSidebarAPI, expose) - -setLinkComponent(AnchorLink) - -const themes = adaptSourcegraphThemeToEditorTheme() - -const Main: React.FC> = () => { - // Debt: make sure we only rerender on necessary changes - const state = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.observeState()), [])) - - const [filters, setFilters] = useState(undefined) - useDeepCompareEffectNoCheck(() => { - setFilters(state?.context.searchResults?.filters) - }, [state?.context.searchResults?.filters]) - - const authenticatedUser = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.getAuthenticatedUser()), []) - ) - - const instanceURL = useObservable(useMemo(() => wrapRemoteObservable(extensionCoreAPI.getInstanceURL()), [])) - - const theme = useObservable(themes) - - const settingsCascade = useObservable( - useMemo(() => wrapRemoteObservable(extensionCoreAPI.observeSourcegraphSettings()), []) - ) - // Do not block rendering on settings unless we observe UI jitter - - // Debt: If init is taking too long, show a message. - // Also check if anything has errored out. - - // If any of the remote values have yet to load. - const initialized = - state !== undefined && - authenticatedUser !== undefined && - instanceURL !== undefined && - theme !== undefined && - settingsCascade !== undefined - if (!initialized) { - return - } - - const webviewPageProps: WebviewPageProps = { - extensionCoreAPI, - platformContext, - authenticatedUser, - settingsCascade, - instanceURL, - } - - if (state.status === 'context-invalidated') { - return - } - - // If a search hasn't been performed yet - if (state.status === 'search-home' || !state.context.submittedSearchQueryState) { - // TODO: should we hide the access token form permanently if an unauthenticated user - // has performed a search before? Or just for this session? - if (!authenticatedUser) { - return ( - <> - - - - ) - } - return - } - - // is wrapped w/ React.memo so pass only necessary props. - return ( - <> - - - ) -} - -const root = createRoot(document.querySelector('#root')!) - -const routes = [ - { - path: '/*', - element:
    , - }, -] -const router = createMemoryRouter(routes, { - initialEntries: ['/'], -}) - -root.render( - - - - - -) diff --git a/client/vscode/src/webview/theming/highlight.scss b/client/vscode/src/webview/theming/highlight.scss deleted file mode 100644 index 944fa69bb83..00000000000 --- a/client/vscode/src/webview/theming/highlight.scss +++ /dev/null @@ -1,1025 +0,0 @@ -// stylelint-disable - -// Default token colors from "Dark (Visual Studio)" and "Light (Visual Studio)". -// It's conservative, meaning it doesn't highlght more specific token types that -// may correspond to different CSS variables across themes. -// We can manually override those for popular themes. -// Some colors are lifted from Dark/Light+ because they have -// corresponding CSS variables for classes that the defaults do not. -.sourcegraph-extension.theme-light, -.sourcegraph-extension.theme-dark { - td.line, - td.line::before { - color: var(--vscode-editorLineNumber-foreground); - } - - .match-highlight, - .match-highlight-sticky, - .selection-highlight, - .selection-highlight-sticky { - background-color: var(--vscode-editor-findMatchHighlightBackground); - } - - .hljs-comment, - .hljs-quote, - .hljs-variable { - color: var(--vscode-editor-foreground); - } - - .hljs-keyword, - .hljs-selector-tag, - .hljs-built_in, - .hljs-name, - .hljs-tag { - color: var(--vscode-debugTokenExpression-name); - } - - .hljs-string, - .hljs-title, - .hljs-section, - .hljs-attribute, - .hljs-literal, - .hljs-template-tag, - .hljs-template-variable, - .hljs-type { - color: var(--vscode-debugTokenExpression-string); - } - - .hljs-number { - color: var(--vscode-debugTokenExpression-number); - } - - .hl-source, - .hl-text { - color: var(--vscode-editor-foreground); - } - - .hl-punctuation.hl-definition { - color: var(--hl-gray-0); - } - - .hl-keyword { - color: var(--vscode-debugTokenExpression-name); - } - - .hl-variable { - color: var(--vscode-editor-foreground); - } - - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: var(--vscode-editor-foreground); - } - - // Classes - .hl-support.hl-class, - .hl-entity.hl-name.hl-class, - .hl-meta.hl-class { - color: var(--vscode-editor-foreground); - } - .hl-entity.hl-other.hl-inherited-class { - color: var(--vscode-editor-foreground); - } - - // Storage - .hl-storage { - color: var(--vscode-debugTokenExpression-name); - } - - // Support - .hl-support.hl-function { - color: var(--vscode-editor-foreground); - } - - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: var(--vscode-debugTokenExpression-string); - } - - .hl-number { - color: var(--vscode-debugTokenExpression-number); - } - - // regexp literals - .hl-string.hl-regexp { - color: var(--vscode-textPreformat-foreground); - } - // escape sequences - .hl-constant.hl-character.hl-escape { - color: var(--vscode-textPreformat-foreground); - } - - // Constants - .hl-constant { - color: var(--vscode-editor-foreground); - } - .hl-constant.hl-numeric { - color: var(--vscode-debugTokenExpression-number); - } - .hl-constant.hl-other.hl-color { - color: var(--vscode-editor-foreground); - } - - // Tags - .hl-entity.hl-name.hl-tag { - color: var(--vscode-debugView-valueChangedHighlight); - } - - // Attributes - .hl-entity.hl-other.hl-attribute-name { - color: var(--vscode-debugTokenExpression-string); - } - - // Attribute IDs - .hl-entity.hl-other.hl-attribute-name.hl-id, - .hl-punctuation.hl-definition.hl-entity { - color: var(--vscode-editor-foreground); - } - - // Selector - .hl-entity.hl-other.hl-attribute-name.hl-css, - .hl-entity.hl-other.hl-attribute-name.hl-sass, - .hl-entity.hl-other.hl-attribute-name.hl-less { - color: var(--vscode-textPreformat-foreground); - } - - .hl-source.hl-scss, - .hl-source.hl-css, - .hl-source.hl-less { - .hl-entity.hl-other.hl-attribute-name { - color: var(--vscode-textPreformat-foreground); - } - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: var(--vscode-descriptionForeground); - } - - // Operators - .hl-operator { - color: var(--vscode-editor-foreground); - } - - // debt/v2: special case highlighting for specific languages. - // reference `client/branded/**/highlight.scss` -} - -// Theme-specific overrides. -// Includes top themes from VS Code's Marketplace: -// - https://marketplace.visualstudio.com/search?target=VSCode&category=Themes&sortBy=Installs -// and this blog post: -// - https://about.sourcegraph.com/blog/workspaces-of-sourcegraph/ - -// Vetted themes: -// - Dark+ (overrides: ☑) -// - Light+ (overrides: ☑) -// - Dark (Visual Studio) (overrides: ☑) -// - Light (Visual Studio) (overrides: ☑) -// - Monokai (overrides: ☑) -// - Solarized (overrides: partial) -// - Solarized Dark (overrides: partial) -// - High Contrast (overrides: ☑) -// - One Dark Pro (overrides: ☑) -// - Dracula (overrides: ☑) -// - Dracula Soft(overrides: ☑) -// - GitHub (overrides: no) -// - Atom One Dark (overrides: ☑) -// - Winter is Coming (overrides: no) -// - Monokai Pro (overrides: ☑, only for base filter) -// - Night Owl (overrides: ☑, only for dark) -// - One Monokai (overrides: partial) -// - Cobalt2 (overrides: ☑) -// - Material Theme (overrides: no) -// - SynthWave '84 (overrides: ☑) -// - Panda (overrides: ☑) -// - Hack The Box (overrides: ☑) -// - Hack The Box-Lite (overrides: ☑) - -// Debt: this technique isn't resilient to changes in themes. -// Themes aren't likely to change that often, but it would -// still be better to find a way to automate this. - -// FULLY SUPPORTED THEMES - -// Default Dark VS Code theme. -body[data-vscode-theme-name^='Dark+ (default dark)'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #dcdcaa; - } - - // Constants - .hl-variable.hl-constant { - color: #4fc1ff; - } - .hl-variable { - color: #9cdcfe; - } - - // Entities - .hl-entity.hl-name.hl-type { - color: #4ec9b0; - } - - .hl-keyword { - color: var(--vscode-debugTokenExpression-name); - } - - .hl-type.hl-support, - .hl-type.hl-primitive { - color: #4ec9b0; - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #6a9955; - } -} - -// Dark Visual Studio (minimalistic) theme -body[data-vscode-theme-name^='Dark (Visual Studio)'].sourcegraph-extension.theme-dark { - .hl-keyword { - color: var(--vscode-debugView-valueChangedHighlight); - } - - // Storage - .hl-storage { - color: var(--vscode-debugView-valueChangedHighlight); - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #6a9955; - } -} - -// Default Light VS Code theme. -body[data-vscode-theme-name^='Light+ (default light)'].sourcegraph-extension.theme-light { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #795e26; - } - - // Constants - .hl-variable.hl-constant { - color: #0070c1; - } - .hl-variable { - color: #001080; - } - - .hl-keyword { - color: var(--vscode-debugTokenExpression-boolean); - } - - .hl-storage { - color: var(--vscode-debugTokenExpression-boolean); - } - - // Property names - .hl-property .hl-name { - color: #001080; - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #008000; - } - - .hl-import .hl-string { - color: var(--vscode-textPreformat-foreground); - } - - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-entity.hl-name.hl-type { - color: #267f99; - } -} - -// Light Visual Studio (minimalistic) theme -body[data-vscode-theme-name^='Light (Visual Studio)'].sourcegraph-extension.theme-light { - .hl-keyword { - color: var(--vscode-debugTokenExpression-boolean); - } - - .hl-storage { - color: var(--vscode-debugTokenExpression-boolean); - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #008000; - } - - // Operators - .hl-operator { - color: var(--vscode-editor-foreground); - } - - .hl-import .hl-string { - color: var(--vscode-textPreformat-foreground); - } -} - -// High contrast (default VS Code dark HC theme) -body[data-vscode-theme-name='High Contrast'].sourcegraph-extension.theme-dark { - .match-highlight, - .match-highlight-sticky, - .selection-highlight, - .selection-highlight-sticky { - background-color: white; - color: black; - } - - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #dcdcaa; - } - - .hl-keyword { - color: #c586c0; - } - - .hl-storage { - color: #569cd6; - } - - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #569cd6; - } - - .hl-type { - color: #569cd6; - } - - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #7ca668; - } -} - -// Monokai -body[data-vscode-theme-name='Monokai'].sourcegraph-extension.theme-dark { - .hl-keyword { - color: #f92672; - } - .hl-import-export-all, - .hl-number { - color: #ae81ff; - } - .hl-storage { - color: #66d9ef; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #e6db74; - } - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #a6e22e; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #88846f; - } - .hl-entity.hl-name.hl-type { - color: #a6e22e; - text-decoration: underline; - text-underline-position: under; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin { - color: #66d9ef; - font-style: italic; - } - .hl-variable.hl-parameter { - color: #fd971f; - font-style: italic; - } -} - -// One Dark Pro -body[data-vscode-theme-name='One Dark Pro'].sourcegraph-extension.theme-dark { - .hl-keyword, - .hl-storage { - color: #c678dd; - } - .hl-variable.hl-readwrite { - color: #e06c75; - } - .hl-variable.hl-parameter { - color: #e06c75; - font-style: italic; - } - .hl-variable.hl-constant, - .hl-variable.hl-object { - color: #e5c07b; - } - .hl-object-literal.hl-key { - color: #e06c75; - .hl-function { - color: #61afef; - } - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: var(--vscode-editor-wordHighlightBorder); - font-style: italic; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-type.hl-name { - color: #e5c07b; - } - .hl-number { - color: #d19a66; - } - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #61afef; - } -} - -// Dracula -body[data-vscode-theme-name='Dracula'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function { - color: #50fa7b; - } - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: var(--vscode-terminal-ansiCyan); - } - - .hl-number, - .hl-constant.hl-numeric, - .hl-constant.hl-other.hl-placeholder { - color: var(--vscode-terminal-ansiBlue); - } - - .hl-keyword, - .hl-storage { - color: #ff79c6; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #f1fa8c; - } - .hl-variable.hl-parameter, - .hl-variable.hl-object, - .hl-entity.hl-type.hl-module { - color: #ffb86c; - } - .hl-variable.hl-object { - color: var(--vscode-editor-foreground); - } - .hl-variable.hl-constant { - color: #bd93f9; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-type.hl-name { - color: #8be9fd; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #6272a4; - } -} - -// Dracula -body[data-vscode-theme-name='Dracula Soft'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #62e884; - } - .hl-keyword, - .hl-storage { - color: #f286c4; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #e7ee98; - } - .hl-variable.hl-parameter, - .hl-variable.hl-object, - .hl-entity.hl-type.hl-module { - color: #ffb86c; - } - .hl-variable.hl-object { - color: var(--vscode-editor-foreground); - } - .hl-variable.hl-constant { - color: #bf9eee; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-type.hl-name { - color: #97e1f1; - font-style: italic; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #7b7f8b; - } -} - -// Atom One Dark -body[data-vscode-theme-name='Atom One Dark'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #61afef; - } - .hl-keyword, - .hl-storage { - color: #c678dd; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #98c379; - } - .hl-number { - color: #d19a66; - } - .hl-variable.hl-alias { - color: #e06c75; - } - .hl-object-literal.hl-key { - color: #e06c75; - .hl-function { - color: #61afef; - } - } - .hl-entity.hl-type.hl-name { - color: #e5c07b; - } - .hl-entity.hl-type.hl-module { - color: #98c379; - } - .hl-variable.hl-constant { - color: #d19a66; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #5c6370; - } -} - -// Monokai Pro (add filters on requst) -body[data-vscode-theme-name='Monokai Pro'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #a9dc76; - } - .hl-keyword { - color: #ff6188; - } - .hl-storage { - color: #78dce8; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #ffd866; - } - .hl-number { - color: #ab9df2; - } - .hl-type { - color: #78dce8; - // Pimitives/builtins are itliac - &.hl-primitive, - &.hl-builtin { - font-style: italic; - } - } - .hl-variable.hl-constant { - color: #ab9df2; - } - .hl-variable.hl-parameter { - color: #fc9867; - font-style: italic; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #727072; - } -} - -// Cobalt2 -body[data-vscode-theme-name='Cobalt2'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #ffc600; - } - .hl-storage, - .hl-keyword { - color: #ffc600; - } - .hl-storage.hl-function { - color: #fb94ff; - } - .hl-keyword.hl-export { - color: #ff9d00; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #0088ff; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #a5ff90; - } - .hl-number { - color: #ff628c; - } - .hl-entity.hl-type.hl-name { - color: #ff68b8; - font-style: italic; - } - .hl-entity.hl-type.hl-module, - .hl-type.hl-primitive, - .hl-type.hl-builtin { - color: #80ffbb; - } -} - -// Panda -body[data-vscode-theme-name='Panda Syntax'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #6fc1ff; - } - .hl-keyword { - color: #ff75b5; - } - .hl-storage { - color: #ffb86c; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #676b79; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-new .hl-entity.hl-type { - color: #ffcc95; - } -} - -// Night Owl TODO use starting match -body[data-vscode-theme-name^='Night Owl'].sourcegraph-extension.theme-dark { - .hl-keyword { - color: #c792ea; - font-style: italic; - } - .hl-storage { - color: #c792ea; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #ecc48d; - } - .hl-variable { - font-style: italic; - &.hl-constant { - color: #82aaff; - } - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-entity.hl-type { - color: #ffcb8b; - font-style: italic; - } - .hl-entity.hl-type.hl-module { - color: #c792ea; - font-style: italic; - } - .hl-number { - color: #f78c6c; - } - .hl-boolean { - font-style: italic; - color: #ff5874; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #637777; - } -} - -// Night Owl (No Italics) -body[data-vscode-theme-name='Night Owl (No Italics)'].sourcegraph-extension.theme-dark { - .hl-keyword { - font-style: normal; - } - .hl-variable { - font-style: normal; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin, - .hl-entity.hl-type { - font-style: normal; - } - .hl-entity.hl-type.hl-module { - font-style: normal; - } - .hl-boolean { - font-style: normal; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: normal; - } -} - -// Hack The Box(-Lite) -body[data-vscode-theme-name^='Hack The Box'].sourcegraph-extension.theme-dark { - .hl-keyword { - color: #cf8dfb; - } - .hl-storage { - color: #ff8484; - } - .hl-entity.hl-name.hl-type { - color: #ff8484; - } - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #ffcc5c; - } - .hl-variable.hl-parameter { - color: #5cb2ff; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: rgba(215, 228, 255, 0.28); - } -} - -// SynthWave '84 -body[data-vscode-theme-name="SynthWave '84"].sourcegraph-extension.theme-dark { - .hl-keyword, - .hl-storage, - .hl-type.hl-storage { - color: #fede5d; - } - .hl-number { - color: #f97e72; - } - .hl-variable { - color: #ff7edb; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #ff8b39; - } - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #36f9f6; - } - .hl-parameter { - font-style: italic; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #848bbd; - } - .hl-type { - color: #fe4450; - &.hl-punctuation, - &.hl-meta { - color: #bbbbbb; - } - } - .hl-arrow { - color: #fede5d; - } -} - -// PARTIALLY SUPPORTED THEMES - -// One Monokai -body[data-vscode-theme-name='One Monokai'].sourcegraph-extension.theme-dark { - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-support.hl-function, - .hl-variable.hl-function { - color: #98c379; - } - .hl-keyword { - color: #e06c75; - } - .hl-storage { - color: #56b6c2; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #676f7d; - } - .hl-type.hl-name { - color: #61afef; - } -} - -// Solarized Light -body[data-vscode-theme-name='Solarized Light'].sourcegraph-extension.theme-light { - .hl-keyword { - color: #859900; - } - .hl-storage { - color: #073642; - font-weight: bold; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #93a1a1; - } - .hl-source, - .hl-text, - .hl-variable, - .hl-function { - color: #268bd2; - } - .hl-attribute-name { - color: #93a1a1; - } -} - -// Solarized Dark -body[data-vscode-theme-name='Solarized Dark'].sourcegraph-extension.theme-dark { - .hl-keyword { - color: #859900; - } - .hl-storage { - color: #93a1a1; - font-weight: bold; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - font-style: italic; - color: #657b83; - } - .hl-source, - .hl-text, - .hl-variable, - .hl-function { - color: #268bd2; - } - .hl-attribute-name { - color: #93a1a1; - } -} - -// Material Theme Palenight High Contrast -body[data-vscode-theme-name='Material Theme Palenight High Contrast'].sourcegraph-extension.theme-dark { - // Keyword - .hl-keyword { - color: #89ddff; - } - // Storage - .hl-storage { - color: #a6accd; - } - // Constants - .hl-constant { - color: #c3e88d; - } - .hl-import-export-all, - .hl-number, - .hl-constant.hl-numeric, - .hl-integer, - .hl-decimal { - color: #f78c6c; - } - // Strings - .hl-string, - .hl-constant.hl-other.hl-symbol { - color: #c3e88d; - } - // Functions - .hl-entity.hl-name.hl-function, - .hl-meta.hl-require, - .hl-support.hl-function.hl-any-method, - .hl-variable.hl-function { - color: #82aaff; - } - // Comments - .hl-comment, - .hl-punctuation.hl-definition.hl-comment { - color: #676e95; - font-style: italic; - } - .hl-type.hl-primitive, - .hl-type.hl-builtin { - color: #c792ea; - } - .hl-variable.hl-parameter { - color: #a6accd; - } - .hl-punctuation { - color: #89ddff; - } - .match-highlight, - .match-highlight-sticky, - .selection-highlight, - .selection-highlight-sticky { - background-color: var(--vscode-editor-findMatchHighlightBackground); - border-bottom: 1px solid var(--vscode-editor-findMatchBorder); - } -} diff --git a/client/vscode/src/webview/theming/sourcegraphTheme.ts b/client/vscode/src/webview/theming/sourcegraphTheme.ts deleted file mode 100644 index fff8a476561..00000000000 --- a/client/vscode/src/webview/theming/sourcegraphTheme.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BehaviorSubject, type Observable } from 'rxjs' - -/** - * Adds correct theme class to element, - * returns Observable with latest theme. - */ -export function adaptSourcegraphThemeToEditorTheme(): Observable<'theme-dark' | 'theme-light' | undefined> { - const body = document.querySelector('body') - - const themes = new BehaviorSubject<'theme-dark' | 'theme-light' | undefined>(undefined) - - function applyVSCodeThemeToSourcegraph(): void { - const vscodeTheme = body?.dataset.vscodeThemeKind - const sourcegraphThemeClass = vscodeTheme === 'vscode-light' ? 'theme-light' : 'theme-dark' - if (sourcegraphThemeClass !== themes.value) { - if (sourcegraphThemeClass === 'theme-light') { - body?.classList.remove('theme-dark') - body?.classList.add('theme-light', 'sourcegraph-extension') - themes.next('theme-light') - } else { - body?.classList.remove('theme-light') - body?.classList.add('theme-dark', 'sourcegraph-extension') - themes.next('theme-dark') - } - } - } - - applyVSCodeThemeToSourcegraph() - - const mutationObserver = new MutationObserver(() => { - applyVSCodeThemeToSourcegraph() - }) - - mutationObserver.observe(body!, { childList: false, attributes: true }) - - return themes.asObservable() -} diff --git a/client/vscode/tests/BUILD.bazel b/client/vscode/tests/BUILD.bazel deleted file mode 100644 index 284b2c28140..00000000000 --- a/client/vscode/tests/BUILD.bazel +++ /dev/null @@ -1,6 +0,0 @@ -# TODO(bazel): disabled for now -# gazelle:js disabled - -# The library itself is a testing library so put all files into the main library -# gazelle:js_files **/*.{ts,tsx} -# gazelle:js_test_files does-not-exist-to-disable diff --git a/client/vscode/tests/context.ts b/client/vscode/tests/context.ts deleted file mode 100644 index d105e68b9f3..00000000000 --- a/client/vscode/tests/context.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type puppeteer from 'puppeteer' - -import type { SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations' -import type { SearchEvent } from '@sourcegraph/shared/src/search/stream' -import { - createSharedIntegrationTestContext, - type IntegrationTestContext, - type IntegrationTestOptions, -} from '@sourcegraph/shared/src/testing/integration/context' - -import type { VSCodeGraphQlOperations } from '../src/graphql-operations' - -import { commonVSCodeGraphQlResults } from './graphql' - -export interface VSCodeIntegrationTestContext - extends IntegrationTestContext< - VSCodeGraphQlOperations & SharedGraphQlOperations, - string & keyof (VSCodeGraphQlOperations & SharedGraphQlOperations) - > { - /** - * Configures fake responses for streaming search - * - * @param overrides The array of events to return. - */ - overrideSearchStreamEvents: (overrides: SearchEvent[]) => void -} - -export async function createVSCodeIntegrationTestContext( - { currentTest, directory }: Omit, - vsCodeFrontendPage: puppeteer.Page, - sourcegraphBaseUrl = 'https://sourcegraph.com' -): Promise { - const sharedTestContext = await createSharedIntegrationTestContext({ - driver: { - newPage: () => Promise.resolve(), - sourcegraphBaseUrl, - browser: vsCodeFrontendPage.browser(), - page: vsCodeFrontendPage, - }, - currentTest, - directory, - }) - - sharedTestContext.overrideGraphQL(commonVSCodeGraphQlResults) - - let searchStreamEventOverrides: SearchEvent[] = [] - - const streamApiPath = '/.api/search/stream?*params' - sharedTestContext.server.options(new URL(streamApiPath, sourcegraphBaseUrl).href).intercept((request, response) => { - response - .setHeader('Access-Control-Allow-Origin', '*') - .setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') - .send(200) - }) - - sharedTestContext.server.get(new URL(streamApiPath, sourcegraphBaseUrl).href).intercept((request, response) => { - if (!searchStreamEventOverrides || searchStreamEventOverrides.length === 0) { - throw new Error( - 'Search stream event overrides missing. Call overrideSearchStreamEvents() to set the events.' - ) - } - - const responseContent = searchStreamEventOverrides - .map(event => `event: ${event.type}\ndata: ${JSON.stringify(event.data)}\n\n`) - .join('') - response - .status(200) - .setHeader('Access-Control-Allow-Origin', '*') - .type('text/event-stream') - .send(responseContent) - }) - - return { - ...sharedTestContext, - overrideSearchStreamEvents: overrides => { - searchStreamEventOverrides = overrides - }, - // Try closing existing search panels when we have multiple test cases. - dispose: () => sharedTestContext.dispose(), - } -} diff --git a/client/vscode/tests/getWebview.ts b/client/vscode/tests/getWebview.ts deleted file mode 100644 index dd39a02ad87..00000000000 --- a/client/vscode/tests/getWebview.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type puppeteer from 'puppeteer' - -export interface VSCodeWebviewFrames { - sidebarFrame: puppeteer.Frame - searchPanelFrame: puppeteer.Frame -} - -/** - * The VSCode extension currently opens two web views: - * one is the sidebar content and the other is the search page. - * - * @param page The VS Code frontend page. - * @returns Search panel and sidebar webview frames. - */ -export async function getVSCodeWebviewFrames(page: puppeteer.Page): Promise { - let sidebarFrame: puppeteer.Frame | undefined - let searchPanelFrame: puppeteer.Frame | undefined - - // Find the Sourcegraph icon in the activity bar - // to open the Sourcegraph sidebar and active the extension, - // which should open a search panel in the process. - await page.waitForSelector('[aria-label="Sourcegraph"]') - await page.click('[aria-label="Sourcegraph"]') - - // In the release of VS Code at the time this test harness was written, there was no - // stable unique selector for the search panel webview's outermost iframe ancestor. - // Reverse to look for panel webview first. - const outerFrameHandles = (await page?.$$('div[id^="webview"] iframe')).reverse() - - for (const outerFrameHandle of outerFrameHandles) { - if (sidebarFrame && searchPanelFrame) { - break - } - - const webview = await findSearchWebview(outerFrameHandle) - if (webview?.type === 'panel') { - searchPanelFrame = webview.frame - } else if (webview?.type === 'sidebar') { - sidebarFrame = webview.frame - } - } - - if (!sidebarFrame || !searchPanelFrame) { - const missingWebviewNames: string[] = [] - if (!sidebarFrame) { - missingWebviewNames.push('sidebar webview') - } - if (!searchPanelFrame) { - missingWebviewNames.push('panel webview') - } - throw new Error(`Could not find ${missingWebviewNames.join(',')}`) - } - - return { - sidebarFrame, - searchPanelFrame, - } -} - -async function findSearchWebview( - outerFrameHandle: puppeteer.ElementHandle -): Promise<{ frame: puppeteer.Frame; type: 'panel' | 'sidebar' } | undefined> { - try { - const outerFrame = await outerFrameHandle.contentFrame() - if (!outerFrame) { - return - } - - // The search web views have another iframe inside it. ¯\_(ツ)_/¯ - const frameHandle = await outerFrame.waitForSelector('iframe') - if (frameHandle === null) { - return - } - - const frame = await frameHandle.contentFrame() - if (frame === null) { - return - } - - const body = await frame.waitForSelector('body') - if (body) { - const className = await (await body.getProperty('className')).jsonValue() - if (typeof className === 'string') { - if (className.includes('search-sidebar')) { - return { frame, type: 'sidebar' } - } - if (className.includes('search-panel')) { - return { frame, type: 'panel' } - } - } - } - } catch { - // Likely a selector timeout. Noop, we will throw if we never find the webview. - } - return -} diff --git a/client/vscode/tests/graphql.ts b/client/vscode/tests/graphql.ts deleted file mode 100644 index fbfbdf0fbf8..00000000000 --- a/client/vscode/tests/graphql.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { SharedGraphQlOperations } from '@sourcegraph/shared/src/graphql-operations' -import { sharedGraphQlResults } from '@sourcegraph/shared/src/testing/integration/graphQlResults' - -import type { VSCodeGraphQlOperations } from '../src/graphql-operations' - -/** - * Predefined results for GraphQL requests that are made on almost every user flow. - */ -export const commonVSCodeGraphQlResults: Partial = { - ...sharedGraphQlResults, - LogEvents: () => ({ - __typename: 'Mutation', - logEvents: null, - }), - CurrentAuthState: () => ({ - __typename: 'Query', - currentUser: null, - }), - ListSearchContexts: () => ({ - searchContexts: { - nodes: [], - totalCount: 0, - pageInfo: { hasNextPage: false, endCursor: null }, - }, - }), - IsSearchContextAvailable: () => ({ - isSearchContextAvailable: true, - }), - SiteProductVersion: () => ({ - __typename: 'Query', - site: { - __typename: 'Site', - productVersion: '3.38.2', - }, - }), - RepositoryMetadata: () => ({ - __typename: 'Query', - repositoryRedirect: null, - }), - TreeEntries: () => ({ - __typename: 'Query', - repository: null, - }), - FileNames: () => ({ - __typename: 'Query', - repository: null, - }), - BlobContent: () => ({ - __typename: 'Query', - repository: null, - }), -} diff --git a/client/vscode/tests/installExtension.ts b/client/vscode/tests/installExtension.ts deleted file mode 100644 index df3a8a609ed..00000000000 --- a/client/vscode/tests/installExtension.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { mkdirSync, type WriteStream, createWriteStream } from 'fs' -import path from 'path' -import type { Readable } from 'stream' - -import { type Entry, open as _openZip, type ZipFile } from 'yauzl' - -export function installExtension(extensionPath: string, extensionDirectory: string): Promise { - return openZip(extensionPath, true).then(zipfile => extractZip(zipfile, extensionDirectory)) -} - -function openZip(zipFile: string, lazy: boolean = false): Promise { - return new Promise((resolve, reject) => { - _openZip(zipFile, lazy ? { lazyEntries: true } : {}, (error?: Error | null, zipfile?: ZipFile) => { - if (error || zipfile === undefined) { - reject(error) - } else { - resolve(zipfile) - } - }) - }) -} - -function openZipStream(zipFile: ZipFile, entry: Entry): Promise { - return new Promise((resolve, reject) => { - zipFile.openReadStream(entry, (error?: Error | null, stream?: Readable) => { - if (error || stream === undefined) { - reject(error) - } else { - resolve(stream) - } - }) - }) -} - -function extractZip(zipfile: ZipFile, targetPath: string): Promise { - let extractedEntriesCount = 0 - - return new Promise((resolve, reject) => { - const readNextEntry = (): void => { - extractedEntriesCount++ - zipfile.readEntry() - } - - zipfile.once('error', reject) - zipfile.once('close', () => { - if (zipfile.entryCount === extractedEntriesCount) { - resolve() - } else { - reject(new Error('Incomplete extraction.')) - } - }) - zipfile.readEntry() - - zipfile.on('entry', async (entry: Entry) => { - const fileName = entry.fileName // .replace(options.sourcePathRegex, '') - - // directory file names end with '/' - if (fileName.endsWith('/')) { - const targetFileName = path.join(targetPath, fileName) - mkdirSync(targetFileName, { recursive: true }) - readNextEntry() - return - } - - const stream = openZipStream(zipfile, entry) - const mode = modeFromEntry(entry) - - await extractEntry(await stream, fileName, mode, targetPath) - readNextEntry() - }) - }) -} - -function extractEntry(stream: Readable, fileName: string, mode: number, targetPath: string): Promise { - const directoryName = path.dirname(fileName) - const targetDirectoryName = path.join(targetPath, directoryName) - if (!targetDirectoryName.startsWith(targetPath)) { - return Promise.reject(new Error('invalid file')) - } - - const targetFileName = path.join(targetPath, fileName) - - let istream: WriteStream - - mkdirSync(targetDirectoryName, { recursive: true }) - - return new Promise((resolve, reject) => { - try { - istream = createWriteStream(targetFileName, { mode }) - istream.once('close', () => resolve()) - istream.once('error', reject) - stream.once('error', reject) - stream.pipe(istream) - } catch { - reject(new Error('Unknown error')) - } - }) -} - -function modeFromEntry(entry: Entry): number { - const attribute = entry.externalFileAttributes >> 16 || 33188 - - return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */] - .map(mask => attribute & mask) - .reduce((a, b) => a + b, attribute & 61440 /* S_IFMT */) -} diff --git a/client/vscode/tests/launch.ts b/client/vscode/tests/launch.ts deleted file mode 100644 index 381bdb2e235..00000000000 --- a/client/vscode/tests/launch.ts +++ /dev/null @@ -1,106 +0,0 @@ -import childProcess from 'child_process' -import { mkdtempSync } from 'fs' -import { tmpdir } from 'os' -import path from 'path' - -import puppeteer, { type Page } from 'puppeteer' -import rimraf from 'rimraf' - -const PORT = 29378 -const verbose = process.argv.includes('-v') || process.argv.includes('--verbose') - -export interface VSCodeTestDriver { - page: Page - dispose: (onDispose?: () => void) => void -} - -export async function launchVsCode(vscodeExecutablePath: string): Promise { - const extensionDevelopmentPath = path.join(__dirname, '..') - - const userDataDirectory = mkdtempSync(path.join(tmpdir(), 'vsce')) - const extensionsDirectory = mkdtempSync(path.join(tmpdir(), 'vsce')) - - console.log('Starting VS Code', { verbose, vscodeExecutablePath, userDataDirectory, extensionsDirectory }) - - const vsCodeProcess = childProcess.spawn( - vscodeExecutablePath, - [ - `--extensionDevelopmentPath=${extensionDevelopmentPath}`, - // Load extension in web worker extension host - // so we can intercept requests with Polly.js through - // the Chrome DevTools protocol. - '--extensionDevelopmentKind=web', - `--remote-debugging-port=${PORT || 9229}`, - `--user-data-dir=${userDataDirectory}`, - `--extensions-dir=${extensionsDirectory}`, - '--enable-logging', - '--skip-release-notes', - // https://github.com/microsoft/vscode-test/issues/120 - '--disable-updates', - // https://github.com/microsoft/vscode/issues/84238 - '--no-sandbox', - '--disable-gpu', - ], - { - env: process.env, - stdio: verbose ? 'inherit' : ['pipe', 'pipe', 'pipe'], - } - ) - - const dispose: VSCodeTestDriver['dispose'] = onDispose => { - setTimeout(() => { - vsCodeProcess.kill() - - // eslint-disable-next-line no-void - void delay(1000).then(() => { - rimraf.sync(userDataDirectory) - rimraf.sync(extensionsDirectory) - - onDispose?.() - }) - }, 1000) - } - - for (const event of ['SIGINT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM'] as const) { - process.on(event, () => dispose?.()) - } - process.on('exit', () => dispose?.()) - process.on('uncaughtException', () => dispose?.()) - - // Initialize Puppeteer - - const browserURL = `http://127.0.0.1:${PORT}` - await delay(2000) - console.log(`VSCode started in debug mode on ${browserURL}`) - - const browser = await puppeteer.connect({ - browserURL, - defaultViewport: null, // used to bypass Chrome viewport issue, doesn't work w/ VS code. - slowMo: 50, - }) - - await delay(1000) - - // We look for the VSCode frontend process - const pages = await browser.pages() - - let page: null | Page = null - for (const _page of pages) { - const title = await _page.title() - if (title.includes('Get Started')) { - page = _page - } - } - if (page === null) { - throw new Error('Could not find VS Code frontend page') - } - - return { - page, - dispose, - } -} - -function delay(timeout: number): Promise { - return new Promise(resolve => setTimeout(resolve, timeout)) -} diff --git a/client/vscode/tests/tsconfig.json b/client/vscode/tests/tsconfig.json deleted file mode 100644 index 023492b62c1..00000000000 --- a/client/vscode/tests/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "references": [{ "path": "../" }, { "path": "../../shared/src/testing" }], - "compilerOptions": { - "types": ["node"], - "module": "commonjs", - "rootDir": ".", - "outDir": "../out/src/tests", - }, - "include": ["**/*"], - "exclude": [], -} diff --git a/client/vscode/tests/vsce.test.ts b/client/vscode/tests/vsce.test.ts deleted file mode 100644 index fc157e0a503..00000000000 --- a/client/vscode/tests/vsce.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { downloadAndUnzipVSCode } from '@vscode/test-electron' -import { beforeEach, describe, it } from 'mocha' - -import { - mixedSearchStreamEvents, - highlightFileResult, -} from '@sourcegraph/shared/src/search/integration/streaming-search-mocks' -import type { Settings } from '@sourcegraph/shared/src/settings/settings' - -import { createVSCodeIntegrationTestContext, type VSCodeIntegrationTestContext } from './context' -import { getVSCodeWebviewFrames } from './getWebview' -import { launchVsCode, type VSCodeTestDriver } from './launch' - -describe('VS Code extension', () => { - let vsCodeDriver: VSCodeTestDriver - before(async () => { - const vscodeExecutablePath = await downloadAndUnzipVSCode() - vsCodeDriver = await launchVsCode(vscodeExecutablePath) - }) - after(() => vsCodeDriver?.dispose()) - - let testContext: VSCodeIntegrationTestContext - - beforeEach(async function () { - testContext = await createVSCodeIntegrationTestContext( - { - currentTest: this.currentTest!, - directory: __dirname, - }, - vsCodeDriver.page - ) - }) - - // TODO: Reset VS Code extension state between test cases in `afterEach` - // once we have multiple tests. This will likely involve just closing the - // search panel. - // - // This was initially attempted before (#40470) but was failing in the CI - // while the actual functionality kept working (tested locally) and needs - // fixing before we add more test cases to the suite. - - it('works', async () => { - const userSettings: Settings = { - extensions: {}, - } - - testContext.overrideGraphQL({ - ...highlightFileResult, - ViewerSettings: () => ({ - viewerSettings: { - __typename: 'SettingsCascade', - final: JSON.stringify(userSettings), - subjects: [ - { - __typename: 'User', - displayName: 'Test User', - id: 'TestUserSettingsID', - latestSettings: { - id: 123, - contents: JSON.stringify(userSettings), - }, - username: 'test', - viewerCanAdminister: true, - settingsURL: '/users/test/settings', - }, - ], - }, - }), - BlobContent: () => ({ - repository: { - commit: { - blob: { - content: 'testing\rvsce\n', - binary: false, - byteSize: 2, - }, - }, - }, - }), - FileNames: () => ({ - repository: { - commit: { - fileNames: ['overridable/bool_or_string_test.go'], - }, - }, - }), - }) - - testContext.overrideSearchStreamEvents([...mixedSearchStreamEvents]) - - const { searchPanelFrame, sidebarFrame } = await getVSCodeWebviewFrames(vsCodeDriver.page) - - // Focus search box - await searchPanelFrame.waitForSelector('..cm-editor') - await searchPanelFrame.click('.cm-editor') - - await vsCodeDriver.page.keyboard.type('test', { - delay: 50, - }) - // Submit search - await searchPanelFrame.waitForSelector('.test-search-button', { visible: true }) - await searchPanelFrame.click('.test-search-button', { delay: 50 }) - - try { - await searchPanelFrame.waitForSelector('.test-search-result', { visible: true }) - } catch { - throw new Error('Timeout waiting for search results to render') - } - - // Submit new search from sidebar filter - try { - await sidebarFrame.waitForSelector('.search-sidebar .search-filter-keyword', { visible: true }) - await sidebarFrame.click('.search-sidebar .search-filter-keyword', { delay: 50 }) - await searchPanelFrame.waitForSelector('.test-search-result', { visible: true }) - } catch { - throw new Error('Timeout waiting for filtered search results to render') - } - - // Open Repo page from search results - await searchPanelFrame.waitForSelector('.test-search-result button', { visible: true }) - await searchPanelFrame.click('.test-search-result button', { delay: 50 }) - - // Redirect back to search results from Repo Page - try { - await searchPanelFrame.waitForSelector('.test-back-to-search-view-btn', { visible: true }) - await searchPanelFrame.click('.test-back-to-search-view-btn', { delay: 50 }) - } catch { - throw new Error('Timeout waiting for search results to render after viewing repo page') - } - - // Open remote file from search results - try { - await searchPanelFrame.waitForSelector('.test-search-result strong', { visible: true }) - await searchPanelFrame.click('.test-search-result strong', { delay: 100 }) - } catch { - throw new Error('Timeout waiting for search results to render after nevigating back from repo display page') - } - - await vsCodeDriver.page.waitForTimeout(10000) - - // Look for file title - const remoteFileTitle = await vsCodeDriver.page.title() - if (!remoteFileTitle.includes('bool_or_string_test.go')) { - throw new Error('Timeout waiting for remote file to render') - } - }) - - // Potential future test cases: - // - Sourcegraph extensions work on remote files -}) diff --git a/client/vscode/tsconfig.json b/client/vscode/tsconfig.json deleted file mode 100644 index fd8bf739cc9..00000000000 --- a/client/vscode/tsconfig.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "module": "commonjs", - "target": "es2020", - "lib": ["esnext", "DOM", "DOM.Iterable"], - "sourceMap": true, - "sourceRoot": "src", - "baseUrl": "./src", - "paths": { - "*": ["types/*", "../../shared/src/types/*", "../../common/src/types/*", "*"], - }, - "esModuleInterop": true, - "resolveJsonModule": true, - "strict": true, - "jsx": "react-jsx", - }, - "references": [ - { - "path": "../branded", - }, - { - "path": "../build-config", - }, - { - "path": "../client-api", - }, - { - "path": "../codeintellify", - }, - { - "path": "../common", - }, - { - "path": "../extension-api-types", - }, - { - "path": "../http-client", - }, - { - "path": "../shared", - }, - { - "path": "../wildcard", - }, - ], - "include": ["./package.json", "**/*", ".*", "**/*.d.ts", "./code-intel-extensions.json"], - "exclude": ["node_modules", "../../node_modules", ".vscode-test", "out", "dist"], -} diff --git a/dev/ci/internal/ci/operations.go b/dev/ci/internal/ci/operations.go index f28ebf5d176..8cf85dc048f 100644 --- a/dev/ci/internal/ci/operations.go +++ b/dev/ci/internal/ci/operations.go @@ -57,7 +57,6 @@ func CoreTestOperations(buildOpts bk.BuildOptions, diff changed.Diff, opts CoreT clientChecks := operations.NewNamedSet("Client checks", clientChromaticTests(opts), addJetBrainsUnitTests, // ~2.5m - addVsceTests, // ~3.0m addStylelint, ) ops.Merge(clientChecks) @@ -117,20 +116,6 @@ func getParallelTestCount(webParallelTestCount int) int { return webParallelTestCount + len(browsers) } -// Builds and tests the VS Code extensions. -func addVsceTests(pipeline *bk.Pipeline) { - pipeline.AddStep( - ":vscode: Tests for VS Code extension", - withPnpmCache(), - bk.Cmd("pnpm install --frozen-lockfile --fetch-timeout 60000"), - bk.Cmd("pnpm generate"), - bk.Cmd("pnpm --filter @sourcegraph/vscode run build:test"), - // TODO: fix integrations tests and re-enable: https://github.com/sourcegraph/sourcegraph/issues/40891 - // bk.Cmd("pnpm --filter @sourcegraph/vscode run test-integration --verbose"), - // bk.AutomaticRetry(1), - ) -} - func addBrowserExtensionIntegrationTests(parallelTestCount int) operations.Operation { testCount := getParallelTestCount(parallelTestCount) return func(pipeline *bk.Pipeline) { @@ -252,16 +237,6 @@ func addBrowserExtensionReleaseSteps(pipeline *bk.Pipeline) { bk.Cmd("pnpm --filter @sourcegraph/browser release:npm")) } -// Release the VS Code extension. -func addVsceReleaseSteps(pipeline *bk.Pipeline) { - // Publish extension to the VS Code Marketplace - pipeline.AddStep(":vscode: Extension release", - withPnpmCache(), - bk.Cmd("pnpm install --frozen-lockfile --fetch-timeout 60000"), - bk.Cmd("pnpm generate"), - bk.Cmd("pnpm --filter @sourcegraph/vscode run release")) -} - // Adds a Buildkite pipeline "Wait". func wait(pipeline *bk.Pipeline) { pipeline.AddWait() diff --git a/dev/ci/internal/ci/pipeline.go b/dev/ci/internal/ci/pipeline.go index fb2a48eaf05..1f2327b5ffb 100644 --- a/dev/ci/internal/ci/pipeline.go +++ b/dev/ci/internal/ci/pipeline.go @@ -168,13 +168,6 @@ func GeneratePipeline(c Config) (*bk.Pipeline, error) { wait, addBrowserExtensionReleaseSteps) - case runtype.VsceReleaseBranch: - // If this is a vs code extension release branch, run the vscode-extension tests and release - ops = BazelOpsSet(buildOptions, - addVsceTests, - wait, - addVsceReleaseSteps) - case runtype.BextNightly, runtype.BextManualNightly: // If this is a browser extension nightly build, run the browser-extension tests and // e2e tests. @@ -183,9 +176,6 @@ func GeneratePipeline(c Config) (*bk.Pipeline, error) { wait, addBrowserExtensionE2ESteps) - case runtype.VsceNightly: - ops = BazelOpsSet(buildOptions, addVsceTests) - case runtype.WolfiBaseRebuild: // If this is a Wolfi base image rebuild, rebuild all Wolfi base images // and push to registry, then open a PR diff --git a/dev/ci/runtype/runtype.go b/dev/ci/runtype/runtype.go index f162b0a12dc..afd106922c3 100644 --- a/dev/ci/runtype/runtype.go +++ b/dev/ci/runtype/runtype.go @@ -21,7 +21,6 @@ const ( ReleaseNightly // release branch nightly healthcheck builds BextNightly // browser extension nightly build BextManualNightly // browser extension nightly build, triggered with a branch pattern - VsceNightly // vs code extension nightly build AppRelease // app release build AppInsiders // app insiders build WolfiBaseRebuild // wolfi base image build @@ -31,7 +30,6 @@ const ( TaggedRelease // semver-tagged release ReleaseBranch // release branch build BextReleaseBranch // browser extension release build - VsceReleaseBranch // vs code extension release build // Main branches @@ -103,17 +101,6 @@ func (t RunType) Matcher() *RunTypeMatcher { return &RunTypeMatcher{ Branch: "bext-nightly/", } - case VsceNightly: - return &RunTypeMatcher{ - EnvIncludes: map[string]string{ - "VSCE_NIGHTLY": "true", - }, - } - case VsceReleaseBranch: - return &RunTypeMatcher{ - Branch: "vsce/release", - BranchExact: true, - } case WolfiBaseRebuild: return &RunTypeMatcher{ EnvIncludes: map[string]string{ @@ -204,8 +191,6 @@ func (t RunType) String() string { return "Browser extension nightly release build" case BextManualNightly: return "Manually triggered browser extension nightly release build" - case VsceNightly: - return "VS Code extension nightly release build" case WolfiBaseRebuild: return "Wolfi base images rebuild" case AppRelease: @@ -218,8 +203,6 @@ func (t RunType) String() string { return "Release branch" case BextReleaseBranch: return "Browser extension release build" - case VsceReleaseBranch: - return "VS Code extension release build" case MainBranch: return "Main branch" case MainDryRun: diff --git a/dev/ci/runtype/runtype_test.go b/dev/ci/runtype/runtype_test.go index beb98edbf49..6c35e2f8b2f 100644 --- a/dev/ci/runtype/runtype_test.go +++ b/dev/ci/runtype/runtype_test.go @@ -51,15 +51,6 @@ func TestComputeRunType(t *testing.T) { }, }, want: BextNightly, - }, { - name: "vsce nightly", - args: args{ - branch: "main", - env: map[string]string{ - "VSCE_NIGHTLY": "true", - }, - }, - want: VsceNightly, }, { name: "wolfi base image rebuild", args: args{ @@ -69,12 +60,6 @@ func TestComputeRunType(t *testing.T) { }, }, want: WolfiBaseRebuild, - }, { - name: "vsce release", - args: args{ - branch: "vsce/release", - }, - want: VsceReleaseBranch, }, { name: "app release", args: args{ diff --git a/package.json b/package.json index dcf731bb4fb..bf2e2f2dab9 100644 --- a/package.json +++ b/package.json @@ -184,12 +184,8 @@ "@types/stream-json": "^1.7.3", "@types/svgo": "2.6.0", "@types/uuid": "8.0.1", - "@types/vscode": "^1.76.0", - "@types/vscode-webview": "^1.57.1", "@types/yauzl": "^2.9.2", "@vitejs/plugin-react": "^3.1.0", - "@vscode/test-electron": "^2.3.2", - "@vscode/vsce": "^2.19.0", "abort-controller": "^3.0.0", "autoprefixer": "^10.2.1", "axe-core": "^4.4.1", @@ -334,7 +330,6 @@ "@visx/shape": "^2.11.1", "@visx/text": "2.10.0", "@visx/voronoi": "^2.10.0", - "@vscode/webview-ui-toolkit": "^1.2.2", "agent-base": "6.0.2", "ajv": "^8.6.3", "ajv-formats": "^2.1.1", @@ -427,7 +422,6 @@ "util": "^0.12.5", "utility-types": "^3.10.0", "uuid": "^8.3.0", - "vscode-uri": "^3.0.7", "webext-domain-permission-toggle": "^1.0.1", "webextension-polyfill": "^0.6.0", "yaml-ast-parser": "^0.0.43", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf546f4f821..86bad361ede 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,9 +169,6 @@ importers: '@visx/voronoi': specifier: ^2.10.0 version: 2.10.0(react@18.1.0) - '@vscode/webview-ui-toolkit': - specifier: ^1.2.2 - version: 1.2.2(react@18.1.0) agent-base: specifier: 6.0.2 version: 6.0.2 @@ -448,9 +445,6 @@ importers: uuid: specifier: ^8.3.0 version: 8.3.2 - vscode-uri: - specifier: ^3.0.7 - version: 3.0.7 webext-domain-permission-toggle: specifier: ^1.0.1 version: 1.0.1 @@ -824,24 +818,12 @@ importers: '@types/uuid': specifier: 8.0.1 version: 8.0.1 - '@types/vscode': - specifier: ^1.76.0 - version: 1.76.0 - '@types/vscode-webview': - specifier: ^1.57.1 - version: 1.57.1 '@types/yauzl': specifier: ^2.9.2 version: 2.10.0 '@vitejs/plugin-react': specifier: ^3.1.0 version: 3.1.0(vite@4.4.7) - '@vscode/test-electron': - specifier: ^2.3.2 - version: 2.3.2 - '@vscode/vsce': - specifier: ^2.19.0 - version: 2.19.0 abort-controller: specifier: ^3.0.0 version: 3.0.0 @@ -1393,40 +1375,6 @@ importers: client/testing: {} - client/vscode: - dependencies: - '@sourcegraph/branded': - specifier: workspace:* - version: link:../branded - '@sourcegraph/client-api': - specifier: workspace:* - version: link:../client-api - '@sourcegraph/codeintellify': - specifier: workspace:* - version: link:../codeintellify - '@sourcegraph/common': - specifier: workspace:* - version: link:../common - '@sourcegraph/http-client': - specifier: workspace:* - version: link:../http-client - '@sourcegraph/shared': - specifier: workspace:* - version: link:../shared - '@sourcegraph/wildcard': - specifier: workspace:* - version: link:../wildcard - devDependencies: - '@sourcegraph/build-config': - specifier: workspace:* - version: link:../build-config - '@sourcegraph/extension-api-types': - specifier: workspace:* - version: link:../extension-api-types - vsce: - specifier: ^2.7.0 - version: 2.7.0 - client/web: dependencies: '@sourcegraph/branded': @@ -5197,35 +5145,6 @@ packages: svelte: 4.1.1 dev: false - /@microsoft/fast-element@1.12.0: - resolution: {integrity: sha512-gQutuDHPKNxUEcQ4pypZT4Wmrbapus+P9s3bR/SEOLsMbNqNoXigGImITygI5zhb+aA5rzflM6O8YWkmRbGkPA==} - dev: false - - /@microsoft/fast-foundation@2.49.0: - resolution: {integrity: sha512-Wk4e4QXFVtT5hPwhMfHyGY30kixM0td8aDs7bAD6NM2z2SCBNvpTTWp+FCjx0I0lpUMlMenb6wsw7pMWQreRkQ==} - dependencies: - '@microsoft/fast-element': 1.12.0 - '@microsoft/fast-web-utilities': 5.4.1 - tabbable: 5.3.3 - tslib: 2.1.0 - dev: false - - /@microsoft/fast-react-wrapper@0.1.48(react@18.1.0): - resolution: {integrity: sha512-9NvEjru9Kn5ZKjomAMX6v+eF0DR+eDkxKDwDfi+Wb73kTbrNzcnmlwd4diN15ygH97kldgj2+lpvI4CKLQQWLg==} - peerDependencies: - react: '>=16.9.0' - dependencies: - '@microsoft/fast-element': 1.12.0 - '@microsoft/fast-foundation': 2.49.0 - react: 18.1.0 - dev: false - - /@microsoft/fast-web-utilities@5.4.1: - resolution: {integrity: sha512-ReWYncndjV3c8D8iq9tp7NcFNc1vbVHvcBFPME2nNFKNbS1XCesYZGlIlf3ot5EmuOXPlrzUHOWzQ2vFpIkqDg==} - dependencies: - exenv-es6: 1.1.1 - dev: false - /@microsoft/fast-web-utilities@6.0.0: resolution: {integrity: sha512-ckCA4Xn91ja1Qz+jhGGL1Q3ZeuRpA5VvYcRA7GzA1NP545sl14bwz3tbHCq8jIk+PL7mkSaIveGMYuJB2L4Izg==} dependencies: @@ -11306,14 +11225,6 @@ packages: resolution: {integrity: sha512-2kE8rEFgJpbBAPw5JghccEevQb0XVU0tewF/8h7wPQTeCtoJ6h8qmBIwuzUVm2MutmzC/cpCkwxudixoNYDp1A==} dev: true - /@types/vscode-webview@1.57.1: - resolution: {integrity: sha512-ghW5SfuDmsGDS2A4xkvGsLwDRNc3Vj5rS6rPOyPm/IryZuf3wceZKxgYaUoW+k9f0f/CB7y2c1rRsdOWZWn0PQ==} - dev: true - - /@types/vscode@1.76.0: - resolution: {integrity: sha512-CQcY3+Fe5hNewHnOEAVYj4dd1do/QHliXaknAEYSXx2KEHUzFibDZSKptCon+HPgK55xx20pR+PBJjf0MomnBA==} - dev: true - /@types/ws@8.5.3: resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==} dependencies: @@ -11918,58 +11829,6 @@ packages: pretty-format: 29.7.0 dev: true - /@vscode/test-electron@2.3.2: - resolution: {integrity: sha512-CRfQIs5Wi5Ok5SUCC3PTvRRXa74LD43cSXHC8EuNlmHHEPaJa/AGrv76brcA1hVSxrdja9tiYwp95Lq8kwY0tw==} - engines: {node: '>=16'} - dependencies: - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 - jszip: 3.10.1 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@vscode/vsce@2.19.0: - resolution: {integrity: sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==} - engines: {node: '>= 14'} - hasBin: true - dependencies: - azure-devops-node-api: 11.2.0 - chalk: 2.4.2 - cheerio: 1.0.0-rc.12 - commander: 6.2.1 - glob: 7.2.3 - hosted-git-info: 4.1.0 - jsonc-parser: 3.2.0 - leven: 3.1.0 - markdown-it: 12.3.2 - mime: 1.6.0 - minimatch: 3.1.2 - parse-semver: 1.1.1 - read: 1.0.7 - semver: 5.7.1 - tmp: 0.2.1 - typed-rest-client: 1.8.9 - url-join: 4.0.1 - xml2js: 0.5.0 - yauzl: 2.10.0 - yazl: 2.5.1 - optionalDependencies: - keytar: 7.9.0 - dev: true - - /@vscode/webview-ui-toolkit@1.2.2(react@18.1.0): - resolution: {integrity: sha512-xIQoF4FC3Xh6d7KNKIoIezSiFWYFuf6gQMdDyKueKBFGeKwaHWEn+dY2g3makvvEsNMEDji/woEwvg9QSbuUsw==} - peerDependencies: - react: '>=16.9.0' - dependencies: - '@microsoft/fast-element': 1.12.0 - '@microsoft/fast-foundation': 2.49.0 - '@microsoft/fast-react-wrapper': 0.1.48(react@18.1.0) - react: 18.1.0 - dev: false - /@web3-storage/multipart-parser@1.0.0: resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} dev: true @@ -17515,10 +17374,6 @@ packages: dependencies: queue: 6.0.2 - /immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - dev: true - /immutable@3.7.6: resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==} engines: {node: '>=0.8.0'} @@ -19090,15 +18945,6 @@ packages: array-includes: 3.1.6 object.assign: 4.1.4 - /jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - dev: true - /junk@1.0.3: resolution: {integrity: sha512-3KF80UaaSSxo8jVnRYtMKNGFOoVPBdkkVPsw+Ad0y4oxKXPduS6G6iHkrf69yJVff/VAaYXkV42rtZ7daJxU3w==} engines: {node: '>=0.10.0'} @@ -19245,12 +19091,6 @@ packages: - supports-color dev: true - /lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - dependencies: - immediate: 3.0.6 - dev: true - /lilconfig@2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -24644,10 +24484,6 @@ packages: resolution: {integrity: sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ==} dev: false - /tabbable@5.3.3: - resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==} - dev: false - /tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} dev: false @@ -26417,14 +26253,6 @@ packages: xmlbuilder: 11.0.1 dev: true - /xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - dependencies: - sax: 1.2.4 - xmlbuilder: 11.0.1 - dev: true - /xml@1.0.1: resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} dev: true diff --git a/tsconfig.json b/tsconfig.json index e68305a4a92..60be272fbdf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,6 @@ { "path": "schema" }, { "path": "client/codeintellify" }, { "path": "client/client-api" }, - { "path": "client/vscode" }, { "path": "client/jetbrains" }, { "path": "client/observability-client" }, { "path": "client/observability-server" }