Skip to content

Commit f8b45d6

Browse files
authored
Merge pull request #334 from conwnet/master
release 0.5.0
2 parents f5e74c1 + 028cbd1 commit f8b45d6

33 files changed

Lines changed: 2252 additions & 267 deletions

File tree

.prettierignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ lib
22
dist
33
out
44
node_modules
5-
**/src/vs/**
5+
vscode-web-github1s/src/vs
6+
vscode-web-github1s/extensions

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ The continued development and maintenance of GitHub1s is made possible by these
164164
- [github-code-viewer](https://microsoftedge.microsoft.com/addons/detail/githubcodeviewer/jaaaapanahkknbgdbglnlchbjfhhjlpi) ([febaoshan/edge-extensions-github-code-viewer](https://github.com/febaoshan/edge-extensions-github-code-viewer))
165165
- [Github Web IDE](https://microsoftedge.microsoft.com/addons/detail/akjbkjciknacicbnkfjbnlaeednpadcf) ([zvizvi/Github-Web-IDE](https://github.com/zvizvi/Github-Web-IDE))
166166

167+
### Safari Extension
168+
169+
- [GitHub1s-For-Safari-Extension](https://apps.apple.com/us/app/readcodeonline/id1569026520?mt=12) ([code4you2021/GitHub1s-For-Safari-Extension](https://github.com/code4you2021/GitHub1s-For-Safari-Extension))
170+
167171
### Tampermonkey scripts
168172

169173
- [Mr-B0b/TamperMonkeyScripts/vscode.js](https://github.com/Mr-B0b/TamperMonkeyScripts/blob/main/vscode.js)

extensions/github1s/src/extension.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { registerEventListeners } from '@/listeners';
1717
import { PageType } from './router/types';
1818

1919
export async function activate(context: vscode.ExtensionContext) {
20-
const browserUrl = (await await vscode.commands.executeCommand(
20+
const browserUrl = (await vscode.commands.executeCommand(
2121
'github1s.vscode.get-browser-url'
2222
)) as string;
2323

@@ -40,17 +40,37 @@ export async function activate(context: vscode.ExtensionContext) {
4040

4141
// sponsors in Status Bar
4242
showSponsors();
43-
await showGitpod();
43+
showGitpod();
4444

45-
// open corresponding editor if there is a filePath specified in browser url
46-
const { filePath, pageType } = await router.getState();
47-
if (filePath && [PageType.TREE, PageType.BLOB].includes(pageType)) {
45+
// initialize the VSCode's state
46+
initialVSCodeState();
47+
}
48+
49+
// initialize the VSCode's state according to the router url
50+
const initialVSCodeState = async () => {
51+
const routerState = await router.getState();
52+
const { filePath, pageType } = routerState;
53+
const scheme = GitHub1sFileSystemProvider.scheme;
54+
55+
if (filePath && pageType === PageType.TREE) {
4856
vscode.commands.executeCommand(
49-
pageType === PageType.TREE ? 'revealInExplorer' : 'vscode.open',
50-
vscode.Uri.parse('').with({
51-
scheme: GitHub1sFileSystemProvider.scheme,
52-
path: filePath,
53-
})
57+
'revealInExplorer',
58+
vscode.Uri.parse('').with({ scheme, path: filePath })
59+
);
60+
} else if (filePath && pageType === PageType.BLOB) {
61+
const { startLineNumber, endLineNumber } = routerState;
62+
const start = new vscode.Position(startLineNumber - 1, 0);
63+
const end = new vscode.Position(endLineNumber - 1, 999999);
64+
const documentShowOptions: vscode.TextDocumentShowOptions = startLineNumber
65+
? { selection: new vscode.Range(start, end) }
66+
: {};
67+
68+
// TODO: the selection of the opening file may be cleared
69+
// when editor try to restore previous state in the same file
70+
vscode.commands.executeCommand(
71+
'vscode.open',
72+
vscode.Uri.parse('').with({ scheme, path: filePath }),
73+
documentShowOptions
5474
);
5575
} else if (pageType === PageType.PULL_LIST) {
5676
vscode.commands.executeCommand('github1s.views.pull-request-list.focus');
@@ -59,4 +79,4 @@ export async function activate(context: vscode.ExtensionContext) {
5979
} else if ([PageType.PULL, PageType.COMMIT].includes(pageType)) {
6080
vscode.commands.executeCommand('workbench.scm.focus');
6181
}
62-
}
82+
};

extensions/github1s/src/helpers/func.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,31 @@ export const throttle = <T extends (...args: any[]) => any>(
4141
timer = setTimeout(() => (timer = null), interval);
4242
};
4343
};
44+
45+
export const debounce = <T extends (...args: any[]) => any>(
46+
func: T,
47+
wait: number
48+
) => {
49+
let timer = null;
50+
return function (...args: Parameters<T>): void {
51+
timer && clearTimeout(timer);
52+
timer = setTimeout(() => func.call(this, ...args), timer);
53+
};
54+
};
55+
56+
// debounce an async func. once an async func canceled, it throws a exception
57+
export const debounceAsyncFunc = <T extends (...args: any[]) => Promise<any>>(
58+
func: T,
59+
wait: number
60+
) => {
61+
let timer = null;
62+
let previousReject = null;
63+
return function (...args: Parameters<T>): ReturnType<T> {
64+
return new Promise((resolve, reject) => {
65+
timer && clearTimeout(timer);
66+
previousReject && previousReject();
67+
timer = setTimeout(() => resolve(func.call(this, ...args)), wait);
68+
previousReject = reject;
69+
}) as ReturnType<T>;
70+
};
71+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @file extension url helpers
3+
* @author netcon
4+
*/
5+
6+
export const getSourcegraphUrl = (
7+
owner: string,
8+
repo: string,
9+
ref: string,
10+
path: string,
11+
line: number,
12+
character: number
13+
): string => {
14+
const repoUrl = `https://sourcegraph.com/github.com/${owner}/${repo}@${ref}`;
15+
return `${repoUrl}/-/blob${path}#L${line + 1}:${character + 1}`;
16+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @file Sourcegraph api common utils
3+
* @author netcon
4+
*/
5+
6+
import {
7+
ApolloClient,
8+
createHttpLink,
9+
InMemoryCache,
10+
} from '@apollo/client/core';
11+
import { trimEnd, trimStart } from '@/helpers/util';
12+
13+
const sourcegraphLink = createHttpLink({
14+
// Since the Sourcegraph refused the CORS check now,
15+
// use Vercel Serverless Function to proxy it temporarily
16+
// See `/api/sourcegraph.js`
17+
uri: '/api/sourcegraph',
18+
});
19+
20+
export const sourcegraphClient = new ApolloClient({
21+
link: sourcegraphLink,
22+
cache: new InMemoryCache(),
23+
});
24+
25+
export const canBeConvertToRegExp = (str: string) => {
26+
try {
27+
new RegExp(str);
28+
return true;
29+
} catch (e) {
30+
return false;
31+
}
32+
};
33+
34+
export const combineGlobsToRegExp = (globs: string[]) => {
35+
// only support very simple globs convert now
36+
const result = Array.from(
37+
new Set(
38+
globs.map((glob: string) =>
39+
trimEnd(trimStart(glob, '*/'), '*/').replace(/^\./, '\\.')
40+
)
41+
)
42+
)
43+
// if the glob still not can be convert to a regexp, just ignore it
44+
.filter((item) => canBeConvertToRegExp(item))
45+
.join('|');
46+
// ensure the result can be convert to a regexp
47+
return canBeConvertToRegExp(result) ? result : '';
48+
};
49+
50+
export const escapeRegexp = (text: string): string =>
51+
text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
52+
53+
export const getRepoRefQueryString = (
54+
owner: string,
55+
repo: string,
56+
ref: string
57+
) => {
58+
// the string may looks like `^github\.com/conwnet/github1s$`
59+
const repoPattern = `^${escapeRegexp(`github\.com/${owner}/${repo}`)}$`;
60+
const repoRefQueryString =
61+
ref.toUpperCase() === 'HEAD'
62+
? `repo:${repoPattern}`
63+
: `repo:${repoPattern}@${ref}`;
64+
return repoRefQueryString;
65+
};
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/**
2+
* @file Sourcegraph definition api
3+
* @author netcon
4+
*/
5+
6+
import { gql } from '@apollo/client/core';
7+
import { sourcegraphClient } from './common';
8+
import { getSymbolPositions } from './position';
9+
10+
export interface SymbolDefinition {
11+
precise: boolean;
12+
owner: string;
13+
repo: string;
14+
ref: string;
15+
path: string;
16+
range: {
17+
start: {
18+
line: number;
19+
character: number;
20+
};
21+
end: {
22+
line: number;
23+
character: number;
24+
};
25+
};
26+
}
27+
28+
const LSIFDefinitionsQuery = gql`
29+
query(
30+
$repository: String!
31+
$ref: String!
32+
$path: String!
33+
$line: Int!
34+
$character: Int!
35+
) {
36+
repository(name: $repository) {
37+
commit(rev: $ref) {
38+
blob(path: $path) {
39+
lsif {
40+
definitions(line: $line, character: $character) {
41+
nodes {
42+
resource {
43+
path
44+
repository {
45+
name
46+
}
47+
commit {
48+
oid
49+
}
50+
}
51+
range {
52+
start {
53+
line
54+
character
55+
}
56+
end {
57+
line
58+
character
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
`;
69+
70+
// find definitions with Sourcegraph LSIF
71+
// https://docs.sourcegraph.com/code_intelligence/explanations/precise_code_intelligence
72+
const getLSIFDefinitions = async (
73+
owner: string,
74+
repo: string,
75+
ref: string,
76+
path: string,
77+
line: number,
78+
character: number
79+
): Promise<SymbolDefinition[]> => {
80+
const response = await sourcegraphClient.query({
81+
query: LSIFDefinitionsQuery,
82+
variables: {
83+
repository: `github.com/${owner}/${repo}`,
84+
ref,
85+
path: path.slice(1),
86+
line,
87+
character,
88+
},
89+
});
90+
const definitionNodes =
91+
response?.data?.repository?.commit?.blob?.lsif?.definitions?.nodes;
92+
return (definitionNodes || []).map(({ resource, range }) => {
93+
const [owner, repo] = resource.repository.name
94+
.split('/')
95+
.filter(Boolean)
96+
.slice(-2);
97+
return {
98+
precise: true,
99+
owner,
100+
repo,
101+
ref: resource.commit.oid,
102+
path: `/${resource.path}`,
103+
range,
104+
};
105+
});
106+
};
107+
108+
export const getSymbolDefinitions = (
109+
owner: string,
110+
repo: string,
111+
ref: string,
112+
path: string,
113+
line: number,
114+
character: number,
115+
symbol: string
116+
): Promise<SymbolDefinition[]> => {
117+
// if failed to find definitions from LSIF,
118+
// fallback to search-based definitions, using
119+
// two promise instead of `await` to request in
120+
// parallel for getting result as soon as possible
121+
const LSIFDefinitionsPromise = getLSIFDefinitions(
122+
owner,
123+
repo,
124+
ref,
125+
path,
126+
line,
127+
character
128+
);
129+
const searchDefinitionsPromise = getSymbolPositions(owner, repo, ref, symbol);
130+
131+
return LSIFDefinitionsPromise.then((LSIFDefinitions) => {
132+
if (LSIFDefinitions.length) {
133+
return LSIFDefinitions;
134+
}
135+
return searchDefinitionsPromise as Promise<SymbolDefinition[]>;
136+
});
137+
};

0 commit comments

Comments
 (0)