support extensions contributing actions to the search results info bar
Created by: sqs
This lets extensions and other encapsulated code add buttons or information-display actions right above the search results. Example use cases:
- "Replace" for codemod
- "Statistics" for language statistics
Sample screenshot (this PR just adds the capability to add things like the Replace button, it doesn't actually add that button):
Example code to add that (you can also do this in an extension):
diff --git a/web/src/Layout.tsx b/web/src/Layout.tsx
index 7c419c624..99931fd55 100644
--- a/web/src/Layout.tsx
+++ b/web/src/Layout.tsx
@@ -1,5 +1,5 @@
import { LoadingSpinner } from '@sourcegraph/react-loading-spinner'
-import React, { Suspense } from 'react'
+import React, { Suspense, useEffect } from 'react'
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router'
import { Observable } from 'rxjs'
import { ActivationProps } from '../../shared/src/components/activation/Activation'
@@ -41,6 +41,7 @@ import { UserAreaHeaderNavItem } from './user/area/UserAreaHeader'
import { UserSettingsAreaRoute } from './user/settings/UserSettingsArea'
import { UserSettingsSidebarItems } from './user/settings/UserSettingsSidebar'
import { parseBrowserRepoURL } from './util/url'
+import { registerCodemodSearchContributions } from './enterprise/codemod/contributions/search'
export interface LayoutProps
extends RouteComponentProps<any>,
@@ -101,6 +102,11 @@ export const Layout: React.FunctionComponent<LayoutProps> = props => {
const needsSiteInit = window.context.showOnboarding
const isSiteInit = props.location.pathname === '/site-admin/init'
+ useEffect(() => {
+ const sub = registerCodemodSearchContributions(props)
+ return () => sub.unsubscribe()
+ }, [])
+
useScrollToLocationHash(props.location)
// Remove trailing slash (which is never valid in any of our URLs).
if (props.location.pathname !== '/' && props.location.pathname.endsWith('/')) {
diff --git a/web/src/enterprise/codemod/contributions/search.ts b/web/src/enterprise/codemod/contributions/search.ts
new file mode 100644
index 000000000..d0b9b1ab0
--- /dev/null
+++ b/web/src/enterprise/codemod/contributions/search.ts
@@ -0,0 +1,61 @@
+import H from 'history'
+import { Subscription, Unsubscribable } from 'rxjs'
+import { parseContributionExpressions } from '../../../../../shared/src/api/client/services/contribution'
+import { ContributableMenu } from '../../../../../shared/src/api/protocol'
+import { ExtensionsControllerProps } from '../../../../../shared/src/extensions/controller'
+
+export const CODEMOD_PANEL_VIEW_ID = 'codemod'
+
+export function registerCodemodSearchContributions({
+ history,
+ extensionsController,
+}: {
+ history: H.History
+} & ExtensionsControllerProps<'services'>): Unsubscribable {
+ const subscriptions = new Subscription()
+
+ const REPLACE_ID = 'codemod.search.replace'
+ subscriptions.add(
+ extensionsController.services.commands.registerCommand({
+ command: REPLACE_ID,
+ run: () => {
+ const text = prompt('Enter replacement text:')
+ if (text !== null) {
+ const params = new URLSearchParams(history.location.search)
+ history.push({
+ // TODO!(sqs):why is this commented out/necessary?
+ //
+ // ...TabsWithURLViewStatePersistence.urlForTabID(history.location, CODEMOD_PANEL_VIEW_ID),
+ search: `${params}`,
+ })
+ }
+ return Promise.resolve()
+ },
+ })
+ )
+ subscriptions.add(
+ extensionsController.services.contribution.registerContributions({
+ contributions: parseContributionExpressions({
+ actions: [
+ {
+ id: REPLACE_ID,
+ title: 'Replace',
+ category: 'Codemod',
+ command: REPLACE_ID,
+ actionItem: {
+ label: 'Replace...',
+ // TODO!(sqs): icon theme color doesn't update
+ iconURL:
+ "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='width:24px;height:24px' viewBox='0 0 24 24'%3E%3Cpath fill='%23a2b0cd' d='M11,6C12.38,6 13.63,6.56 14.54,7.46L12,10H18V4L15.95,6.05C14.68,4.78 12.93,4 11,4C7.47,4 4.57,6.61 4.08,10H6.1C6.56,7.72 8.58,6 11,6M16.64,15.14C17.3,14.24 17.76,13.17 17.92,12H15.9C15.44,14.28 13.42,16 11,16C9.62,16 8.37,15.44 7.46,14.54L10,12H4V18L6.05,15.95C7.32,17.22 9.07,18 11,18C12.55,18 14,17.5 15.14,16.64L20,21.5L21.5,20L16.64,15.14Z' /%3E%3C/svg%3E",
+ },
+ },
+ ],
+ menus: {
+ [ContributableMenu.SearchResultsToolbar]: [{ action: REPLACE_ID }],
+ },
+ }),
+ })
+ )
+
+ return subscriptions
+}