UNPKG

@lvce-editor/file-search-worker

Version:

Web Worker for the file search functionality in LVCE Editor.

1,961 lines (1,878 loc) 82.4 kB
const normalizeLine = line => { if (line.startsWith('Error: ')) { return line.slice('Error: '.length); } if (line.startsWith('VError: ')) { return line.slice('VError: '.length); } return line; }; const getCombinedMessage = (error, message) => { const stringifiedError = normalizeLine(`${error}`); if (message) { return `${message}: ${stringifiedError}`; } return stringifiedError; }; const NewLine$2 = '\n'; const getNewLineIndex$1 = (string, startIndex = undefined) => { return string.indexOf(NewLine$2, startIndex); }; const mergeStacks = (parent, child) => { if (!child) { return parent; } const parentNewLineIndex = getNewLineIndex$1(parent); const childNewLineIndex = getNewLineIndex$1(child); if (childNewLineIndex === -1) { return parent; } const parentFirstLine = parent.slice(0, parentNewLineIndex); const childRest = child.slice(childNewLineIndex); const childFirstLine = normalizeLine(child.slice(0, childNewLineIndex)); if (parentFirstLine.includes(childFirstLine)) { return parentFirstLine + childRest; } return child; }; class VError extends Error { constructor(error, message) { const combinedMessage = getCombinedMessage(error, message); super(combinedMessage); this.name = 'VError'; if (error instanceof Error) { this.stack = mergeStacks(this.stack, error.stack); } if (error.codeFrame) { // @ts-ignore this.codeFrame = error.codeFrame; } if (error.code) { // @ts-ignore this.code = error.code; } } } class AssertionError extends Error { constructor(message) { super(message); this.name = 'AssertionError'; } } const Object$1 = 1; const Number$1 = 2; const Array$1 = 3; const String = 4; const Boolean$1 = 5; const Function = 6; const Null = 7; const Unknown = 8; const getType = value => { switch (typeof value) { case 'number': return Number$1; case 'function': return Function; case 'string': return String; case 'object': if (value === null) { return Null; } if (Array.isArray(value)) { return Array$1; } return Object$1; case 'boolean': return Boolean$1; default: return Unknown; } }; const object = value => { const type = getType(value); if (type !== Object$1) { throw new AssertionError('expected value to be of type object'); } }; const number = value => { const type = getType(value); if (type !== Number$1) { throw new AssertionError('expected value to be of type number'); } }; const array = value => { const type = getType(value); if (type !== Array$1) { throw new AssertionError('expected value to be of type array'); } }; const string = value => { const type = getType(value); if (type !== String) { throw new AssertionError('expected value to be of type string'); } }; const isMessagePort = value => { return value && value instanceof MessagePort; }; const isMessagePortMain = value => { return value && value.constructor && value.constructor.name === 'MessagePortMain'; }; const isOffscreenCanvas = value => { return typeof OffscreenCanvas !== 'undefined' && value instanceof OffscreenCanvas; }; const isInstanceOf = (value, constructorName) => { return value?.constructor?.name === constructorName; }; const isSocket = value => { return isInstanceOf(value, 'Socket'); }; const transferrables = [isMessagePort, isMessagePortMain, isOffscreenCanvas, isSocket]; const isTransferrable = value => { for (const fn of transferrables) { if (fn(value)) { return true; } } return false; }; const walkValue = (value, transferrables, isTransferrable) => { if (!value) { return; } if (isTransferrable(value)) { transferrables.push(value); return; } if (Array.isArray(value)) { for (const item of value) { walkValue(item, transferrables, isTransferrable); } return; } if (typeof value === 'object') { for (const property of Object.values(value)) { walkValue(property, transferrables, isTransferrable); } return; } }; const getTransferrables = value => { const transferrables = []; walkValue(value, transferrables, isTransferrable); return transferrables; }; const attachEvents = that => { const handleMessage = (...args) => { const data = that.getData(...args); that.dispatchEvent(new MessageEvent('message', { data })); }; that.onMessage(handleMessage); const handleClose = event => { that.dispatchEvent(new Event('close')); }; that.onClose(handleClose); }; class Ipc extends EventTarget { constructor(rawIpc) { super(); this._rawIpc = rawIpc; attachEvents(this); } } const E_INCOMPATIBLE_NATIVE_MODULE = 'E_INCOMPATIBLE_NATIVE_MODULE'; const E_MODULES_NOT_SUPPORTED_IN_ELECTRON = 'E_MODULES_NOT_SUPPORTED_IN_ELECTRON'; const ERR_MODULE_NOT_FOUND = 'ERR_MODULE_NOT_FOUND'; const NewLine$1 = '\n'; const joinLines$1 = lines => { return lines.join(NewLine$1); }; const RE_AT = /^\s+at/; const RE_AT_PROMISE_INDEX = /^\s*at async Promise.all \(index \d+\)$/; const isNormalStackLine = line => { return RE_AT.test(line) && !RE_AT_PROMISE_INDEX.test(line); }; const getDetails = lines => { const index = lines.findIndex(isNormalStackLine); if (index === -1) { return { actualMessage: joinLines$1(lines), rest: [] }; } let lastIndex = index - 1; while (++lastIndex < lines.length) { if (!isNormalStackLine(lines[lastIndex])) { break; } } return { actualMessage: lines[index - 1], rest: lines.slice(index, lastIndex) }; }; const splitLines$2 = lines => { return lines.split(NewLine$1); }; const RE_MESSAGE_CODE_BLOCK_START = /^Error: The module '.*'$/; const RE_MESSAGE_CODE_BLOCK_END = /^\s* at/; const isMessageCodeBlockStartIndex = line => { return RE_MESSAGE_CODE_BLOCK_START.test(line); }; const isMessageCodeBlockEndIndex = line => { return RE_MESSAGE_CODE_BLOCK_END.test(line); }; const getMessageCodeBlock = stderr => { const lines = splitLines$2(stderr); const startIndex = lines.findIndex(isMessageCodeBlockStartIndex); const endIndex = startIndex + lines.slice(startIndex).findIndex(isMessageCodeBlockEndIndex, startIndex); const relevantLines = lines.slice(startIndex, endIndex); const relevantMessage = relevantLines.join(' ').slice('Error: '.length); return relevantMessage; }; const isModuleNotFoundMessage = line => { return line.includes('[ERR_MODULE_NOT_FOUND]'); }; const getModuleNotFoundError = stderr => { const lines = splitLines$2(stderr); const messageIndex = lines.findIndex(isModuleNotFoundMessage); const message = lines[messageIndex]; return { message, code: ERR_MODULE_NOT_FOUND }; }; const isModuleNotFoundError = stderr => { if (!stderr) { return false; } return stderr.includes('ERR_MODULE_NOT_FOUND'); }; const isModulesSyntaxError = stderr => { if (!stderr) { return false; } return stderr.includes('SyntaxError: Cannot use import statement outside a module'); }; const RE_NATIVE_MODULE_ERROR = /^innerError Error: Cannot find module '.*.node'/; const RE_NATIVE_MODULE_ERROR_2 = /was compiled against a different Node.js version/; const isUnhelpfulNativeModuleError = stderr => { return RE_NATIVE_MODULE_ERROR.test(stderr) && RE_NATIVE_MODULE_ERROR_2.test(stderr); }; const getNativeModuleErrorMessage = stderr => { const message = getMessageCodeBlock(stderr); return { message: `Incompatible native node module: ${message}`, code: E_INCOMPATIBLE_NATIVE_MODULE }; }; const getModuleSyntaxError = () => { return { message: `ES Modules are not supported in electron`, code: E_MODULES_NOT_SUPPORTED_IN_ELECTRON }; }; const getHelpfulChildProcessError = (stdout, stderr) => { if (isUnhelpfulNativeModuleError(stderr)) { return getNativeModuleErrorMessage(stderr); } if (isModulesSyntaxError(stderr)) { return getModuleSyntaxError(); } if (isModuleNotFoundError(stderr)) { return getModuleNotFoundError(stderr); } const lines = splitLines$2(stderr); const { actualMessage, rest } = getDetails(lines); return { message: actualMessage, code: '', stack: rest }; }; class IpcError extends VError { // @ts-ignore constructor(betterMessage, stdout = '', stderr = '') { if (stdout || stderr) { // @ts-ignore const { message, code, stack } = getHelpfulChildProcessError(stdout, stderr); const cause = new Error(message); // @ts-ignore cause.code = code; cause.stack = stack; super(cause, betterMessage); } else { super(betterMessage); } // @ts-ignore this.name = 'IpcError'; // @ts-ignore this.stdout = stdout; // @ts-ignore this.stderr = stderr; } } const readyMessage = 'ready'; const getData$2 = event => { return event.data; }; const listen$7 = () => { // @ts-ignore if (typeof WorkerGlobalScope === 'undefined') { throw new TypeError('module is not in web worker scope'); } return globalThis; }; const signal$8 = global => { global.postMessage(readyMessage); }; class IpcChildWithModuleWorker extends Ipc { getData(event) { return getData$2(event); } send(message) { // @ts-ignore this._rawIpc.postMessage(message); } sendAndTransfer(message) { const transfer = getTransferrables(message); // @ts-ignore this._rawIpc.postMessage(message, transfer); } dispose() { // ignore } onClose(callback) { // ignore } onMessage(callback) { this._rawIpc.addEventListener('message', callback); } } const wrap$f = global => { return new IpcChildWithModuleWorker(global); }; const waitForFirstMessage = async port => { const { resolve, promise } = Promise.withResolvers(); port.addEventListener('message', resolve, { once: true }); const event = await promise; // @ts-ignore return event.data; }; const listen$6 = async () => { const parentIpcRaw = listen$7(); signal$8(parentIpcRaw); const parentIpc = wrap$f(parentIpcRaw); const firstMessage = await waitForFirstMessage(parentIpc); if (firstMessage.method !== 'initialize') { throw new IpcError('unexpected first message'); } const type = firstMessage.params[0]; if (type === 'message-port') { parentIpc.send({ jsonrpc: '2.0', id: firstMessage.id, result: null }); parentIpc.dispose(); const port = firstMessage.params[1]; return port; } return globalThis; }; class IpcChildWithModuleWorkerAndMessagePort extends Ipc { getData(event) { return getData$2(event); } send(message) { this._rawIpc.postMessage(message); } sendAndTransfer(message) { const transfer = getTransferrables(message); this._rawIpc.postMessage(message, transfer); } dispose() { if (this._rawIpc.close) { this._rawIpc.close(); } } onClose(callback) { // ignore } onMessage(callback) { this._rawIpc.addEventListener('message', callback); this._rawIpc.start(); } } const wrap$e = port => { return new IpcChildWithModuleWorkerAndMessagePort(port); }; const IpcChildWithModuleWorkerAndMessagePort$1 = { __proto__: null, listen: listen$6, wrap: wrap$e }; const Two = '2.0'; const create$4$1 = (method, params) => { return { jsonrpc: Two, method, params }; }; const callbacks = Object.create(null); const set$2 = (id, fn) => { callbacks[id] = fn; }; const get$2 = id => { return callbacks[id]; }; const remove = id => { delete callbacks[id]; }; let id = 0; const create$3$1 = () => { return ++id; }; const registerPromise = () => { const id = create$3$1(); const { resolve, promise } = Promise.withResolvers(); set$2(id, resolve); return { id, promise }; }; const create$2$1 = (method, params) => { const { id, promise } = registerPromise(); const message = { jsonrpc: Two, method, params, id }; return { message, promise }; }; class JsonRpcError extends Error { constructor(message) { super(message); this.name = 'JsonRpcError'; } } const NewLine = '\n'; const DomException = 'DOMException'; const ReferenceError$1 = 'ReferenceError'; const SyntaxError$1 = 'SyntaxError'; const TypeError$1 = 'TypeError'; const getErrorConstructor = (message, type) => { if (type) { switch (type) { case DomException: return DOMException; case TypeError$1: return TypeError; case SyntaxError$1: return SyntaxError; case ReferenceError$1: return ReferenceError; default: return Error; } } if (message.startsWith('TypeError: ')) { return TypeError; } if (message.startsWith('SyntaxError: ')) { return SyntaxError; } if (message.startsWith('ReferenceError: ')) { return ReferenceError; } return Error; }; const constructError = (message, type, name) => { const ErrorConstructor = getErrorConstructor(message, type); if (ErrorConstructor === DOMException && name) { return new ErrorConstructor(message, name); } if (ErrorConstructor === Error) { const error = new Error(message); if (name && name !== 'VError') { error.name = name; } return error; } return new ErrorConstructor(message); }; const joinLines = lines => { return lines.join(NewLine); }; const splitLines$1 = lines => { return lines.split(NewLine); }; const getCurrentStack = () => { const currentStack = joinLines(splitLines$1(new Error().stack || '').slice(2)); return currentStack; }; const getNewLineIndex = (string, startIndex = undefined) => { return string.indexOf(NewLine, startIndex); }; const getParentStack = error => { let parentStack = error.stack || error.data || error.message || ''; if (parentStack.startsWith(' at')) { parentStack = error.message + NewLine + parentStack; } return parentStack; }; const MethodNotFound = -32601; const Custom$1 = -32001; const restoreJsonRpcError = error => { const currentStack = getCurrentStack(); if (error && error instanceof Error) { if (typeof error.stack === 'string') { error.stack = error.stack + NewLine + currentStack; } return error; } if (error && error.code && error.code === MethodNotFound) { const restoredError = new JsonRpcError(error.message); const parentStack = getParentStack(error); restoredError.stack = parentStack + NewLine + currentStack; return restoredError; } if (error && error.message) { const restoredError = constructError(error.message, error.type, error.name); if (error.data) { if (error.data.stack && error.data.type && error.message) { restoredError.stack = error.data.type + ': ' + error.message + NewLine + error.data.stack + NewLine + currentStack; } else if (error.data.stack) { restoredError.stack = error.data.stack; } if (error.data.codeFrame) { // @ts-ignore restoredError.codeFrame = error.data.codeFrame; } if (error.data.code) { // @ts-ignore restoredError.code = error.data.code; } if (error.data.type) { // @ts-ignore restoredError.name = error.data.type; } } else { if (error.stack) { const lowerStack = restoredError.stack || ''; // @ts-ignore const indexNewLine = getNewLineIndex(lowerStack); const parentStack = getParentStack(error); // @ts-ignore restoredError.stack = parentStack + lowerStack.slice(indexNewLine); } if (error.codeFrame) { // @ts-ignore restoredError.codeFrame = error.codeFrame; } } return restoredError; } if (typeof error === 'string') { return new Error(`JsonRpc Error: ${error}`); } return new Error(`JsonRpc Error: ${error}`); }; const unwrapJsonRpcResult = responseMessage => { if ('error' in responseMessage) { const restoredError = restoreJsonRpcError(responseMessage.error); throw restoredError; } if ('result' in responseMessage) { return responseMessage.result; } throw new JsonRpcError('unexpected response message'); }; const warn$1 = (...args) => { console.warn(...args); }; const resolve = (id, response) => { const fn = get$2(id); if (!fn) { console.log(response); warn$1(`callback ${id} may already be disposed`); return; } fn(response); remove(id); }; const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND'; const getErrorType = prettyError => { if (prettyError && prettyError.type) { return prettyError.type; } if (prettyError && prettyError.constructor && prettyError.constructor.name) { return prettyError.constructor.name; } return undefined; }; const isAlreadyStack = line => { return line.trim().startsWith('at '); }; const getStack = prettyError => { const stackString = prettyError.stack || ''; const newLineIndex = stackString.indexOf('\n'); if (newLineIndex !== -1 && !isAlreadyStack(stackString.slice(0, newLineIndex))) { return stackString.slice(newLineIndex + 1); } return stackString; }; const getErrorProperty = (error, prettyError) => { if (error && error.code === E_COMMAND_NOT_FOUND) { return { code: MethodNotFound, message: error.message, data: error.stack }; } return { code: Custom$1, message: prettyError.message, data: { stack: getStack(prettyError), codeFrame: prettyError.codeFrame, type: getErrorType(prettyError), code: prettyError.code, name: prettyError.name } }; }; const create$1$1 = (id, error) => { return { jsonrpc: Two, id, error }; }; const getErrorResponse = (id, error, preparePrettyError, logError) => { const prettyError = preparePrettyError(error); logError(error, prettyError); const errorProperty = getErrorProperty(error, prettyError); return create$1$1(id, errorProperty); }; const create$5 = (message, result) => { return { jsonrpc: Two, id: message.id, result: result ?? null }; }; const getSuccessResponse = (message, result) => { const resultProperty = result ?? null; return create$5(message, resultProperty); }; const getErrorResponseSimple = (id, error) => { return { jsonrpc: Two, id, error: { code: Custom$1, // @ts-ignore message: error.message, data: error } }; }; const getResponse = async (message, ipc, execute, preparePrettyError, logError, requiresSocket) => { try { const result = requiresSocket(message.method) ? await execute(message.method, ipc, ...message.params) : await execute(message.method, ...message.params); return getSuccessResponse(message, result); } catch (error) { if (ipc.canUseSimpleErrorResponse) { return getErrorResponseSimple(message.id, error); } return getErrorResponse(message.id, error, preparePrettyError, logError); } }; const defaultPreparePrettyError = error => { return error; }; const defaultLogError = () => { // ignore }; const defaultRequiresSocket = () => { return false; }; const defaultResolve = resolve; // TODO maybe remove this in v6 or v7, only accept options object to simplify the code const normalizeParams = args => { if (args.length === 1) { const options = args[0]; return { ipc: options.ipc, message: options.message, execute: options.execute, resolve: options.resolve || defaultResolve, preparePrettyError: options.preparePrettyError || defaultPreparePrettyError, logError: options.logError || defaultLogError, requiresSocket: options.requiresSocket || defaultRequiresSocket }; } return { ipc: args[0], message: args[1], execute: args[2], resolve: args[3], preparePrettyError: args[4], logError: args[5], requiresSocket: args[6] }; }; const handleJsonRpcMessage = async (...args) => { const options = normalizeParams(args); const { message, ipc, execute, resolve, preparePrettyError, logError, requiresSocket } = options; if ('id' in message) { if ('method' in message) { const response = await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket); try { ipc.send(response); } catch (error) { const errorResponse = getErrorResponse(message.id, error, preparePrettyError, logError); ipc.send(errorResponse); } return; } resolve(message.id, message); return; } if ('method' in message) { await getResponse(message, ipc, execute, preparePrettyError, logError, requiresSocket); return; } throw new JsonRpcError('unexpected message'); }; const invokeHelper = async (ipc, method, params, useSendAndTransfer) => { const { message, promise } = create$2$1(method, params); if (useSendAndTransfer && ipc.sendAndTransfer) { ipc.sendAndTransfer(message); } else { ipc.send(message); } const responseMessage = await promise; return unwrapJsonRpcResult(responseMessage); }; const send = (transport, method, ...params) => { const message = create$4$1(method, params); transport.send(message); }; const invoke$2 = (ipc, method, ...params) => { return invokeHelper(ipc, method, params, false); }; const invokeAndTransfer = (ipc, method, ...params) => { return invokeHelper(ipc, method, params, true); }; const commands = Object.create(null); const register$1 = commandMap => { Object.assign(commands, commandMap); }; const getCommand = key => { return commands[key]; }; const execute$1 = (command, ...args) => { const fn = getCommand(command); if (!fn) { throw new Error(`command not found ${command}`); } return fn(...args); }; const createRpc = ipc => { const rpc = { // @ts-ignore ipc, /** * @deprecated */ send(method, ...params) { send(ipc, method, ...params); }, invoke(method, ...params) { return invoke$2(ipc, method, ...params); }, invokeAndTransfer(method, ...params) { return invokeAndTransfer(ipc, method, ...params); }, async dispose() { await ipc?.dispose(); } }; return rpc; }; const requiresSocket = () => { return false; }; const preparePrettyError = error => { return error; }; const logError = () => { // handled by renderer worker }; const handleMessage = event => { const actualRequiresSocket = event?.target?.requiresSocket || requiresSocket; const actualExecute = event?.target?.execute || execute$1; return handleJsonRpcMessage(event.target, event.data, actualExecute, resolve, preparePrettyError, logError, actualRequiresSocket); }; const handleIpc = ipc => { if ('addEventListener' in ipc) { ipc.addEventListener('message', handleMessage); } else if ('on' in ipc) { // deprecated ipc.on('message', handleMessage); } }; const listen$1 = async (module, options) => { const rawIpc = await module.listen(options); if (module.signal) { module.signal(rawIpc); } const ipc = module.wrap(rawIpc); return ipc; }; const create$4 = async ({ commandMap }) => { // TODO create a commandMap per rpc instance register$1(commandMap); const ipc = await listen$1(IpcChildWithModuleWorkerAndMessagePort$1); handleIpc(ipc); const rpc = createRpc(ipc); return rpc; }; const WebWorkerRpcClient = { __proto__: null, create: create$4 }; const rpcs = Object.create(null); const set$g = (id, rpc) => { rpcs[id] = rpc; }; const get$1 = id => { return rpcs[id]; }; /* eslint-disable @typescript-eslint/explicit-function-return-type */ const create$3 = rpcId => { return { // @ts-ignore invoke(method, ...params) { const rpc = get$1(rpcId); // @ts-ignore return rpc.invoke(method, ...params); }, // @ts-ignore invokeAndTransfer(method, ...params) { const rpc = get$1(rpcId); // @ts-ignore return rpc.invokeAndTransfer(method, ...params); }, set(rpc) { set$g(rpcId, rpc); }, async dispose() { const rpc = get$1(rpcId); await rpc.dispose(); } }; }; const RendererWorker$1 = 1; const { invoke: invoke$3, set: set$3} = create$3(RendererWorker$1); const setFocus$2 = key => { return invoke$3('Focus.setFocus', key); }; const getFileIcon$1 = async options => { return invoke$3('IconTheme.getFileIcon', options); }; const getFolderIcon$1 = async options => { return invoke$3('IconTheme.getFolderIcon', options); }; const closeWidget$2 = async widgetId => { return invoke$3('Viewlet.closeWidget', widgetId); }; const openUri$2 = async (uri, focus, options) => { await invoke$3('Main.openUri', uri, focus, options); }; const showErrorDialog$2 = async errorInfo => { // @ts-ignore await invoke$3('ErrorHandling.showErrorDialog', errorInfo); }; const RendererWorker = { __proto__: null, closeWidget: closeWidget$2, getFileIcon: getFileIcon$1, getFolderIcon: getFolderIcon$1, invoke: invoke$3, openUri: openUri$2, set: set$3, setFocus: setFocus$2, showErrorDialog: showErrorDialog$2}; const { invoke: invoke$1, set: set$1, setFocus: setFocus$1, closeWidget: closeWidget$1, showErrorDialog: showErrorDialog$1, openUri: openUri$1, getFileIcon, getFolderIcon } = RendererWorker; const closeWidget = async id => { // @ts-ignore await closeWidget$1(id); }; const close = async state => { await closeWidget(state.uid); return state; }; const User = 1; const Script = 2; const minimumSliderSize = 20; const Default$1 = 0; const Finished = 2; const create$2 = () => { const states = Object.create(null); return { get(uid) { return states[uid]; }, set(uid, oldState, newState) { states[uid] = { oldState, newState }; }, dispose(uid) { delete states[uid]; }, getKeys() { return Object.keys(states).map(key => { return Number.parseInt(key); }); }, clear() { for (const key of Object.keys(states)) { delete states[key]; } }, wrapCommand(fn) { const wrapped = async (uid, ...args) => { const { newState } = states[uid]; const newerState = await fn(newState, ...args); if (newState === newerState) { return; } const latest = states[uid]; states[uid] = { oldState: latest.oldState, newState: newerState }; }; return wrapped; }, diff(uid, modules, numbers) { const { oldState, newState } = states[uid]; const diffResult = []; for (let i = 0; i < modules.length; i++) { const fn = modules[i]; if (!fn(oldState, newState)) { diffResult.push(numbers[i]); } } return diffResult; } }; }; const { get, set, dispose: dispose$1, wrapCommand } = create$2(); const create$1 = ({ itemHeight, headerHeight = 0, minimumSliderSize = 20 }) => { return { deltaY: 0, minLineY: 0, maxLineY: 0, finalDeltaY: 0, itemHeight, headerHeight, items: [], minimumSliderSize, focusedIndex: -1, touchOffsetY: 0, touchTimeStamp: 0, touchDifference: 0, scrollBarHeight: 0, scrollBarActive: false }; }; const getListHeight$1 = (height, headerHeight) => { if (headerHeight) { return height - headerHeight; } return headerHeight; }; const setDeltaY = (state, deltaY) => { object(state); number(deltaY); const { itemHeight, items, height, headerHeight } = state; const listHeight = getListHeight$1(height, headerHeight); const itemsLength = items.length; const finalDeltaY = itemsLength * itemHeight - listHeight; if (deltaY < 0) { deltaY = 0; } else if (deltaY > finalDeltaY) { deltaY = Math.max(finalDeltaY, 0); } if (state.deltaY === deltaY) { return state; } const minLineY = Math.round(deltaY / itemHeight); const maxLineY = minLineY + Math.round(listHeight / itemHeight); number(minLineY); number(maxLineY); return { ...state, deltaY, minLineY, maxLineY }; }; const handleWheel = (state, deltaMode, deltaY) => { object(state); number(deltaMode); number(deltaY); return setDeltaY(state, state.deltaY + deltaY); }; const create = (uid, uri, listItemHeight, x, y, width, height, platform, args, workspaceUri) => { const state = { workspaceUri, uid, icons: [], state: Default$1, picks: [], recentPicks: [], recentPickIds: Object.create(null), versionId: 0, warned: [], maxVisibleItems: 10, uri, cursorOffset: 0, height: 300, top: 50, width: 600, ...create$1({ itemHeight: listItemHeight, headerHeight: 38, minimumSliderSize: minimumSliderSize }), inputSource: User, args, focused: false, platform, value: '', fileIconCache: Object.create(null) }; set(uid, state, state); }; const RenderItems = 1; const RenderFocus = 2; const RenderValue = 3; const RenderCursorOffset = 7; const RenderFocusedIndex = 8; const Height = 9; const diffType$4 = RenderFocus; const isEqual$4 = (oldState, newState) => { return oldState.focused === newState.focused; }; const diffType$3 = RenderFocusedIndex; const isEqual$3 = (oldState, newState) => { return oldState.focusedIndex === newState.focusedIndex; }; const diffType$2 = Height; const isEqual$2 = (oldState, newState) => { return oldState.items.length === newState.items.length; }; const diffType$1 = RenderItems; const isEqual$1 = (oldState, newState) => { return oldState.items === newState.items && oldState.minLineY === newState.minLineY && oldState.maxLineY === newState.maxLineY && oldState.focusedIndex === newState.focusedIndex; }; const diffType = RenderValue; const isEqual = (oldState, newState) => { return newState.inputSource === User || oldState.value === newState.value; }; const modules = [isEqual$2, isEqual$1, isEqual, isEqual$3, isEqual$4]; const numbers = [diffType$2, diffType$1, diffType, diffType$3, diffType$4]; const diff = (oldState, newState) => { const diffResult = []; for (let i = 0; i < modules.length; i++) { const fn = modules[i]; if (!fn(oldState, newState)) { diffResult.push(numbers[i]); } } return diffResult; }; const diff2 = uid => { const { oldState, newState } = get(uid); return diff(oldState, newState); }; const dispose = uid => { dispose$1(uid); }; const setColorTheme = id => { return invoke$1(/* ColorTheme.setColorTheme */'ColorTheme.setColorTheme', /* colorThemeId */id); }; const focusPick$1 = async pick => { const label = pick.label; await setColorTheme(/* colorThemeId */label); }; const ColorTheme$1 = 0; const Commands$1 = 1; const Custom = 2; const File$2 = 3; const GoToLine$2 = 4; const Help$2 = 5; const Recent$1 = 6; const Symbol$2 = 7; const View$3 = 8; const WorkspaceSymbol$2 = 9; const EveryThing$1 = 100; const noop$2 = async () => {}; const getFn$3 = id => { switch (id) { case ColorTheme$1: return focusPick$1; default: return noop$2; } }; const focusPick = (id, pick) => { const fn = getFn$3(id); return fn(pick); }; const getIconsCached = (paths, fileIconCache) => { return paths.map(path => fileIconCache[path]); }; const getMissingIconRequests = (dirents, fileIconCache) => { const missingRequests = []; for (const dirent of dirents) { if (!dirent.path) { continue; } if (!(dirent.path in fileIconCache)) { missingRequests.push({ type: dirent.type, name: dirent.name, path: dirent.path }); } } return missingRequests; }; const None$2 = 0; const Directory = 3; const File$1 = 7; const requestFileIcon = async request => { if (!request.name) { return ''; } return request.type === File$1 ? getFileIcon({ name: request.name }) : getFolderIcon({ name: request.name }); }; const requestFileIcons = async requests => { const promises = requests.map(requestFileIcon); return Promise.all(promises); }; const updateIconCache = (iconCache, missingRequests, newIcons) => { if (missingRequests.length === 0) { return iconCache; } const newFileIconCache = { ...iconCache }; for (let i = 0; i < missingRequests.length; i++) { const request = missingRequests[i]; const icon = newIcons[i]; newFileIconCache[request.path] = icon; } return newFileIconCache; }; const getPath = dirent => { return dirent.path; }; const toDirent = pick => { const dirent = { type: pick.direntType, name: pick.label, path: pick.uri }; return dirent; }; const getQuickPickFileIcons = async (items, fileIconCache) => { const dirents = items.map(toDirent); const missingRequests = getMissingIconRequests(dirents, fileIconCache); const newIcons = await requestFileIcons(missingRequests); const newFileIconCache = updateIconCache(fileIconCache, missingRequests, newIcons); const paths = dirents.map(getPath); const icons = getIconsCached(paths, newFileIconCache); return { icons, newFileIconCache }; }; const focusIndex = async (state, index) => { const { providerId, maxVisibleItems, items, minLineY, maxLineY, fileIconCache } = state; await focusPick(providerId, items[index]); if (index < minLineY + 1) { const minLineY = index; const maxLineY = Math.min(index + maxVisibleItems, items.length - 1); const sliced = items.slice(minLineY, maxLineY); const { newFileIconCache, icons } = await getQuickPickFileIcons(sliced, fileIconCache); // TODO need to scroll up return { ...state, minLineY, maxLineY, focusedIndex: index, icons, fileIconCache: newFileIconCache }; } if (index >= maxLineY - 1) { // TODO need to scroll down const maxLineY = index + 1; const minLineY = Math.max(maxLineY - maxVisibleItems, 0); const sliced = items.slice(minLineY, maxLineY); const { newFileIconCache, icons } = await getQuickPickFileIcons(sliced, fileIconCache); return { ...state, minLineY, maxLineY, focusedIndex: index, fileIconCache: newFileIconCache, icons }; } const sliced = items.slice(minLineY, maxLineY); const { newFileIconCache, icons } = await getQuickPickFileIcons(sliced, fileIconCache); return { ...state, focusedIndex: index, fileIconCache: newFileIconCache, icons }; }; const first = () => { return 0; }; const last = items => { return items.length - 1; }; const next = (items, index) => { return (index + 1) % items.length; }; const previous = (items, index) => { return index === 0 ? items.length - 1 : index - 1; }; const focusFirst = state => { return focusIndex(state, first()); }; const focusLast = state => { const { items } = state; return focusIndex(state, last(items)); }; const focusNext = state => { const { items, focusedIndex } = state; const nextIndex = next(items, focusedIndex); return focusIndex(state, nextIndex); }; const focusPrevious = state => { const { items, focusedIndex } = state; const previousIndex = previous(items, focusedIndex); return focusIndex(state, previousIndex); }; const commandIds = ['close', 'dispose', 'focusFirst', 'focusIndex', 'focusLast', 'focusNext', 'focusPrevious', 'handleBeforeInput', 'handleBlur', 'handleClickAt', 'diff2', 'handleFocus', 'handleInput', 'handleWheel', 'renderEventListeners', 'selectCurrentIndex', 'selectIndex', 'selectItem', 'setValue']; const getCommandIds = () => { return commandIds; }; const Enter = 3; const Escape = 8; const PageUp = 10; const PageDown = 11; const UpArrow = 14; const DownArrow = 16; const FocusQuickPickInput = 20; const getKeyBindings = () => { return [{ key: Escape, command: 'Viewlet.closeWidget', args: ['QuickPick'], when: FocusQuickPickInput }, { key: UpArrow, command: 'QuickPick.focusPrevious', when: FocusQuickPickInput }, { key: DownArrow, command: 'QuickPick.focusNext', when: FocusQuickPickInput }, { key: PageUp, command: 'QuickPick.focusFirst', when: FocusQuickPickInput }, { key: PageDown, command: 'QuickPick.focusLast', when: FocusQuickPickInput }, { key: Enter, command: 'QuickPick.selectCurrentIndex', when: FocusQuickPickInput }]; }; const getNewValueDeleteContentBackward = (value, selectionStart, selectionEnd, data) => { const after = value.slice(selectionEnd); if (selectionStart === selectionEnd) { const before = value.slice(0, selectionStart - 1); const newValue = before + after; return { newValue, cursorOffset: before.length }; } const before = value.slice(0, selectionStart); const newValue = before + after; return { newValue, cursorOffset: selectionStart }; }; const getNewValueDeleteContentForward = (value, selectionStart, selectionEnd, data) => { const before = value.slice(0, selectionStart); if (selectionStart === selectionEnd) { const after = value.slice(selectionEnd + 1); const newValue = before + after; return { newValue, cursorOffset: selectionStart }; } const after = value.slice(selectionEnd); const newValue = before + after; return { newValue, cursorOffset: selectionStart }; }; const RE_ALPHA_NUMERIC = /[a-z\d]/i; const isAlphaNumeric = character => { return RE_ALPHA_NUMERIC.test(character); }; const getNewValueDeleteWordBackward = (value, selectionStart, selectionEnd, data) => { const after = value.slice(selectionEnd); if (selectionStart === selectionEnd) { let startIndex = Math.max(selectionStart - 1, 0); while (startIndex > 0 && isAlphaNumeric(value[startIndex])) { startIndex--; } const before = value.slice(0, startIndex); const newValue = before + after; return { newValue, cursorOffset: before.length }; } const before = value.slice(0, selectionStart); const newValue = before + after; return { newValue, cursorOffset: selectionStart }; }; const getNewValueDeleteWordForward = (value, selectionStart, selectionEnd, data) => { const before = value.slice(0, selectionStart); if (selectionStart === selectionEnd) { let startIndex = Math.min(selectionStart + 1, value.length - 1); while (startIndex < value.length && isAlphaNumeric(value[startIndex])) { startIndex++; } const after = value.slice(startIndex); const newValue = before + after; return { newValue, cursorOffset: before.length }; } const after = value.slice(selectionEnd); const newValue = before + after; return { newValue, cursorOffset: selectionStart }; }; const getNewValueInsertText = (value, selectionStart, selectionEnd, data) => { if (selectionStart === value.length) { const newValue = value + data; return { newValue, cursorOffset: newValue.length }; } const before = value.slice(0, selectionStart); const after = value.slice(selectionEnd); const newValue = before + data + after; return { newValue, cursorOffset: selectionStart + data.length }; }; const getNewValueInsertCompositionText = (value, selectionStart, selectionEnd, data) => { return getNewValueInsertText(value, selectionStart, selectionEnd, data); }; const getNewValueInsertLineBreak = (value, selectionStart, selectionEnd, data) => { return { newValue: value, cursorOffset: selectionEnd }; }; const InsertText = 'insertText'; const DeleteContentBackward = 'deleteContentBackward'; const DeleteContentForward = 'deleteContentForward'; const DeleteWordForward = 'deleteWordForward'; const DeleteWordBackward = 'deleteWordBackward'; const InsertLineBreak = 'insertLineBreak'; const InsertCompositionText = 'insertCompositionText'; const InsertFromPaste = 'insertFromPaste'; const getNewValueFunction = inputType => { switch (inputType) { case InsertFromPaste: case InsertText: return getNewValueInsertText; case DeleteContentBackward: return getNewValueDeleteContentBackward; case DeleteContentForward: return getNewValueDeleteContentForward; case DeleteWordForward: return getNewValueDeleteWordForward; case DeleteWordBackward: return getNewValueDeleteWordBackward; case InsertLineBreak: return getNewValueInsertLineBreak; case InsertCompositionText: return getNewValueInsertCompositionText; default: throw new Error(`unsupported input type ${inputType}`); } }; const getNewValue = (value, inputType, data, selectionStart, selectionEnd) => { const fn = getNewValueFunction(inputType); return fn(value, selectionStart, selectionEnd, data); }; const Diagonal = 1; const Left = 2; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const createTable = size => { const table = []; for (let i = 0; i < size; i++) { const row = new Uint8Array(size); table.push(row); } return table; }; const EmptyMatches = []; const Dash = '-'; const Dot = '.'; const EmptyString = ''; const Space = ' '; const Underline = '_'; const T = 't'; const isLowerCase = char => { return char === char.toLowerCase(); }; const isUpperCase = char => { return char === char.toUpperCase(); }; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const isGap = (columnCharBefore, columnChar) => { switch (columnCharBefore) { case Dash: case Underline: case EmptyString: case T: case Space: case Dot: return true; } if (isLowerCase(columnCharBefore) && isUpperCase(columnChar)) { return true; } return false; }; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const getScore = (rowCharLow, rowChar, columnCharBefore, columnCharLow, columnChar, isDiagonalMatch) => { if (rowCharLow !== columnCharLow) { return -1; } const isMatch = rowChar === columnChar; if (isMatch) { if (isDiagonalMatch) { return 8; } if (isGap(columnCharBefore, columnChar)) { return 8; } return 5; } if (isGap(columnCharBefore, columnChar)) { return 8; } return 5; }; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const isPatternInWord = (patternLow, patternPos, patternLen, wordLow, wordPos, wordLen) => { while (patternPos < patternLen && wordPos < wordLen) { if (patternLow[patternPos] === wordLow[wordPos]) { patternPos += 1; } wordPos += 1; } return patternPos === patternLen; // pattern must be exhausted }; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const traceHighlights = (table, arrows, patternLength, wordLength) => { let row = patternLength; let column = wordLength; const matches = []; while (row >= 1 && column >= 1) { const arrow = arrows[row][column]; if (arrow === Left) { column--; } else if (arrow === Diagonal) { row--; column--; const start = column + 1; while (row >= 1 && column >= 1) { const arrow = arrows[row][column]; if (arrow === Left) { break; } if (arrow === Diagonal) { row--; column--; } } const end = column; matches.unshift(end, start); } } matches.unshift(table[patternLength][wordLength - 1]); return matches; }; // based on https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/src/vs/base/common/filters.ts by Microsoft (License MIT) const gridSize = 128; const table = createTable(gridSize); const arrows = createTable(gridSize); const fuzzySearch = (pattern, word) => { const patternLength = Math.min(pattern.length, gridSize - 1); const wordLength = Math.min(word.length, gridSize - 1); const patternLower = pattern.toLowerCase(); const wordLower = word.toLowerCase(); if (!isPatternInWord(patternLower, 0, patternLength, wordLower, 0, wordLength)) { return EmptyMatches; } let strongMatch = false; for (let row = 1; row < patternLength + 1; row++) { const rowChar = pattern[row - 1]; const rowCharLow = patternLower[row - 1]; for (let column = 1; column < wordLength + 1; column++) { const columnChar = word[column - 1]; const columnCharLow = wordLower[column - 1]; const columnCharBefore = word[column - 2] || ''; const isDiagonalMatch = arrows[row - 1][column - 1] === Diagonal; const score = getScore(rowCharLow, rowChar, columnCharBefore, columnCharLow, columnChar, isDiagonalMatch); if (row === 1 && score > 5) { strongMatch = true; } let diagonalScore = score + table[row - 1][column - 1]; if (isDiagonalMatch && score !== -1) { diagonalScore += 2; } const leftScore = table[row][column - 1]; if (leftScore > diagonalScore) { table[row][column] = leftScore; arrows[row][column] = Left; } else { table[row][column] = diagonalScore; arrows[row][column] = Diagonal; } } } if (!strongMatch) { return EmptyMatches; } const highlights = traceHighlights(table, arrows, patternLength, wordLength); return highlights; }; const filterQuickPickItem = (pattern, word) => { const matches = fuzzySearch(pattern, word); return matches; }; const filterQuickPickItems = (items, value) => { if (!value) { return items; } const results = []; for (const item of items) { const filterValue = item.label; const matches = filterQuickPickItem(value, filterValue); if (matches.length > 0) { results.push({ ...item, matches }); } } return results; }; const Command = '>'; const Symbol$1 = '@'; const WorkspaceSymbol$1 = '#'; const GoToLine$1 = ':'; const View$2 = 'view '; const None$1 = ''; const Help$1 = '?'; const getQuickPickPrefix = value => { if (value.startsWith(Command)) { return Command; } if (value.startsWith(Symbol$1)) { return Symbol$1; } if (value.startsWith(WorkspaceSymbol$1)) { return WorkspaceSymbol$1; } if (value.startsWith(GoToLine$1)) { return GoToLine$1; } if (value.startsWith(View$2)) { return View$2; } return None$1; }; const noop$1 = value => { return value; }; const getFilterValueEverything = value => { const prefix = getQuickPickPrefix(value); const prefixLength = prefix.length; return value.slice(prefixLength).trim(); }; const getFn$2 = id => { switch (id) { case EveryThing$1: return getFilterValueEverything; default: return noop$1; } }; const getFilterValue = (id, value) => { const fn = getFn$2(id); const filterValue = fn(value); return filterValue; }; const getFinalDeltaY = (height, itemHeight, itemsLength) => { const contentHeight = itemsLength * itemHeight; const finalDeltaY = Math.max(contentHeight - height, 0); return finalDeltaY; }; const getListHeight = (itemsLength, itemHeight, maxHeight) => { number(itemsLength); number(itemHeight); number(maxHeight); if (itemsLength === 0) { return itemHeight; } const totalHeight = itemsLength * itemHeight; return Math.min(totalHeight, maxHeight); }; const getColorThemeNames = async () => { return invoke$1(/* Ajax.getJson */'ColorTheme.getColorThemeNames'); }; const toProtoVisibleItem$2 = name => { const pick = { label: name, description: '', fileIcon: '', icon: '', matches: [], direntType: 0, uri: '' }; return pick; }; const getPicks$c = async searchValue => { const colorThemeNames = await getColorThemeNames(); const picks = colorThemeNames.map(toProtoVisibleItem$2); return picks; }; const handleError = async (error, notify = true, prefix = '') => { console.error(error); }; const showErrorDialog = async error => { const code = error.code; const message = error.message; const stack = error.stack; const name = error.name; const errorInfo = { code, message, stack, name }; await showErrorDialog$1(errorInfo); }; const warn = (...args) => { console.warn(...args); }; const state$2 = { menuEntries: [] }; const getAll = () => { return state$2.menuEntries; }; const add = menuEntries => { state$2.menuEntries = [...state$2.menuEntries, ...menuEntries]; }; // TODO combine Ajax with cache (specify strategy: cacheFirst, networkFirst) const getBuiltinPicks = async () => { const builtinPicks = getAll(); return builtinPicks; }; const prefixIdWithExt = item => { if (!item.label) { warn('[QuickPick] item has missing label', item); } if (!item.id) { warn('[QuickPick] item has missing id', item); } return { ...item, id: `ext.${item.id}`, label: item.label || item.id }; }; const getExtensionPicks = async () => { try { // TODO don't call this every time const extensionPicks = await invoke$1('ExtensionHost.getCommands'); if (!extensionPicks) { return []; } const mappedPicks = extensionPicks.map(prefixIdWithExt); return mappedPicks; } catch (error) { console.error(`Failed to get extension picks: ${error}`); return []; } }; const toProtoVisibleItem$1 = item => { const pick = { label: item.label, description: '', fileIcon: '', icon: '', matches: [], direntType: 0, uri: '' }; // @ts-ignore pick.id = item.id; // @ts-ignore pick.args =