Skip to content

Commit 27ba519

Browse files
authored
feat: make action item jump to matching line (#30)
1 parent 8dfad3d commit 27ba519

File tree

5 files changed

+43
-34
lines changed

5 files changed

+43
-34
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
},
3939
{
4040
"id": "codeOwnership.file",
41+
"command": "open",
42+
"commandArguments": [
43+
"${get(context, `codeOwnership.file.${resource.uri}.url`)}"
44+
],
4145
"actionItem": {
4246
"label": "${get(context, `codeOwnership.file.${resource.uri}.label`)}",
4347
"description": "${get(context, `codeOwnership.file.${resource.uri}.description`)}"

src/codeOwners.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const SHOW_SINGLE_OWNER_MAX_LENGTH = 14
22

3-
export function formatCodeOwners(owners: string[] | null): { label: string | null; description: string | null } {
4-
if (owners === null) {
3+
export function formatCodeOwners(
4+
owners: string[] | null | undefined
5+
): { label: string | null; description: string | null } {
6+
if (!owners) {
57
return { label: null, description: null }
68
}
79
if (owners.length === 0) {

src/codeownersFile.ts

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const getCodeownersFile = memoizeAsync(
1111
}: {
1212
uri: string
1313
sourcegraph: typeof import('sourcegraph')
14-
}): Promise<string | null> => {
14+
}): Promise<{ path: string; content: string } | null> => {
1515
const { repo, rev } = resolveURI(uri)
1616
const { data } = await sourcegraph.commands.executeCommand(
1717
'queryGraphQL',
@@ -35,10 +35,10 @@ const getCodeownersFile = memoizeAsync(
3535
throw new Error('repository or commit not found when getting CODEOWNERS file')
3636
}
3737
if (data.repository.commit.codeownersBlob && data.repository.commit.codeownersBlob.content) {
38-
return data.repository.commit.codeownersBlob.content
38+
return { path: 'CODEOWNERS', content: data.repository.commit.codeownersBlob.content }
3939
}
4040
if (data.repository.commit.githubCodeownersBlob && data.repository.commit.githubCodeownersBlob.content) {
41-
return data.repository.commit.githubCodeownersBlob.content
41+
return { path: '.github/CODEOWNERS', content: data.repository.commit.githubCodeownersBlob.content }
4242
}
4343
return null
4444
},
@@ -48,15 +48,21 @@ const getCodeownersFile = memoizeAsync(
4848
}
4949
)
5050

51-
export async function getCodeOwners(uri: string): Promise<string[] | null> {
51+
export interface ResolvedOwnersLine {
52+
path: string
53+
lineNumber: number
54+
owners: string[]
55+
}
56+
57+
export async function getCodeOwners(uri: string): Promise<ResolvedOwnersLine | null> {
5258
const codeownersFile = await getCodeownersFile({ uri, sourcegraph })
5359
if (!codeownersFile) {
5460
return null
5561
}
56-
const codeownersEntries = parseCodeownersFile(codeownersFile)
57-
const entry = matchCodeownersFile(resolveURI(uri).path, codeownersEntries)
58-
if (entry) {
59-
return entry.owners
62+
const codeownersEntries = parseCodeownersFile(codeownersFile.content)
63+
const matching = codeownersEntries.find(entry => matchPattern(resolveURI(uri).path, entry.pattern))
64+
if (matching) {
65+
return { path: codeownersFile.path, lineNumber: matching.lineNumber, owners: matching.owners }
6066
}
6167
return null
6268
}
@@ -65,6 +71,7 @@ export async function getCodeOwners(uri: string): Promise<string[] | null> {
6571
* An individual entry from a CODEOWNERS file
6672
*/
6773
export interface CodeOwnersEntry {
74+
lineNumber: number
6875
pattern: string
6976
owners: string[]
7077
}
@@ -74,17 +81,17 @@ export interface CodeOwnersEntry {
7481
* of the file).
7582
*/
7683
export function parseCodeownersFile(str: string): CodeOwnersEntry[] {
77-
const entries = []
84+
const entries: CodeOwnersEntry[] = []
7885
const lines = str.split('\n')
7986

80-
for (const line of lines) {
81-
const [content] = line.split('#')
87+
for (const [index, lineText] of lines.entries()) {
88+
const [content] = lineText.split('#')
8289
const trimmed = content.trim()
8390
if (trimmed === '') {
8491
continue
8592
}
8693
const [pattern, ...owners] = trimmed.split(/\s+/)
87-
entries.push({ pattern, owners })
94+
entries.push({ pattern, owners, lineNumber: index + 1 })
8895
}
8996

9097
return entries.reverse()
@@ -96,16 +103,3 @@ export function parseCodeownersFile(str: string): CodeOwnersEntry[] {
96103
export function matchPattern(filename: string, pattern: string): boolean {
97104
return ignore().add(pattern).ignores(filename)
98105
}
99-
100-
/**
101-
* Match a filename against CODEOWNERS entries to determine which (if any) it
102-
* matches.
103-
*/
104-
export function matchCodeownersFile(filename: string, entries: CodeOwnersEntry[]): CodeOwnersEntry | null {
105-
for (const entry of entries) {
106-
if (matchPattern(filename, entry.pattern)) {
107-
return entry
108-
}
109-
}
110-
return null
111-
}

src/extension.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { startWith, switchMap, filter, map } from 'rxjs/operators'
33
import * as sourcegraph from 'sourcegraph'
44
import { getCodeOwners } from './codeownersFile'
55
import { formatCodeOwners } from './codeOwners'
6+
import { resolveURI } from './uri'
67

78
export function activate(ctx: sourcegraph.ExtensionContext): void {
89
ctx.subscriptions.add(
@@ -22,17 +23,25 @@ export function activate(ctx: sourcegraph.ExtensionContext): void {
2223
switchMap(async textDocument => {
2324
if (!sourcegraph.configuration.get().value['codeOwnership.hide']) {
2425
try {
25-
return { textDocument, owners: await getCodeOwners(textDocument.uri) }
26+
return { textDocument, resolvedOwnersLine: await getCodeOwners(textDocument.uri) }
2627
} catch (err) {
2728
console.error(`Error getting code owners for ${textDocument.uri}:`, err)
2829
}
2930
}
30-
return { textDocument, owners: null }
31+
return { textDocument, resolvedOwnersLine: null }
3132
})
3233
)
33-
.subscribe(({ textDocument, owners }) => {
34-
const { label, description } = formatCodeOwners(owners)
34+
.subscribe(({ textDocument, resolvedOwnersLine }) => {
35+
const { label, description } = formatCodeOwners(resolvedOwnersLine?.owners)
36+
const { repo, rev } = resolveURI(textDocument.uri)
37+
const url =
38+
resolvedOwnersLine &&
39+
new URL(
40+
`${repo}@${rev}/-/blob/${resolvedOwnersLine.path}#L${resolvedOwnersLine.lineNumber}`,
41+
sourcegraph.internal.sourcegraphURL
42+
).href
3543
sourcegraph.internal.updateContext({
44+
[`codeOwnership.file.${textDocument.uri}.url`]: url,
3645
[`codeOwnership.file.${textDocument.uri}.label`]: label,
3746
[`codeOwnership.file.${textDocument.uri}.description`]: description,
3847
})

src/uri.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ export function resolveURI(uri: string): { repo: string; rev: string; path: stri
66
const url = new URL(uri)
77
if (url.protocol === 'git:') {
88
return {
9-
repo: (url.host + url.pathname).replace(/^\/*/, '').toLowerCase(),
10-
rev: url.search.slice(1).toLowerCase(),
11-
path: url.hash.slice(1),
9+
repo: decodeURIComponent((url.host + url.pathname).replace(/^\/*/, '').toLowerCase()),
10+
rev: decodeURIComponent(url.search.slice(1).toLowerCase()),
11+
path: decodeURIComponent(url.hash.slice(1)),
1212
}
1313
}
1414
throw new Error(`unrecognized URI: ${JSON.stringify(uri)} (supported URI schemes: git)`)

0 commit comments

Comments
 (0)