Skip to content

Commit 5a4e64d

Browse files
committed
Update based on feedback
1 parent 7b03f18 commit 5a4e64d

File tree

6 files changed

+97
-31
lines changed

6 files changed

+97
-31
lines changed

package.json

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
],
99
"wip": true,
1010
"categories": [],
11-
"tags": [],
11+
"tags": [
12+
"campaigns"
13+
],
1214
"contributes": {
1315
"actions": [
1416
{
15-
"id": "start-find-replace",
16-
"title": "Find-replace",
17-
"command": "start-find-replace",
17+
"id": "findReplace.startFindReplace",
18+
"title": "Create a new find-replace campaign",
19+
"command": "findReplace.startFindReplace",
1820
"commandArguments": [
1921
"${get(context, 'searchQuery')}"
2022
],
@@ -26,12 +28,12 @@
2628
"menus": {
2729
"search/results/toolbar": [
2830
{
29-
"action": "start-find-replace"
31+
"action": "findReplace.startFindReplace"
3032
}
3133
],
3234
"commandPalette": [
3335
{
34-
"action": "start-find-replace"
36+
"action": "findReplace.startFindReplace"
3537
}
3638
]
3739
},
@@ -49,7 +51,8 @@
4951
"watch:typecheck": "tsc -p tsconfig.json -w",
5052
"watch:build": "tsc -p tsconfig.dist.json -w",
5153
"sourcegraph:prepublish": "yarn run typecheck && yarn run build",
52-
"prettier": "prettier '**/*.{ts,md,json}' --write --list-different"
54+
"prettier": "prettier '**/*.{ts,md,json}' --write --list-different",
55+
"test": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\"}' mocha --require ts-node/register **/*.test.ts"
5356
},
5457
"browserslist": [
5558
"last 1 Chrome versions",
@@ -60,9 +63,13 @@
6063
"devDependencies": {
6164
"@sourcegraph/eslint-config": "^0.20.11",
6265
"@sourcegraph/tsconfig": "^4.0.1",
66+
"@types/assert": "^1.5.2",
67+
"@types/mocha": "^8.0.3",
68+
"assert": "^2.0.0",
6369
"eslint": "^7.11.0",
6470
"lnfs-cli": "^2.1.0",
6571
"mkdirp": "^1.0.4",
72+
"mocha": "^8.2.0",
6673
"parcel-bundler": "^1.12.4",
6774
"prettier": "^2.1.2",
6875
"sourcegraph": "^24.7.0",
@@ -71,6 +78,7 @@
7178
"dependencies": {
7279
"@sourcegraph/campaigns-client": "file:../campaigns-client",
7380
"rxjs": "^6.6.3",
74-
"slugify": "^1.4.5"
81+
"slugify": "^1.4.5",
82+
"ts-node": "^9.0.0"
7583
}
7684
}

src/edit-file.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { editFile } from './edit-file'
2+
import assert from 'assert'
3+
4+
const input = 'Red redder reddest'
5+
6+
describe('editFile', () => {
7+
it('returns identical output', () => {
8+
const output = editFile(input, 'foo', 'bar')
9+
assert.strictEqual(output, input)
10+
})
11+
12+
it('replaces a string with a replacement', () => {
13+
const output = editFile(input, 'red', 'bar')
14+
assert.strictEqual(output, 'Red barder bardest')
15+
})
16+
17+
it('replaces a string with a blank string', () => {
18+
const output = editFile(input, 'ed', '')
19+
assert.strictEqual(output, 'R rder rdest')
20+
})
21+
22+
it('works with blank find string', () => {
23+
const output = editFile(input, '', 'foo')
24+
assert.strictEqual(output, input)
25+
})
26+
})

src/edit-file.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Edit a file to apply a string replacement
3+
*/
4+
export function editFile(text: string, findString: string, replacement: string): string {
5+
if (findString.length === 0) {
6+
return text
7+
}
8+
9+
if (!text.includes(findString)) {
10+
return text
11+
}
12+
13+
return text.split(findString).join(replacement)
14+
}

src/find-replace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as sourcegraph from 'sourcegraph'
1+
import sourcegraph from 'sourcegraph'
22
import { registerFindReplaceAction } from './register-action'
33

44
export function activate(context: sourcegraph.ExtensionContext): void {

src/register-action.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,82 @@
11
import { Subscription } from 'rxjs'
2-
import * as sourcegraph from 'sourcegraph'
2+
import sourcegraph from 'sourcegraph'
33
import { evaluateAndCreateCampaignSpec } from '@sourcegraph/campaigns-client'
44
import slugify from 'slugify'
55
import { getCurrentUser } from './util'
6+
import { editFile } from './edit-file'
67

78
// TODO: sanitize this for real, it gets used in the description of the campaign
89
const escapedMarkdownCode = (text: string): string => '`' + text.replace(/`/g, '\\`') + '`'
910

10-
// TODO: instead of using fileFilter, use the search results as the list of matching files
11-
const fileFilter = (path: string): boolean => path.endsWith('.yaml') || path.endsWith('.yml') || path.endsWith('.md')
12-
1311
export const registerFindReplaceAction = (): Subscription => {
1412
const subscription = new Subscription()
1513
subscription.add(
16-
sourcegraph.commands.registerCommand('start-find-replace', async (searchQuery: string) => {
14+
sourcegraph.commands.registerCommand('findReplace.startFindReplace', async (searchQuery: string) => {
1715
// TODO: in the future, use the search query to get the list of matching files.
1816
console.log('context.searchQuery', searchQuery)
1917

18+
if (!searchQuery) {
19+
return
20+
}
21+
2022
// To create campaigns, a namespace is used, which can be the current user's username.
2123
const currentUser = await getCurrentUser()
22-
const namespaceName = currentUser?.username
23-
console.log('currentUser', currentUser)
24+
if (!currentUser) {
25+
throw new Error('No current user')
26+
}
27+
const namespaceName = currentUser.username
2428

25-
const match = await sourcegraph.app.activeWindow!.showInputBox({
29+
const findString = await sourcegraph.app.activeWindow!.showInputBox({
2630
prompt: 'Find all matches of:',
2731
})
28-
if (!match) {
32+
if (!findString) {
2933
return
3034
}
3135

32-
const replacement = await sourcegraph.app.activeWindow!.showInputBox({
36+
const replacementString = await sourcegraph.app.activeWindow!.showInputBox({
3337
prompt: 'Replace with:',
3438
})
3539
// Empty string is a valid replacement, so compare directly with undefined.
36-
if (replacement === undefined) {
40+
if (replacementString === undefined) {
3741
return
3842
}
3943

40-
const name = `replace-${slugify(match)}-with-${slugify(replacement)}`
41-
const description = `Replace ${escapedMarkdownCode(match)} with ${escapedMarkdownCode(replacement)}`
44+
const campaignName = `replace-${slugify(findString)}-with-${slugify(replacementString)}`
45+
const description = `Replace ${escapedMarkdownCode(findString)} with ${escapedMarkdownCode(
46+
replacementString
47+
)}`
4248

4349
let percentage = 0
4450
const { applyURL, diffStat } = await sourcegraph.app.activeWindow!.withProgress(
4551
{ title: '**Find-replace**' },
46-
async reporter =>
52+
reporter =>
4753
evaluateAndCreateCampaignSpec(namespaceName, {
48-
name,
54+
name: campaignName,
4955
on: [
5056
{
51-
repositoriesMatchingQuery: match,
57+
repositoriesMatchingQuery: searchQuery,
5258
},
5359
],
5460
description,
5561
steps: [
5662
{
57-
fileFilter,
63+
fileFilter: () => true,
5864
editFile: (path, text) => {
59-
if (!text.includes(match)) {
65+
if (!text.includes(findString)) {
66+
// skip the file by returning null
6067
return null
6168
}
6269

6370
percentage += (100 - percentage) / 100
6471
reporter.next({ message: `Computing changes in ${path}`, percentage })
65-
return text.split(match).join(replacement)
72+
73+
return editFile(text, findString, replacementString)
6674
},
6775
},
6876
],
6977
changesetTemplate: {
7078
title: description,
71-
branch: `campaign/${name}`,
79+
branch: `campaign/${campaignName}`,
7280
commit: {
7381
message: description,
7482
author: {

src/util.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import sourcegraph from 'sourcegraph'
22

3-
export async function getCurrentUser(): Promise<{ username: string; email: string }> {
4-
const response = await sourcegraph.commands.executeCommand(
3+
interface CurrentUserResponse {
4+
data?: {
5+
currentUser?: {
6+
username: string
7+
email: string
8+
}
9+
}
10+
errors?: string[]
11+
}
12+
13+
export async function getCurrentUser(): Promise<{ username: string; email: string } | undefined> {
14+
const response = await sourcegraph.commands.executeCommand<CurrentUserResponse>(
515
'queryGraphQL',
616
`{
717
currentUser {

0 commit comments

Comments
 (0)