().value
56 | const decorations = settings['git.blame.decorations'] || 'none'
57 | const shouldQueryBlameHunks = Boolean(decorations === 'file' || (decorations === 'line' && selections?.length))
58 |
59 | try {
60 | const hunks = shouldQueryBlameHunks
61 | ? await queryBlameHunks({
62 | uri: editor.document.uri,
63 | sourcegraph,
64 | selections: decorations === 'line' ? selections : null,
65 | })
66 | : []
67 | const now = Date.now()
68 |
69 | // Check if the extension host supports status bar items (Introduced in Sourcegraph version 3.26.0).
70 | // If so, display blame info for the first selected line in the status bar.
71 | if ('setStatusBarItem' in editor) {
72 | editor.setStatusBarItem(
73 | statusBarItemType,
74 | getBlameStatusBarItem({ selections, hunks, now, settings, sourcegraph })
75 | )
76 | }
77 |
78 | editor.setDecorations(
79 | decorationType,
80 | getBlameDecorations({
81 | hunks,
82 | now,
83 | settings,
84 | selections,
85 | sourcegraph,
86 | })
87 | )
88 | } catch (err) {
89 | console.error('Decoration/status bar error:', err)
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/uri.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Resolve a URI of the forms git://github.com/owner/repo?rev#path and file:///path to an absolute reference, using
3 | * the given base (root) URI.
4 | */
5 | export function resolveURI(uri: string): { repo: string; rev: string; path: string } {
6 | const url = new URL(uri)
7 | if (url.protocol === 'git:') {
8 | return {
9 | repo: (url.host + decodeURIComponent(url.pathname)).replace(/^\/*/, ''),
10 | rev: decodeURIComponent(url.search.slice(1)),
11 | path: decodeURIComponent(url.hash.slice(1)),
12 | }
13 | }
14 | throw new Error(`unrecognized URI: ${JSON.stringify(uri)} (supported URI schemes: git)`)
15 | }
16 |
--------------------------------------------------------------------------------
/src/util/memoizeAsync.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a function that memoizes the async result of func. If the Promise is rejected, the result will not be
3 | * cached.
4 | *
5 | * @param toKey etermines the cache key for storing the result based on the first argument provided to the memoized
6 | * function
7 | */
8 | export function memoizeAsync(
9 | func: (params: P) => Promise,
10 | toKey: (params: P) => string
11 | ): (params: P) => Promise {
12 | const cache = new Map>()
13 | return (params: P) => {
14 | const key = toKey(params)
15 | const hit = cache.get(key)
16 | if (hit) {
17 | return hit
18 | }
19 | const p = func(params)
20 | p.then(null, () => cache.delete(key))
21 | cache.set(key, p)
22 | return p
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/util/stubs.ts:
--------------------------------------------------------------------------------
1 | import { uniqueId } from 'lodash'
2 | import { Subject, Subscription } from 'rxjs'
3 | import * as sinon from 'sinon'
4 | import * as sourcegraph from 'sourcegraph'
5 | import { MarkupKind } from 'vscode-languageserver-types'
6 |
7 | const URI = URL
8 | type URI = URL
9 | class Position {
10 | constructor(public line: number, public character: number) {}
11 | }
12 | class Range {
13 | constructor(public start: Position, public end: Position) {}
14 | }
15 | class Location {
16 | constructor(public uri: URI, public range: Range) {}
17 | }
18 | class Selection extends Range {
19 | constructor(public anchor: Position, public active: Position) {
20 | super(anchor, active)
21 | }
22 | }
23 |
24 | /**
25 | * Creates an object that (mostly) implements the Sourcegraph API,
26 | * with all methods being Sinon spys and all Subscribables being Subjects.
27 | */
28 | export const createMockSourcegraphAPI = (sourcegraphURL?: string) => {
29 | const rootChanges = new Subject()
30 | // const shims: typeof import('sourcegraph') = {
31 | const openedTextDocuments = new Subject()
32 | return {
33 | internal: {
34 | sourcegraphURL: sourcegraphURL || 'https://sourcegraph.test',
35 | },
36 | URI,
37 | Position,
38 | Range,
39 | Location,
40 | Selection,
41 | MarkupKind,
42 | workspace: {
43 | onDidOpenTextDocument: openedTextDocuments,
44 | openedTextDocuments,
45 | textDocuments: [] as sourcegraph.TextDocument[],
46 | onDidChangeRoots: rootChanges,
47 | rootChanges,
48 | roots: [] as sourcegraph.WorkspaceRoot[],
49 | },
50 | languages: {
51 | registerHoverProvider: sinon.spy(
52 | (
53 | selector: sourcegraph.DocumentSelector,
54 | provider: {
55 | provideHover: (
56 | textDocument: sourcegraph.TextDocument,
57 | position: Position
58 | ) => Promise
59 | }
60 | ) => new Subscription()
61 | ),
62 | registerDefinitionProvider: sinon.spy(
63 | (
64 | selector: sourcegraph.DocumentSelector,
65 | provider: {
66 | provideDefinition: (
67 | textDocument: sourcegraph.TextDocument,
68 | position: Position
69 | ) => Promise
70 | }
71 | ) => new Subscription()
72 | ),
73 | registerLocationProvider: sinon.spy(
74 | (
75 | selector: sourcegraph.DocumentSelector,
76 | provider: {
77 | provideLocations: (
78 | textDocument: sourcegraph.TextDocument,
79 | position: Position
80 | ) => Promise
81 | }
82 | ) => new Subscription()
83 | ),
84 | registerReferenceProvider: sinon.spy(
85 | (
86 | selector: sourcegraph.DocumentSelector,
87 | provider: {
88 | provideReferences: (
89 | textDocument: sourcegraph.TextDocument,
90 | position: Position,
91 | context: sourcegraph.ReferenceContext
92 | ) => Promise
93 | }
94 | ) => new Subscription()
95 | ),
96 | registerTypeDefinitionProvider: sinon.spy(
97 | (
98 | selector: sourcegraph.DocumentSelector,
99 | provider: {
100 | provideTypeDefinition: (
101 | textDocument: sourcegraph.TextDocument,
102 | position: Position
103 | ) => Promise
104 | }
105 | ) => new Subscription()
106 | ),
107 | registerImplementationProvider: sinon.spy(
108 | (
109 | selector: sourcegraph.DocumentSelector,
110 | provider: {
111 | provideImplementation: (
112 | textDocument: sourcegraph.TextDocument,
113 | position: Position
114 | ) => Promise
115 | }
116 | ) => new Subscription()
117 | ),
118 | },
119 | app: {
120 | createDecorationType: () => ({ key: uniqueId('decorationType') }),
121 | },
122 | configuration: {},
123 | search: {},
124 | commands: {},
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/@sourcegraph/tsconfig/tsconfig.json",
3 | "compilerOptions": {
4 | "target": "es2018",
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "lib": ["es2018", "webworker"],
8 | "inlineSources": true,
9 | "inlineSourceMap": true,
10 | "declaration": false,
11 | "outDir": "dist",
12 | "noEmit": true,
13 | "rootDir": "src",
14 | "esModuleInterop": true,
15 | "allowSyntheticDefaultImports": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@sourcegraph/tslint-config"]
3 | }
4 |
--------------------------------------------------------------------------------