diff --git a/exa/language_server_pb/language_server.proto b/exa/language_server_pb/language_server.proto index a324f08..b6e0b19 100644 --- a/exa/language_server_pb/language_server.proto +++ b/exa/language_server_pb/language_server.proto @@ -63,9 +63,13 @@ message DocumentPosition { // Next ID: 9, Previous field: cursor_position. message Document { - string absolute_path = 1; - // Path relative to the root of the workspace. - string relative_path = 2; + // OS specific separators. + string absolute_path_migrate_me_to_uri = 1 [deprecated = true]; + string absolute_uri = 12; + // Path relative to the root of the workspace. Slash separated. + // Leave empty if the document is not in the workspace. + string relative_path_migrate_me_to_workspace_uri = 2 [deprecated = true]; + string workspace_uri = 13; string text = 3; // Language ID provided by the editor. string editor_language = 4 [(validate.rules).string.min_len = 1]; @@ -83,6 +87,13 @@ message Document { "\r\n" ] }]; + + // These fields are not used by the chrome extension. + Range visible_range = 9; + bool is_cutoff_start = 10; + bool is_cutoff_end = 11; + int32 lines_cutoff_start = 14; + int32 lines_cutoff_end = 15; } message ExperimentConfig { diff --git a/generate.js b/generate.js index c7662de..53fabae 100644 --- a/generate.js +++ b/generate.js @@ -3,7 +3,7 @@ const { execSync } = require('child_process'); require('dotenv').config(); if (process.env.CODEIUM_ENV === 'monorepo') { execSync( - 'pnpm buf generate ../../.. --path ../../language_server_pb/language_server.proto --path ../../seat_management_pb/seat_management.proto --include-imports' + 'pnpm buf generate ../../.. --path ../../language_server_pb/language_server.proto --path ../../seat_management_pb/seat_management.proto --path ../../opensearch_clients_pb/opensearch_clients.proto --include-imports' ); } else { execSync('pnpm buf generate'); diff --git a/package.json b/package.json index cbf886b..2252d21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeium-chrome", - "version": "1.12.7", + "version": "1.20.4", "description": "", "license": "MIT", "scripts": { diff --git a/src/codemirror.ts b/src/codemirror.ts index 10d0fb8..1f9ac24 100644 --- a/src/codemirror.ts +++ b/src/codemirror.ts @@ -136,7 +136,7 @@ export class CodeMirrorManager { lineEnding: '\n', // We could use the regular path which could have a drive: prefix, but // this is probably unusual. - relativePath: relativePath, + absoluteUri: `file:///${relativePath}`, }, editorOptions, }); @@ -330,6 +330,12 @@ export class CodeMirrorManager { if (event.key === 'Tab' && event.shiftKey) { return { consumeEvent: false, forceTriggerCompletion }; } + + // TODO(kevin): clean up autoHandle logic + // Currently we have: + // Jupyter Notebook: tab = true, escape = false + // Code Mirror Websites: tab = true, escape = true + // Jupyter Lab: tab = false, escape = false if (!event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey) { if (alsoHandle.tab && event.key === tabKey && this.acceptCompletion()) { return { consumeEvent: true, forceTriggerCompletion }; @@ -340,7 +346,7 @@ export class CodeMirrorManager { // Special case if we are in jupyter notebooks and the tab key has been rebinded. // We do not want to consume the default keybinding, because it triggers the default // jupyter completion. - if (alsoHandle.tab && tabKey != 'Tab' && event.key === 'Tab') { + if (alsoHandle.tab && !alsoHandle.escape && tabKey !== 'Tab' && event.key === 'Tab') { return { consumeEvent: false, forceTriggerCompletion }; } } diff --git a/src/codemirrorInject.ts b/src/codemirrorInject.ts index edf4e13..5b5f926 100644 --- a/src/codemirrorInject.ts +++ b/src/codemirrorInject.ts @@ -6,8 +6,14 @@ declare type CodeMirror = typeof import('codemirror'); export class CodeMirrorState { codeMirrorManager: CodeMirrorManager; docs: CodeMirror.Doc[] = []; + debounceMs: number = 0; hookedEditors = new WeakSet(); - constructor(extensionId: string, cm: CodeMirror | undefined, readonly multiplayer: boolean) { + constructor( + extensionId: string, + cm: CodeMirror | undefined, + readonly multiplayer: boolean, + debounceMs?: number + ) { this.codeMirrorManager = new CodeMirrorManager(extensionId, { ideName: 'codemirror', ideVersion: `${cm?.version ?? 'unknown'}-${window.location.hostname}`, @@ -15,6 +21,7 @@ export class CodeMirrorState { if (cm !== undefined) { cm.defineInitHook(this.editorHook()); } + this.debounceMs = debounceMs ?? 0; } editorHook(): (editor: CodeMirror.Editor) => void { @@ -78,7 +85,7 @@ export class CodeMirrorState { undefined, undefined ); - }); + }, this.debounceMs); }); } } diff --git a/src/common.ts b/src/common.ts index d596d54..36fc58d 100644 --- a/src/common.ts +++ b/src/common.ts @@ -13,9 +13,10 @@ import { } from '../proto/exa/language_server_pb/language_server_pb'; const EXTENSION_NAME = 'chrome'; -const EXTENSION_VERSION = '1.12.7'; +const EXTENSION_VERSION = '1.20.4'; export const CODEIUM_DEBUG = false; +export const DEFAULT_PATH = 'unknown_url'; export interface ClientSettings { apiKey?: string; diff --git a/src/component/Options.tsx b/src/component/Options.tsx index 490b4b3..25cc2e8 100644 --- a/src/component/Options.tsx +++ b/src/component/Options.tsx @@ -155,6 +155,8 @@ const Options = () => { const jupyterNotebookKeybindingAcceptRef = createRef(); const [jupyterNotebookKeybindingAcceptText, setJupyterNotebookKeybindingAcceptText] = useState(''); + const [jupyterDebounceMs, setJupyterDebounceMs] = useState(0); + const jupyterDebounceMsRef = createRef(); useEffect(() => { (async () => { @@ -188,6 +190,11 @@ const Options = () => { })().catch((e) => { console.error(e); }); + (async () => { + setJupyterDebounceMs((await getStorageItem('jupyterDebounceMs')) ?? 0); + })().catch((e) => { + console.error(e); + }); }, []); // TODO(prem): Deduplicate with serviceWorker.ts/storage.ts. const resolvedPortalUrl = useMemo(() => { @@ -405,6 +412,31 @@ const Options = () => { + + Jupyter debounce time + setJupyterDebounceMs(Number(e.target.value))} + /> + + + + ); }; diff --git a/src/jupyterInject.ts b/src/jupyterInject.ts index 7855116..c3b195c 100644 --- a/src/jupyterInject.ts +++ b/src/jupyterInject.ts @@ -1,9 +1,11 @@ import type CodeMirror from 'codemirror'; import { CodeMirrorManager } from './codemirror'; -import { JupyterNotebookKeyBindings } from './common'; +import { DEFAULT_PATH, JupyterNotebookKeyBindings } from './common'; import { EditorOptions } from '../proto/exa/codeium_common_pb/codeium_common_pb'; +// Note: this file only deals with jupyter notebook 6 (not Jupyter Lab) + declare class Cell { code_mirror: CodeMirror.Editor; notebook: Notebook; @@ -63,7 +65,7 @@ class JupyterState { this.keybindings = keybindings; } - patchCellKeyEvent() { + patchCellKeyEvent(debounceMs?: number) { const beforeMainHandler = (doc: CodeMirror.Doc, event: KeyboardEvent) => { return this.codeMirrorManager.beforeMainKeyHandler( doc, @@ -96,7 +98,7 @@ class JupyterState { return; } } - const textModels = []; + const textModels: CodeMirror.Doc[] = []; const editableCells = [...this.notebook.get_cells()]; let currentModelWithOutput; @@ -143,9 +145,10 @@ class JupyterState { const url = window.location.href; // URLs are usually of the form, http://localhost:XXXX/notebooks/path/to/notebook.ipynb - // We only want the path to the notebook. + // We only want path/to/notebook.ipynb + // If the URL is not of this form, we just use "unknown_url" const path = new URL(url).pathname; - const relativePath = path.endsWith('.ipynb') ? path : undefined; + const relativePath = path.endsWith('.ipynb') ? path.replace(/^\//, '') : undefined; await codeMirrorManager.triggerCompletion( true, // isNotebook @@ -156,10 +159,10 @@ class JupyterState { tabSize: BigInt(editor.getOption('tabSize') ?? 4), insertSpaces: !(editor.getOption('indentWithTabs') ?? false), }), - relativePath, - undefined + relativePath ?? DEFAULT_PATH, + undefined // create_disposables ); - }); + }, debounceMs ?? 0); }; }; this.jupyter.CodeCell.prototype.handle_codemirror_keyevent = replaceOriginalHandler( @@ -189,10 +192,11 @@ class JupyterState { export function inject( extensionId: string, jupyter: Jupyter, - keybindings: JupyterNotebookKeyBindings + keybindings: JupyterNotebookKeyBindings, + debounceMs?: number ): JupyterState { const jupyterState = new JupyterState(extensionId, jupyter, keybindings); - jupyterState.patchCellKeyEvent(); + jupyterState.patchCellKeyEvent(debounceMs); jupyterState.patchShortcutManagerHandler(); return jupyterState; } diff --git a/src/jupyterlabPlugin.ts b/src/jupyterlabPlugin.ts index f7978e8..c235dde 100644 --- a/src/jupyterlabPlugin.ts +++ b/src/jupyterlabPlugin.ts @@ -55,17 +55,21 @@ class CodeiumPlugin { codeMirrorManager: CodeMirrorManager; keybindings: Promise; + debounceMs: number; + constructor( readonly extensionId: string, app: JupyterFrontEnd, notebookTracker: INotebookTracker, editorTracker: IEditorTracker, - documentManager: IDocumentManager + documentManager: IDocumentManager, + debounceMs: number ) { this.app = app; this.notebookTracker = notebookTracker; this.editorTracker = editorTracker; this.documentManager = documentManager; + this.debounceMs = debounceMs; this.codeMirrorManager = new CodeMirrorManager(extensionId, { ideName: 'jupyterlab', ideVersion: `${app.name.toLowerCase()} ${app.version}`, @@ -224,7 +228,7 @@ class CodeiumPlugin { return keybindingDisposables; } ); - }); + }, this.debounceMs); void chrome.runtime.sendMessage(this.extensionId, { type: 'success' }); return false; } @@ -232,7 +236,8 @@ class CodeiumPlugin { export function getPlugin( extensionId: string, - jupyterapp: JupyterFrontEnd + jupyterapp: JupyterFrontEnd, + debounceMs: number ): JupyterFrontEndPlugin { return { id: 'codeium:plugin', @@ -244,7 +249,14 @@ export function getPlugin( documentManager: IDocumentManager ) => { // This indirection is necessary to get us a `this` to store state in. - new CodeiumPlugin(extensionId, app, notebookTracker, editorTracker, documentManager); + new CodeiumPlugin( + extensionId, + app, + notebookTracker, + editorTracker, + documentManager, + debounceMs + ); }, requires: [ // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/src/monacoCompletionProvider.ts b/src/monacoCompletionProvider.ts index 803845f..bdc117c 100644 --- a/src/monacoCompletionProvider.ts +++ b/src/monacoCompletionProvider.ts @@ -4,6 +4,7 @@ import { IdeInfo, LanguageServerClient } from './common'; import { getLanguage } from './monacoLanguages'; import { TextAndOffsets, computeTextAndOffsets } from './notebook'; import { numUtf8BytesToNumCodeUnits } from './utf'; +import { sleep } from './utils'; import { Language } from '../proto/exa/codeium_common_pb/codeium_common_pb'; import { CompletionItem, @@ -197,9 +198,11 @@ function colabRelativePath(): string | undefined { return undefined; } if (fileId.source === 'drive') { - return `${fileId.fileId}.ipynb`; + let fileIdString = fileId.fileId; + fileIdString = fileIdString.replace(/^\//, ''); + return `${fileIdString}.ipynb`; } - return fileId.fileId; + return fileId.fileId.replace(/^\//, ''); } function deepnoteAndDatabricksRelativePath(url: string): string | undefined { @@ -219,9 +222,11 @@ function deepnoteAndDatabricksRelativePath(url: string): string | undefined { export class MonacoCompletionProvider implements monaco.languages.InlineCompletionsProvider { modelUriToEditor = new Map(); client: LanguageServerClient; + debounceMs: number; - constructor(readonly extensionId: string, readonly monacoSite: MonacoSite) { + constructor(readonly extensionId: string, readonly monacoSite: MonacoSite, debounceMs: number) { this.client = new LanguageServerClient(extensionId); + this.debounceMs = debounceMs; } getIdeInfo(): IdeInfo { @@ -284,7 +289,11 @@ export class MonacoCompletionProvider implements monaco.languages.InlineCompleti private absolutePath(model: monaco.editor.ITextModel): string | undefined { // Given we are using path, note the docs on fsPath: https://microsoft.github.io/monaco-editor/api/classes/monaco.Uri.html#fsPath - return model.uri.path; + if (this.monacoSite === OMonacoSite.COLAB) { + // The colab absolute path is something like the cell number (i.e. /3) + return colabRelativePath(); + } + return model.uri.path.replace(/^\//, ''); // TODO(prem): Adopt some site-specific convention. } @@ -434,14 +443,15 @@ export class MonacoCompletionProvider implements monaco.languages.InlineCompleti language: getLanguage(getEditorLanguage(model)), cursorOffset: BigInt(numUtf8Bytes), lineEnding: '\n', - relativePath: this.relativePath(), - absolutePath: this.absolutePath(model), + relativePathMigrateMeToWorkspaceUri: this.relativePath(), + absoluteUri: 'file:///' + this.absolutePath(model), }, editorOptions: { tabSize: BigInt(model.getOptions().tabSize), insertSpaces: model.getOptions().insertSpaces, }, }); + await sleep(this.debounceMs ?? 0); const response = await this.client.getCompletions(request); if (response === undefined) { return; diff --git a/src/script.ts b/src/script.ts index f09e751..aedf1ec 100644 --- a/src/script.ts +++ b/src/script.ts @@ -21,7 +21,7 @@ async function getAllowedAndKeybindings( (resolve) => { chrome.runtime.sendMessage( extensionId, - { type: 'allowed_and_keybindings' }, + { type: 'jupyter_notebook_allowed_and_keybindings' }, (response: { allowed: boolean; keyBindings: JupyterNotebookKeyBindings }) => { resolve(response); } @@ -31,6 +31,19 @@ async function getAllowedAndKeybindings( return result; } +async function getDebounceMs(extensionId: string): Promise<{ debounceMs: number }> { + const result = await new Promise<{ debounceMs: number }>((resolve) => { + chrome.runtime.sendMessage( + extensionId, + { type: 'debounce_ms' }, + (response: { debounceMs: number }) => { + resolve(response); + } + ); + }); + return result; +} + // Clear any bad state from another tab. void chrome.runtime.sendMessage(extensionId, { type: 'success' }); @@ -50,7 +63,7 @@ declare global { } // Intercept creation of monaco so we don't have to worry about timing the injection. -const addMonacoInject = () => +const addMonacoInject = (debounceMs: number) => Object.defineProperties(window, { MonacoEnvironment: { get() { @@ -79,7 +92,11 @@ const addMonacoInject = () => } } this._codeium_monaco = _monaco; - const completionProvider = new MonacoCompletionProvider(extensionId, injectMonaco); + const completionProvider = new MonacoCompletionProvider( + extensionId, + injectMonaco, + debounceMs + ); if (!_monaco?.languages?.registerInlineCompletionsProvider) { return; } @@ -108,8 +125,7 @@ const addMonacoInject = () => let injectCodeMirror = false; -const jupyterConfigDataElement = document.getElementById('jupyter-config-data'); -if (jupyterConfigDataElement !== null) { +const addJupyterLabInject = (jupyterConfigDataElement: HTMLElement, debounceMs: number) => { const config = JSON.parse(jupyterConfigDataElement.innerText); config.exposeAppInBrowser = true; jupyterConfigDataElement.innerText = JSON.stringify(config); @@ -120,7 +136,7 @@ if (jupyterConfigDataElement !== null) { }, set: function (_jupyterapp?: JupyterFrontEnd) { if (_jupyterapp?.version.startsWith('3.')) { - const p = getPlugin(extensionId, _jupyterapp); + const p = getPlugin(extensionId, _jupyterapp, debounceMs); _jupyterapp.registerPlugin(p); _jupyterapp.activatePlugin(p.id).then( () => { @@ -130,10 +146,18 @@ if (jupyterConfigDataElement !== null) { console.error(e); } ); + } else if (_jupyterapp?.version.startsWith('4.')) { + void chrome.runtime.sendMessage(extensionId, { + type: 'error', + message: + 'Only JupyterLab 3.x is supported. Use the codeium-jupyter extension for JupyterLab 4', + }); } else { void chrome.runtime.sendMessage(extensionId, { type: 'error', - message: 'Only JupyterLab 3.x is supported', + message: `Codeium: Unexpected JupyterLab version: ${ + _jupyterapp?.version ?? '(unknown)' + }. Only JupyterLab 3.x is supported`, }); } this._codeium_jupyterapp = _jupyterapp; @@ -145,7 +169,7 @@ if (jupyterConfigDataElement !== null) { }, set: function (_jupyterlab?: JupyterFrontEnd) { if (_jupyterlab?.version.startsWith('2.')) { - const p = getPlugin(extensionId, _jupyterlab); + const p = getPlugin(extensionId, _jupyterlab, debounceMs); _jupyterlab.registerPlugin(p); _jupyterlab.activatePlugin(p.id).then( () => { @@ -159,15 +183,18 @@ if (jupyterConfigDataElement !== null) { this._codeium_jupyterlab = _jupyterlab; }, }); -} +}; const SUPPORTED_CODEMIRROR_SITES = [ - { pattern: /https?:\/\/(.*\.)?jsfiddle\.net(\/.*)?/, multiplayer: false }, - { pattern: /https:\/\/(.*\.)?codepen\.io(\/.*)?/, multiplayer: false }, - { pattern: /https:\/\/(.*\.)?codeshare\.io(\/.*)?/, multiplayer: true }, + { name: 'JSFiddle', pattern: /https?:\/\/(.*\.)?jsfiddle\.net(\/.*)?/, multiplayer: false }, + { name: 'CodePen', pattern: /https:\/\/(.*\.)?codepen\.io(\/.*)?/, multiplayer: false }, + { name: 'CodeShare', pattern: /https:\/\/(.*\.)?codeshare\.io(\/.*)?/, multiplayer: true }, ]; -const addCodeMirror5GlobalInject = (keybindings: JupyterNotebookKeyBindings | undefined) => +const addCodeMirror5GlobalInject = ( + keybindings: JupyterNotebookKeyBindings | undefined, + debounceMs: number +) => Object.defineProperty(window, 'CodeMirror', { get: function () { return this._codeium_CodeMirror; @@ -178,36 +205,34 @@ const addCodeMirror5GlobalInject = (keybindings: JupyterNotebookKeyBindings | un return; } if (!cm?.version?.startsWith('5.')) { - console.warn("Codeium doesn't support CodeMirror 6"); + console.warn("Codeium: Codeium doesn't support CodeMirror 6"); return; } // We rely on the fact that the Jupyter variable is defined first. if (Object.prototype.hasOwnProperty.call(this, 'Jupyter')) { injectCodeMirror = true; if (keybindings === undefined) { - console.warn('Codeium found no keybindings for Jupyter Notebook'); + console.warn('Codeium: found no keybindings for Jupyter Notebook'); return; } else { - const jupyterState = jupyterInject(extensionId, this.Jupyter, keybindings); + const jupyterState = jupyterInject(extensionId, this.Jupyter, keybindings, debounceMs); addListeners(cm as CodeMirror, jupyterState.codeMirrorManager); - console.log('Activated Codeium for Jupyter Notebook'); + console.log('Codeium: Activating Jupyter Notebook'); } } else { let multiplayer = false; + let name = ''; for (const pattern of SUPPORTED_CODEMIRROR_SITES) { if (pattern.pattern.test(window.location.href)) { - console.log('Codeium: Activating CodeMirror Site'); + name = pattern.name; injectCodeMirror = true; multiplayer = pattern.multiplayer; break; } - - injectCodeMirror = true; - break; } if (injectCodeMirror) { - new CodeMirrorState(extensionId, cm as CodeMirror, multiplayer); - console.log('Codeium: Activating CodeMirror'); + new CodeMirrorState(extensionId, cm as CodeMirror, multiplayer, debounceMs); + console.log(`Codeium: Activating CodeMirror Site: ${name}`); } } }, @@ -215,6 +240,7 @@ const addCodeMirror5GlobalInject = (keybindings: JupyterNotebookKeyBindings | un // In this case, the CodeMirror 5 editor is accessible as a property of elements // with the class CodeMirror. +// TODO(kevin): Do these still work? const SUPPORTED_CODEMIRROR_NONGLOBAL_SITES = [ { pattern: /https:\/\/console\.paperspace\.com\/.*\/notebook\/.*/, notebook: true }, { pattern: /https?:\/\/www\.codewars\.com(\/.*)?/, notebook: false }, @@ -256,10 +282,11 @@ const addCodeMirror5LocalInject = () => { }, 500); }; -getAllowedAndKeybindings(extensionId).then( - (allowedAndKeybindings) => { +Promise.all([getAllowedAndKeybindings(extensionId), getDebounceMs(extensionId)]).then( + ([allowedAndKeybindings, debounceMs]) => { const allowed = allowedAndKeybindings.allowed; const jupyterKeyBindings = allowedAndKeybindings.keyBindings; + const debounce = debounceMs.debounceMs; const validInjectTypes = ['monaco', 'codemirror5', 'none']; const metaTag = document.querySelector('meta[name="codeium:type"]'); const injectionTypes = @@ -274,12 +301,19 @@ getAllowedAndKeybindings(extensionId).then( return; } + // Inject jupyter lab; this is seperate from the others; this is seperate from the others + const jupyterConfigDataElement = document.getElementById('jupyter-config-data'); + if (jupyterConfigDataElement !== null) { + addJupyterLabInject(jupyterConfigDataElement, debounce); + return; + } + if (injectionTypes.includes('monaco')) { - addMonacoInject(); + addMonacoInject(debounce); } if (injectionTypes.includes('codemirror5')) { - addCodeMirror5GlobalInject(jupyterKeyBindings); + addCodeMirror5GlobalInject(jupyterKeyBindings, debounce); addCodeMirror5LocalInject(); } @@ -287,8 +321,8 @@ getAllowedAndKeybindings(extensionId).then( // if no meta tag is found, check the allowlist if (allowed) { // the url matches the allowlist - addMonacoInject(); - addCodeMirror5GlobalInject(jupyterKeyBindings); + addMonacoInject(debounce); + addCodeMirror5GlobalInject(jupyterKeyBindings, debounce); addCodeMirror5LocalInject(); return; } diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts index c84cbd0..eba68c9 100644 --- a/src/serviceWorker.ts +++ b/src/serviceWorker.ts @@ -68,7 +68,7 @@ chrome.runtime.onInstalled.addListener(async () => { // - request for api key // - set icon and error message chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => { - if (message.type === 'allowed_and_keybindings') { + if (message.type === 'jupyter_notebook_allowed_and_keybindings') { (async () => { // If not allowed, the keybindings can be undefined. let allowed = false; @@ -91,13 +91,13 @@ chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => if (!allowed) { sendResponse({ allowed: false, keyBindings: defaultKeyBindings }); - } - - const keybindings: JupyterNotebookKeyBindings = { - accept: accept ? accept : 'Tab', - }; + } else { + const keybindings: JupyterNotebookKeyBindings = { + accept: accept ? accept : 'Tab', + }; - sendResponse({ allowed: allowed, keyBindings: keybindings }); + sendResponse({ allowed: allowed, keyBindings: keybindings }); + } })().catch((e) => { console.error(e); }); @@ -117,6 +117,15 @@ chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => }); return true; } + if (message.type === 'debounce_ms') { + (async () => { + const { jupyterDebounceMs: jupyterDebounceMs } = await getStorageItems(['jupyterDebounceMs']); + sendResponse({ debounceMs: jupyterDebounceMs ? jupyterDebounceMs : 0 }); + })().catch((e) => { + console.error(e); + }); + return true; + } if (message.type == 'error') { unhealthy(message.message).catch((e) => { console.error(e); diff --git a/src/storage.ts b/src/storage.ts index afc4494..60e7fdc 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -24,6 +24,7 @@ export interface Storage { jupyterlabKeybindingDismiss?: string; jupyterNotebookKeybindingAccept?: string; jupyterNotebookKeybindingDismiss?: string; + jupyterDebounceMs?: number; } // In case the defaults change over time, reconcile the saved setting with the @@ -200,4 +201,5 @@ export const defaultAllowlist = [ /https?:\/\/www\.codewars\.com(\/.*)?/, /https:\/\/(.*\.)?github\.com(\/.*)?/, /http:\/\/(localhost|127\.0\.0\.1):[0-9]+\/.*\.ipynb/, + /https:\/\/(.*\.)?script.google.com(\/.*)?/, ].map((reg) => reg.source); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..0d7f188 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/static/manifest.json b/static/manifest.json index ae3d952..ea4fccc 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -1,7 +1,7 @@ { "name": "Codeium: AI Code Autocompletion on all IDEs", "description": "Your modern coding superpower. Get code completions in Colab and more.", - "version": "1.12.7", + "version": "1.20.4", "manifest_version": 3, "background": { "service_worker": "serviceWorker.js"