Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
[![Twitter Follow](https://img.shields.io/badge/style--blue?style=social&logo=twitter&label=Follow%20%40codeiumdev)](https://twitter.com/intent/follow?screen_name=codeiumdev)
![License](https://img.shields.io/github/license/Exafunction/codeium-chrome)
[![Docs](https://img.shields.io/badge/Codeium%20Docs-09B6A2)](https://docs.codeium.com)
[![Canny Board](https://img.shields.io/badge/Feature%20Requests-6b69ff)](https://codeium.canny.io/feature-requests/)
[![built with Codeium](https://codeium.com/badges/main)](https://codeium.com?repo_name=exafunction%2Fcodeium.vim)

[![Visual Studio](https://img.shields.io/visual-studio-marketplace/i/Codeium.codeium?label=Visual%20Studio&logo=visualstudio)](https://marketplace.visualstudio.com/items?itemName=Codeium.codeium)
[![JetBrains](https://img.shields.io/jetbrains/plugin/d/20540?label=JetBrains)](https://plugins.jetbrains.com/plugin/20540-codeium/)
Expand Down
4 changes: 2 additions & 2 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
version: v2
plugins:
- local: node_modules/@bufbuild/protoc-gen-es/bin/protoc-gen-es
- local: [node, node_modules/@bufbuild/protoc-gen-es/bin/protoc-gen-es]
out: proto
opt:
- target=ts
- import_extension=none
- local: node_modules/@connectrpc/protoc-gen-connect-es/bin/protoc-gen-connect-es
- local: [node, node_modules/@connectrpc/protoc-gen-connect-es/bin/protoc-gen-connect-es]
out: proto
opt:
- target=ts
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codeium-chrome",
"version": "1.20.4",
"version": "1.26.3",
"description": "",
"license": "MIT",
"scripts": {
Expand Down
61 changes: 37 additions & 24 deletions src/codemirror.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IDisposable } from '@lumino/disposable';
import type CodeMirror from 'codemirror';

import { editorLanguage, language } from './codemirrorLanguages';
import { CODEIUM_DEBUG, IdeInfo, LanguageServerClient } from './common';
import { CODEIUM_DEBUG, IdeInfo, KeyCombination, LanguageServerClient } from './common';
import { TextAndOffsets, computeTextAndOffsets } from './notebook';
import { numUtf8BytesToNumCodeUnits } from './utf';
import { EditorOptions } from '../proto/exa/codeium_common_pb/codeium_common_pb';
Expand Down Expand Up @@ -308,48 +308,61 @@ export class CodeMirrorManager {
doc: CodeMirror.Doc,
event: KeyboardEvent,
alsoHandle: { tab: boolean; escape: boolean },
tabKey: string = 'Tab'
keyCombination?: KeyCombination
): { consumeEvent: boolean | undefined; forceTriggerCompletion: boolean } {
let forceTriggerCompletion = false;
if (event.ctrlKey) {
if (event.key === ' ') {
forceTriggerCompletion = true;
} else {
return { consumeEvent: false, forceTriggerCompletion };
}
if (event.ctrlKey && event.key === ' ') {
forceTriggerCompletion = true;
}

// Classic notebook may autocomplete these.
if ('"\')}]'.includes(event.key)) {
forceTriggerCompletion = true;
}

if (event.isComposing) {
this.clearCompletion('composing');
return { consumeEvent: false, forceTriggerCompletion };
}
// Shift-tab in jupyter notebooks shows documentation.
if (event.key === 'Tab' && event.shiftKey) {
return { consumeEvent: false, forceTriggerCompletion };

// Accept completion logic
if (keyCombination) {
const matchesKeyCombination =
event.key.toLowerCase() === keyCombination.key.toLowerCase() &&
!!event.ctrlKey === !!keyCombination.ctrl &&
!!event.altKey === !!keyCombination.alt &&
!!event.shiftKey === !!keyCombination.shift &&
!!event.metaKey === !!keyCombination.meta;

if (matchesKeyCombination && this.acceptCompletion()) {
return { consumeEvent: true, 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 };
}
if (alsoHandle.escape && event.key === 'Escape' && this.clearCompletion('user dismissed')) {
return { consumeEvent: true, forceTriggerCompletion };
}
// 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 && !alsoHandle.escape && tabKey !== 'Tab' && event.key === 'Tab') {
return { consumeEvent: false, forceTriggerCompletion };
}

// Code Mirror Websites only
if (
!event.metaKey &&
!event.ctrlKey &&
!event.altKey &&
!event.shiftKey &&
alsoHandle.escape &&
event.key === 'Escape' &&
this.clearCompletion('user dismissed')
) {
return { consumeEvent: true, forceTriggerCompletion };
}

// Shift-tab in jupyter notebooks shows documentation.
if (event.key === 'Tab' && event.shiftKey) {
return { consumeEvent: false, forceTriggerCompletion };
}

switch (event.key) {
case 'Delete':
case 'ArrowDown':
Expand Down
3 changes: 2 additions & 1 deletion src/codemirrorInject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export class CodeMirrorState {
const { consumeEvent, forceTriggerCompletion } = this.codeMirrorManager.beforeMainKeyHandler(
editor.getDoc(),
event,
{ tab: true, escape: true }
{ tab: true, escape: true },
{ key: 'Tab', ctrl: false, alt: false, shift: false, meta: false }
);
if (consumeEvent !== undefined) {
if (consumeEvent) {
Expand Down
16 changes: 12 additions & 4 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../proto/exa/language_server_pb/language_server_pb';

const EXTENSION_NAME = 'chrome';
const EXTENSION_VERSION = '1.20.4';
const EXTENSION_VERSION = '1.26.3';

export const CODEIUM_DEBUG = false;
export const DEFAULT_PATH = 'unknown_url';
Expand All @@ -23,13 +23,21 @@ export interface ClientSettings {
defaultModel?: string;
}

export interface KeyCombination {
key: string;
ctrl?: boolean;
alt?: boolean;
shift?: boolean;
meta?: boolean;
}

export interface JupyterLabKeyBindings {
accept: string;
dismiss: string;
accept: KeyCombination;
dismiss: KeyCombination;
}

export interface JupyterNotebookKeyBindings {
accept: string;
accept: KeyCombination;
}

async function getClientSettings(): Promise<ClientSettings> {
Expand Down
177 changes: 102 additions & 75 deletions src/component/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,52 @@ const Options = () => {
const [portalUrlText, setPortalUrlText] = useState('');
const modelRef = createRef<HTMLInputElement>();
const [modelText, setModelText] = useState('');
const jupyterlabKeybindingAcceptRef = createRef<HTMLInputElement>();
const [jupyterlabKeybindingAcceptText, setJupyterlabKeybindingAcceptText] = useState('');
const jupyterlabKeybindingDismissRef = createRef<HTMLInputElement>();
const [jupyterlabKeybindingDismissText, setJupyterlabKeybindingDismissText] = useState('');
const jupyterNotebookKeybindingAcceptRef = createRef<HTMLInputElement>();
const [jupyterNotebookKeybindingAcceptText, setJupyterNotebookKeybindingAcceptText] =
useState('');
const [jupyterDebounceMs, setJupyterDebounceMs] = useState(0);
const jupyterDebounceMsRef = createRef<HTMLInputElement>();
const [currentKey, setCurrentKey] = useState({
key: '',
ctrl: false,
alt: false,
shift: false,
meta: false,
});
const [jupyterlabAcceptInput, setJupyterlabAcceptInput] = useState(false);
const [jupyterlabDismissInput, setJupyterlabDismissInput] = useState(false);
const [notebookAcceptInput, setNotebookAcceptInput] = useState(false);

const formatKeyCombination = (key: any) => {
const modifiers = [];
if (key.ctrl) modifiers.push('Ctrl');
if (key.alt) modifiers.push('Alt');
if (key.shift) modifiers.push('Shift');
if (key.meta) modifiers.push('Meta');
return [...modifiers, key.key.toUpperCase()].join('+');
};

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault();
const key = e.key;
if (key !== 'Control' && key !== 'Alt' && key !== 'Shift' && key !== 'Meta') {
const ctrl = e.ctrlKey;
const alt = e.altKey;
const shift = e.shiftKey;
const meta = e.metaKey;
setCurrentKey({ key, ctrl, alt, shift, meta });

// Force blur using setTimeout to ensure it happens after state update
setTimeout(() => {
if (e.currentTarget) {
e.currentTarget.blur();
// Also try to remove focus from the document
(document.activeElement as HTMLElement)?.blur();
}
}, 0);
}
};

useEffect(() => {
(async () => {
Expand Down Expand Up @@ -203,6 +240,7 @@ const Options = () => {
}
return PUBLIC_WEBSITE;
}, []);

return (
<Box sx={{ width: '100%', maxWidth: 400, bgcolor: 'background.paper' }}>
{!CODEIUM_ENTERPRISE && (
Expand Down Expand Up @@ -328,95 +366,84 @@ const Options = () => {
}}
/>
<Box sx={{ my: 2, mx: 2 }}>
<Typography variant="h6"> Jupyterlab settings </Typography>
<Typography variant="h6"> Jupyter Settings </Typography>
<Typography variant="body2">
A single keystroke is supported. The syntax is described{' '}
<Link
href="https://github.com/jupyterlab/lumino/blob/f85aad4903504c942fc202c57270e707f1ab87c1/packages/commands/src/index.ts#L1061-L1083"
target="_blank"
>
here
<OpenInNewIcon
fontSize="small"
sx={{
verticalAlign: 'bottom',
}}
/>
</Link>
.
Press the desired key combination in the input field. For example, press "Ctrl+Tab" for a
Ctrl+Tab shortcut.
</Typography>

<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
JupyterLab
</Typography>
<TextField
id="jupyterlabKeybindingAccept"
label="Accept key binding"
label="Accept Shortcut"
variant="standard"
fullWidth
inputRef={jupyterlabKeybindingAcceptRef}
value={jupyterlabKeybindingAcceptText}
onChange={(e) => setJupyterlabKeybindingAcceptText(e.target.value)}
value={jupyterlabAcceptInput ? 'Press keys...' : jupyterlabKeybindingAcceptText || 'Tab'}
onFocus={() => setJupyterlabAcceptInput(true)}
onBlur={async () => {
setJupyterlabAcceptInput(false);
if (currentKey.key) {
const formatted = formatKeyCombination(currentKey);
setJupyterlabKeybindingAcceptText(formatted);
await setStorageItem('jupyterlabKeybindingAccept', formatted);
setCurrentKey({ key: '', ctrl: false, alt: false, shift: false, meta: false });
}
}}
onKeyDown={handleKeyDown}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="text"
onClick={async () => {
const keybinding = jupyterlabKeybindingAcceptRef.current?.value;
await setStorageItem('jupyterlabKeybindingAccept', keybinding);
}}
sx={{ textTransform: 'none' }}
>
Enter Keybinding <LoginIcon />
</Button>
</Box>
<TextField
id="jupyterlabKeybindingDismiss"
label="Dismiss key binding"
label="Dismiss Shortcut"
variant="standard"
fullWidth
inputRef={jupyterlabKeybindingDismissRef}
value={jupyterlabKeybindingDismissText}
onChange={(e) => setJupyterlabKeybindingDismissText(e.target.value)}
value={
jupyterlabDismissInput ? 'Press keys...' : jupyterlabKeybindingDismissText || 'Escape'
}
onFocus={() => setJupyterlabDismissInput(true)}
onBlur={async () => {
setJupyterlabDismissInput(false);
if (currentKey.key) {
const formatted = formatKeyCombination(currentKey);
setJupyterlabKeybindingDismissText(formatted);
await setStorageItem('jupyterlabKeybindingDismiss', formatted);
setCurrentKey({ key: '', ctrl: false, alt: false, shift: false, meta: false });
}
}}
onKeyDown={handleKeyDown}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="text"
onClick={async () => {
const keybinding = jupyterlabKeybindingDismissRef.current?.value;
await setStorageItem('jupyterlabKeybindingDismiss', keybinding);
}}
sx={{ textTransform: 'none' }}
>
Enter Keybinding <LoginIcon />
</Button>
</Box>
</Box>
<Box sx={{ my: 2, mx: 2 }}>
<Typography variant="h6"> Jupyter Notebook settings </Typography>

<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
Jupyter Notebook
</Typography>
<TextField
id="jupyterNotebookKeybindingAccept"
label="Accept key binding"
label="Accept Shortcut"
variant="standard"
fullWidth
inputRef={jupyterNotebookKeybindingAcceptRef}
value={jupyterNotebookKeybindingAcceptText}
onChange={(e) => setJupyterNotebookKeybindingAcceptText(e.target.value)}
value={
notebookAcceptInput ? 'Press keys...' : jupyterNotebookKeybindingAcceptText || 'Tab'
}
onFocus={() => setNotebookAcceptInput(true)}
onBlur={async () => {
setNotebookAcceptInput(false);
if (currentKey.key) {
const formatted = formatKeyCombination(currentKey);
setJupyterNotebookKeybindingAcceptText(formatted);
await setStorageItem('jupyterNotebookKeybindingAccept', formatted);
setCurrentKey({ key: '', ctrl: false, alt: false, shift: false, meta: false });
}
}}
onKeyDown={handleKeyDown}
/>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
variant="text"
onClick={async () => {
const keybinding = jupyterNotebookKeybindingAcceptRef.current?.value;
await setStorageItem('jupyterNotebookKeybindingAccept', keybinding);
}}
sx={{ textTransform: 'none' }}
>
Enter Keybinding <LoginIcon />
</Button>
</Box>
</Box>
<Box sx={{ my: 2, mx: 2 }}>
<Typography variant="h6"> Jupyter debounce time </Typography>

<Typography variant="subtitle1" sx={{ mt: 2, mb: 1 }}>
Performance
</Typography>
<TextField
id="jupyterDebounceMs"
label="Debounce time (ms)"
label="Debounce (ms)"
variant="standard"
fullWidth
type="number"
Expand All @@ -428,8 +455,8 @@ const Options = () => {
<Button
variant="text"
onClick={async () => {
const debounceTime = Number(jupyterDebounceMsRef.current?.value);
await setStorageItem('jupyterDebounceMs', debounceTime);
const debounceMs = parseInt(jupyterDebounceMsRef.current?.value ?? '0');
await setStorageItem('jupyterDebounceMs', debounceMs);
}}
sx={{ textTransform: 'none' }}
>
Expand Down
Loading
Loading