Skip to content

Commit 896ab5b

Browse files
committed
Create find-replace extension
1 parent 5587ea3 commit 896ab5b

File tree

4 files changed

+137
-17
lines changed

4 files changed

+137
-17
lines changed

package.json

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,27 @@
1010
"categories": [],
1111
"tags": [],
1212
"contributes": {
13-
"actions": [],
13+
"actions": [
14+
{
15+
"id": "start-find-replace",
16+
"title": "New find-replace campaign...",
17+
"command": "start-find-replace",
18+
"actionItem": {
19+
"label": "Find-replace"
20+
}
21+
}
22+
],
1423
"menus": {
15-
"editor/title": [],
16-
"commandPalette": []
24+
"global/nav": [
25+
{
26+
"action": "start-find-replace"
27+
}
28+
],
29+
"commandPalette": [
30+
{
31+
"action": "start-find-replace"
32+
}
33+
]
1734
},
1835
"configuration": {}
1936
},
@@ -28,7 +45,8 @@
2845
"serve": "yarn run symlink-package && parcel serve --no-hmr --out-file dist/find-replace.js src/find-replace.ts",
2946
"watch:typecheck": "tsc -p tsconfig.json -w",
3047
"watch:build": "tsc -p tsconfig.dist.json -w",
31-
"sourcegraph:prepublish": "yarn run typecheck && yarn run build"
48+
"sourcegraph:prepublish": "yarn run typecheck && yarn run build",
49+
"prettier": "prettier '**/*.{ts,md}' --write --list-different"
3250
},
3351
"browserslist": [
3452
"last 1 Chrome versions",
@@ -43,7 +61,13 @@
4361
"lnfs-cli": "^2.1.0",
4462
"mkdirp": "^1.0.4",
4563
"parcel-bundler": "^1.12.4",
64+
"prettier": "^2.1.2",
4665
"sourcegraph": "^24.7.0",
4766
"typescript": "^4.0.3"
67+
},
68+
"dependencies": {
69+
"@sourcegraph/campaigns-client": "file:../campaigns-client",
70+
"rxjs": "^6.6.3",
71+
"slugify": "^1.4.5"
4872
}
4973
}

src/find-replace.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import * as sourcegraph from 'sourcegraph'
2+
import { registerFindReplaceAction } from './register-action'
23

3-
export function activate(ctx: sourcegraph.ExtensionContext): void {
4-
ctx.subscriptions.add(
5-
sourcegraph.languages.registerHoverProvider(['*'], {
6-
provideHover: () => ({
7-
contents: {
8-
value: 'Hello world from find-replace! 🎉🎉🎉',
9-
kind: sourcegraph.MarkupKind.Markdown
10-
}
11-
}),
12-
})
13-
)
4+
export function activate(context: sourcegraph.ExtensionContext): void {
5+
context.subscriptions.add(registerFindReplaceAction())
146
}
15-
16-
// Sourcegraph extension documentation: https://docs.sourcegraph.com/extensions/authoring

src/register-action.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Subscription } from 'rxjs'
2+
import * as sourcegraph from 'sourcegraph'
3+
import { evaluateAndCreateCampaignSpec } from '@sourcegraph/campaigns-client'
4+
import slugify from 'slugify'
5+
import { getCurrentUser } from './util'
6+
7+
// TODO(sqs) SECURITY(sqs): sanitize for real
8+
const escapedMarkdownCode = (text: string): string => '`' + text.replace(/`/g, '\\`') + '`'
9+
10+
export const registerFindReplaceAction = (): Subscription => {
11+
const subscription = new Subscription()
12+
subscription.add(
13+
sourcegraph.commands.registerCommand('start-find-replace', async () => {
14+
15+
// To create campaigns, a namespace is used, which can be the current user's username.
16+
const currentUser = await getCurrentUser();
17+
const namespaceName = currentUser?.username
18+
console.log('currentUser', currentUser)
19+
20+
const match = await sourcegraph.app.activeWindow!.showInputBox({
21+
prompt: 'Find all matches of:',
22+
})
23+
if (!match) {
24+
return
25+
}
26+
const replacement = await sourcegraph.app.activeWindow!.showInputBox({
27+
prompt: 'Replace with:',
28+
})
29+
if (replacement === undefined) {
30+
return
31+
}
32+
33+
const name = `replace-${slugify(match)}-with-${slugify(replacement)}`
34+
const description = `Replace ${escapedMarkdownCode(match)} with ${escapedMarkdownCode(replacement)}`
35+
36+
let percentage = 0
37+
const { applyURL, diffStat } = await sourcegraph.app.activeWindow!.withProgress(
38+
{ title: '**Find-replace**' },
39+
async reporter =>
40+
evaluateAndCreateCampaignSpec(namespaceName, {
41+
name,
42+
on: [
43+
{
44+
repositoriesMatchingQuery: match,
45+
},
46+
],
47+
description,
48+
steps: [
49+
{
50+
fileFilter: path =>
51+
path.endsWith('.yaml') || path.endsWith('.yml') || path.endsWith('.md'), // TODO(sqs)
52+
editFile: (path, text) => {
53+
if (!text.includes(match)) {
54+
return null
55+
}
56+
try {
57+
console.log('editFile running on', path)
58+
} catch (error) {
59+
console.error('Caught', error)
60+
}
61+
62+
percentage += (100 - percentage) / 100
63+
reporter.next({ message: `Computing changes in ${path}`, percentage })
64+
return text.split(match).join(replacement)
65+
},
66+
},
67+
],
68+
changesetTemplate: {
69+
title: description,
70+
branch: `campaign/${name}`,
71+
commit: {
72+
message: description,
73+
author: {
74+
name: currentUser.username,
75+
email: currentUser.email
76+
},
77+
},
78+
published: false,
79+
},
80+
})
81+
)
82+
await sourcegraph.app.activeWindow!.showNotification(
83+
`[**Find-replace changes**](${applyURL}) are ready to preview and apply.<br/><br/><small>${diffStat.added} additions, ${diffStat.changed} changes, ${diffStat.deleted} deletions</small>`,
84+
sourcegraph.NotificationType.Success
85+
)
86+
87+
// const relativeURL = new URL(applyURL)
88+
// await sourcegraph.commands.executeCommand('open', `${relativeURL.pathname}${relativeURL.search}`)
89+
})
90+
)
91+
return subscription
92+
}

src/util.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import sourcegraph from 'sourcegraph'
2+
3+
export async function getCurrentUser(): Promise<{username: string, email: string}> {
4+
const response = await sourcegraph.commands.executeCommand(
5+
'queryGraphQL',
6+
`{
7+
currentUser {
8+
username, email
9+
}
10+
}`
11+
)
12+
console.log('A response is going to the extension', response.data)
13+
return response?.data?.currentUser
14+
}

0 commit comments

Comments
 (0)