From ab948822a00f69fd937f291bdba7e7957755f71e Mon Sep 17 00:00:00 2001 From: Bo Zhang Date: Sat, 6 Nov 2021 11:57:07 +0800 Subject: [PATCH 01/64] Forward window.postMessage() to vscode command system So we can control the iframe window in main window --- .../src/vs/workbench/browser/web.main.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/vscode-web-github1s/src/vs/workbench/browser/web.main.ts b/vscode-web-github1s/src/vs/workbench/browser/web.main.ts index 1bce19949..694084e42 100644 --- a/vscode-web-github1s/src/vs/workbench/browser/web.main.ts +++ b/vscode-web-github1s/src/vs/workbench/browser/web.main.ts @@ -65,6 +65,11 @@ import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } fr import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { safeStringify } from 'vs/base/common/objects'; +import { globals } from "vs/base/common/platform"; +import { + AutoSaveMode, + IFilesConfigurationService +} from "vs/workbench/services/filesConfiguration/common/filesConfigurationService"; class BrowserMain extends Disposable { @@ -110,6 +115,13 @@ class BrowserMain extends Disposable { const timerService = accessor.get(ITimerService); const openerService = accessor.get(IOpenerService); const productService = accessor.get(IProductService); + // below codes are changed by ByteLegend + // we need to disable autosave, otherwise the editor will immediately become undirty after change + const filesConfigurationService = accessor.get(IFilesConfigurationService); + if (filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { + filesConfigurationService.toggleAutoSave() + } + // above codes are changed by ByteLegend return { commands: { @@ -393,11 +405,23 @@ class BrowserMain extends Disposable { export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { const workbench = new BrowserMain(domElement, options); - // below codes are changed by github1s + // below codes are changed by github1s and ByteLegend return workbench.open().then(workbench => { + registerCommandForwarder(workbench) // Remove the html load spinner document.querySelector('#load-spinner')?.remove(); return workbench; }); - // above codes are changed by github1s + // above codes are changed by github1s and ByteLegend +} + +// Below is changed by ByteLegend +// This event listener listens to window.postMessage and forwards it to vscode command system +function registerCommandForwarder(workbench: IWorkbench) { + globals.addEventListener('message', (message: MessageEvent) => { + if (message.data && message.data.forwardCommand) { + workbench.commands.executeCommand(message.data.forwardCommand.command, ...message.data.forwardCommand.args); + } + }); } +// Above is changed by ByteLegend From 575932e0fee15bd08713b9b65bf71a01fe6045da Mon Sep 17 00:00:00 2001 From: Bo Zhang Date: Mon, 8 Nov 2021 21:47:15 +0800 Subject: [PATCH 02/64] Add more ignored pattern to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9a28bffee..6553a67c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ lib dist out node_modules +.idea +.yalc +*.log From ecb4c197f6db41d58c3a7143efafe9ef7c1dffdb Mon Sep 17 00:00:00 2001 From: Bo Zhang Date: Mon, 8 Nov 2021 21:57:07 +0800 Subject: [PATCH 03/64] Support caching readDirectory GH API call --- .../src/interfaces/github-api-rest.ts | 161 +++++++++++++++++- 1 file changed, 158 insertions(+), 3 deletions(-) diff --git a/extensions/github1s/src/interfaces/github-api-rest.ts b/extensions/github1s/src/interfaces/github-api-rest.ts index 9413b2664..7435bfd32 100644 --- a/extensions/github1s/src/interfaces/github-api-rest.ts +++ b/extensions/github1s/src/interfaces/github-api-rest.ts @@ -44,13 +44,17 @@ const handleFileSystemRequestError = (error: RequestError) => { ); }; -export const readGitHubDirectory = ( +export const readGitHubDirectory = async ( owner: string, repo: string, ref: string, path: string, options?: RequestInit ) => { + const cached = await readGitHubDirectoryCached(owner, repo, ref, path); + if (cached) { + return cached; + } return fetch( `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}${encodeFilePath( path @@ -122,17 +126,21 @@ export const getGitHubTagRefs = ( }); }; -export const getGitHubAllFiles = ( +export const getGitHubAllFiles = async ( owner: string, repo: string, ref: string, path: string = '/' ) => { - return fetch( + const ret = await fetch( `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}${encodeFilePath( path ).replace(/^\//, ':')}?recursive=1` ).catch(handleFileSystemRequestError); + if (path === '/') { + populateFileTreeCache(owner, repo, ref, ret); + } + return ret; }; export const getGitHubPulls = ( @@ -212,3 +220,150 @@ export const getGitHubCommitDetail = ( options ); }; + +const repoFileTreeCacheKey = (owner: string, repo: string, ref: string) => { + return `${owner}/${repo}/${ref}`; +}; + +async function readGitHubDirectoryCached( + owner: string, + repo: string, + ref: string, + path: string, + options?: RequestInit +): Promise { + const cacheKey = repoFileTreeCacheKey(owner, repo, ref); + if ( + !repoFileTreeCache.has(cacheKey) || + repoFileTreeCache.get(cacheKey).expireTime < new Date() + ) { + // also populate the cache + await getGitHubAllFiles(owner, repo, ref); + } + const cachedFileTree = repoFileTreeCache.get(cacheKey); + if (cachedFileTree.truncated) { + return null; + } + + const nodeItselfAndChildren = cachedFileTree.fileTree.get( + path.substr(1, path.length - 1) + ); + + const children = nodeItselfAndChildren + .slice(1, nodeItselfAndChildren.length) + .map((child) => { + return path === '/' + ? child + : { + // src/main -> main + path: child.path.substring(path.length, child.path.length), + mode: child.mode, + type: child.type, + sha: child.sha, + url: child.url, + }; + }); + + return { + sha: nodeItselfAndChildren[0].sha, + url: nodeItselfAndChildren[0].url, + truncated: false, + tree: [...children], + }; +} + +const repoFileTreeCache: Map = new Map(); + +function populateFileTreeCache( + owner: string, + repo: string, + ref: string, + jsonObj: any +) { + const cacheKey = repoFileTreeCacheKey(owner, repo, ref); + if (jsonObj.truncated) { + /* + https://docs.github.com/en/rest/reference/git#get-a-tree + If truncated is true in the response then the number of items in the tree array exceeded our maximum limit. + If you need to fetch more items, use the non-recursive method of fetching trees, and fetch one sub-tree at a time. + */ + repoFileTreeCache.set(cacheKey, { + owner, + repo, + ref, + sha: jsonObj.sha, + fileTree: new Map(), + url: jsonObj.url, + truncated: true, + expireTime: new Date(new Date().getTime() + 1000 * 3600 * 24 * 365), + }); + } else { + // for SHA, never expires; otherwise 5 seconds expire time. + const expireTime = /^[0-9a-f]{40}$/.test(ref) + ? new Date(new Date().getTime() + 1000 * 3600 * 24 * 365) + : new Date(new Date().getTime() + 5000); + repoFileTreeCache.set(cacheKey, { + owner, + repo, + ref, + sha: jsonObj.sha, + fileTree: createFileTree(jsonObj), + url: jsonObj.url, + truncated: false, + expireTime, + }); + } +} + +/** + * Key: the file path. "" for root path + * Value: [the node itself, ...the children of the path] + */ +function createFileTree(jsonObj: any): Map { + const all: GitTreeOrBlobObject[] = jsonObj.tree; + const ret = new Map(); + all.forEach((item) => { + const pathSegments = item.path.split('/'); + const parentPath = pathSegments.slice(0, pathSegments.length - 1).join('/'); + + if (ret.has(item.path)) { + ret.set(item.path, [item, ...ret.get(item.path)]); + } else { + ret.set(item.path, [item]); + } + + if (!ret.has(parentPath)) { + ret.set(parentPath, []); + } + ret.get(parentPath).push(item); + }); + // special handling for root node + ret.set('', [ + new GitTreeOrBlobObject('', '', 'tree', jsonObj.sha, jsonObj.url), + ...ret.get(''), + ]); + return ret; +} + +class CacheEntry { + constructor( + readonly owner: string, + readonly repo: string, + readonly ref: string, + readonly sha: string, + readonly fileTree: Map, + readonly url: string, + readonly truncated: boolean, + readonly expireTime: Date + ) {} +} + +class GitTreeOrBlobObject { + constructor( + readonly path: string, + readonly mode: string, + readonly type: string, + readonly sha: string, + readonly url: string + ) {} +} From 3381b9c82899d503ca4efda9378f852d404e78f4 Mon Sep 17 00:00:00 2001 From: Bo Zhang Date: Mon, 8 Nov 2021 21:57:33 +0800 Subject: [PATCH 04/64] Add event listener in index.html --- resources/index.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/index.html b/resources/index.html index 44842ed96..0bc29e3a0 100644 --- a/resources/index.html +++ b/resources/index.html @@ -17,7 +17,7 @@ - GitHub1s + ByteLegend Web Editor + +