Skip to content

Commit

Permalink
Fix the completer in Jupyterlab 4 (ploomber#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
brichet committed Dec 27, 2023
1 parent cfb5c75 commit 3892a6b
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 151 deletions.
94 changes: 59 additions & 35 deletions src/completer/customconnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ import {
ICompletionProvider
} from '@jupyterlab/completer';

import { keywords } from './keywords.json';

/**
* A custom connector for completion handlers.
*/
export class CustomCompleterProvider implements ICompletionProvider {
export class SQLCompleterProvider implements ICompletionProvider {
constructor() {
// Build the completion item from the JSON file.
this._items = keywords.map(item => {
return {
label: item.value,
type: 'keyword'
}
})
}

/**
* The context completion provider is applicable on all cases.
* @param context - additional information about context of completion request
Expand All @@ -33,69 +45,81 @@ export class CustomCompleterProvider implements ICompletionProvider {
context: ICompletionContext
): Promise<CompletionHandler.ICompletionItemsReply> {
const editor = context.editor;

if (!editor) {
return Promise.reject('No editor');
}
return new Promise<CompletionHandler.ICompletionItemsReply>(resolve => {
resolve(Private.completionHint(editor!));
resolve(Private.completionHint(editor!, this._items));
});
}

readonly identifier = 'CompletionProvider:custom';
readonly renderer: any = null;
}

/**
* A namespace for custom connector statics.
*/
export namespace CustomConnector {
/**
* The instantiation options for cell completion handlers.
*/
export interface IOptions {
/**
* The session used by the custom connector.
*/
editor: CodeEditor.IEditor | null;
}
private _items: CompletionHandler.ICompletionItem[];
}

/**
* A namespace for Private functionality.
*/
namespace Private {
/**
* Get a list of mocked completion hints.
* Get a list of completion hints.
*
* @param editor Editor
* @returns Completion reply
*/
export function completionHint(
editor: CodeEditor.IEditor
editor: CodeEditor.IEditor,
baseItems: CompletionHandler.ICompletionItem[]
): CompletionHandler.ICompletionItemsReply {
// Find the token at the cursor
const token = editor.getTokenAtCursor();

// Create a list of matching tokens.
const tokenList = [
{ value: token.value + 'Magic', offset: token.offset, type: 'magic' },
{ value: token.value + 'Science', offset: token.offset, type: 'science' },
{ value: token.value + 'Neither', offset: token.offset }
];

// Only choose the ones that have a non-empty type field, which are likely to be of interest.
const completionList = tokenList.filter(t => t.type).map(t => t.value);
// Remove duplicate completions from the list
const matches = Array.from(new Set<string>(completionList));
// Find all the items containing the token value.
let items = baseItems.filter(
item => item.label.toLowerCase().includes(token.value.toLowerCase())
);

const items = new Array<CompletionHandler.ICompletionItem>();
matches.forEach(label => items.push({ label }));
// Sort the items.
items = items.sort((a, b) => {
return sortItems(
token.value.toLowerCase(),
a.label.toLowerCase(),
b.label.toLowerCase()
);
});

return {
start: token.offset,
end: token.offset + token.value.length,
items
items: items
};
}
}

/**
* Compare function to sort items.
* The comparison is based on the position of the token in the label. If the positions
* are the same, it is sorted alphabetically, starting at the token.
*
* @param token - the value of the token in lower case.
* @param a - the label of the first item in lower case.
* @param b - the label of the second item in lower case.
*/
function sortItems(
token: string,
a: string,
b: string
): number {
const ind1 = a.indexOf(token);
const ind2 = b.indexOf(token);
if (ind1 < ind2) {
return -1;
} else if (ind1 > ind2) {
return 1;
} else {
const end1 = a.slice(ind1);
const end2 = b.slice(ind1);
return end1 <= end2 ? -1 : 1;
}
}
}
6 changes: 2 additions & 4 deletions src/completer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
import { ICompletionProviderManager } from '@jupyterlab/completer';
import { INotebookTracker } from '@jupyterlab/notebook';

import { CustomCompleterProvider } from './customconnector';
import { SQLCompleterProvider } from './customconnector';

/**
* Initialization data for the extension.
Expand All @@ -20,9 +20,7 @@ const plugin_completer: JupyterFrontEndPlugin<void> = {
completionManager: ICompletionProviderManager,
notebooks: INotebookTracker
) => {
completionManager.registerProvider(new CustomCompleterProvider());

console.log('JupyterLab custom completer extension is activated!');
completionManager.registerProvider(new SQLCompleterProvider());
}
};

Expand Down
168 changes: 83 additions & 85 deletions src/completer/keywords.json
Original file line number Diff line number Diff line change
@@ -1,85 +1,83 @@
{"keywords": [
{ "value": "ADD" },
{ "value": "ADD CONSTRAINT" },
{ "value": "ALL" },
{ "value": "ALTER" },
{ "value": "ALTER COLUMN" },
{ "value": "ALTER TABLE" },
{ "value": "AND" },
{ "value": "ANY" },
{ "value": "AS" },
{ "value": "ASC" },
{ "value": "BACKUP DATABASE" },
{ "value": "BETWEEN" },
{ "value": "CASE" },
{ "value": "CHECK" },
{ "value": "COLUMN" },
{ "value": "CONSTRAINT" },
{ "value": "CREATE" },
{ "value": "CREATE DATABASE" },
{ "value": "CREATE INDEX" },
{ "value": "CREATE OR REPLACE VIEW" },
{ "value": "CREATE TABLE" },
{ "value": "CREATE PROCEDURE" },
{ "value": "CREATE UNIQUE INDEX" },
{ "value": "CREATE VIEW" },
{ "value": "DATABASE" },
{ "value": "DEFAULT" },
{ "value": "DELETE" },
{ "value": "DESC" },
{ "value": "DISTINCT" },
{ "value": "DROP" },
{ "value": "DROP COLUMN" },
{ "value": "DROP CONSTRAINT" },
{ "value": "DROP DATABASE" },
{ "value": "DROP DEFAULT" },
{ "value": "DROP INDEX" },
{ "value": "DROP TABLE" },
{ "value": "DROP VIEW" },
{ "value": "EXEC" },
{ "value": "EXISTS" },
{ "value": "FOREIGN KEY" },
{ "value": "FROM" },
{ "value": "FULL OUTER JOIN" },
{ "value": "GROUP BY" },
{ "value": "HAVING" },
{ "value": "IN" },
{ "value": "INDEX" },
{ "value": "INNER JOIN" },
{ "value": "INSERT INTO" },
{ "value": "INSERT INTO SELECT" },
{ "value": "IS NULL" },
{ "value": "IS NOT NULL" },
{ "value": "JOIN" },
{ "value": "LEFT JOIN" },
{ "value": "LIKE" },
{ "value": "LIMIT" },
{ "value": "NOT" },
{ "value": "NOT NULL" },
{ "value": "OR" },
{ "value": "ORDER BY" },
{ "value": "OUTER JOIN" },
{ "value": "PRIMARY KEY" },
{ "value": "PROCEDURE" },
{ "value": "RIGHT JOIN" },
{ "value": "ROWNUM" },
{ "value": "SELECT" },
{ "value": "SELECT DISTINCT" },
{ "value": "SELECT INTO" },
{ "value": "SELECT TOP" },
{ "value": "SET" },
{ "value": "TABLE" },
{ "value": "TOP" },
{ "value": "TRUNCATE TABLE" },
{ "value": "UNION" },
{ "value": "UNION ALL" },
{ "value": "UNIQUE" },
{ "value": "UPDATE" },
{ "value": "VALUES" },
{ "value": "VIEW" },
{ "value": "WHERE" }



]
}
{
"keywords": [
{ "value": "ADD" },
{ "value": "ADD CONSTRAINT" },
{ "value": "ALL" },
{ "value": "ALTER" },
{ "value": "ALTER COLUMN" },
{ "value": "ALTER TABLE" },
{ "value": "AND" },
{ "value": "ANY" },
{ "value": "AS" },
{ "value": "ASC" },
{ "value": "BACKUP DATABASE" },
{ "value": "BETWEEN" },
{ "value": "CASE" },
{ "value": "CHECK" },
{ "value": "COLUMN" },
{ "value": "CONSTRAINT" },
{ "value": "CREATE" },
{ "value": "CREATE DATABASE" },
{ "value": "CREATE INDEX" },
{ "value": "CREATE OR REPLACE VIEW" },
{ "value": "CREATE TABLE" },
{ "value": "CREATE PROCEDURE" },
{ "value": "CREATE UNIQUE INDEX" },
{ "value": "CREATE VIEW" },
{ "value": "DATABASE" },
{ "value": "DEFAULT" },
{ "value": "DELETE" },
{ "value": "DESC" },
{ "value": "DISTINCT" },
{ "value": "DROP" },
{ "value": "DROP COLUMN" },
{ "value": "DROP CONSTRAINT" },
{ "value": "DROP DATABASE" },
{ "value": "DROP DEFAULT" },
{ "value": "DROP INDEX" },
{ "value": "DROP TABLE" },
{ "value": "DROP VIEW" },
{ "value": "EXEC" },
{ "value": "EXISTS" },
{ "value": "FOREIGN KEY" },
{ "value": "FROM" },
{ "value": "FULL OUTER JOIN" },
{ "value": "GROUP BY" },
{ "value": "HAVING" },
{ "value": "IN" },
{ "value": "INDEX" },
{ "value": "INNER JOIN" },
{ "value": "INSERT INTO" },
{ "value": "INSERT INTO SELECT" },
{ "value": "IS NULL" },
{ "value": "IS NOT NULL" },
{ "value": "JOIN" },
{ "value": "LEFT JOIN" },
{ "value": "LIKE" },
{ "value": "LIMIT" },
{ "value": "NOT" },
{ "value": "NOT NULL" },
{ "value": "OR" },
{ "value": "ORDER BY" },
{ "value": "OUTER JOIN" },
{ "value": "PRIMARY KEY" },
{ "value": "PROCEDURE" },
{ "value": "RIGHT JOIN" },
{ "value": "ROWNUM" },
{ "value": "SELECT" },
{ "value": "SELECT DISTINCT" },
{ "value": "SELECT INTO" },
{ "value": "SELECT TOP" },
{ "value": "SET" },
{ "value": "TABLE" },
{ "value": "TOP" },
{ "value": "TRUNCATE TABLE" },
{ "value": "UNION" },
{ "value": "UNION ALL" },
{ "value": "UNIQUE" },
{ "value": "UPDATE" },
{ "value": "VALUES" },
{ "value": "VIEW" },
{ "value": "WHERE" }
]
}
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ export const MODULE_VERSION = data.version;
/*
* The current package name.
*/
export const MODULE_NAME = data.name;
export const MODULE_NAME = data.name;
12 changes: 2 additions & 10 deletions ui-tests/jupyter_server_test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,15 @@
opens the server to the world and provide access to JupyterLab
JavaScript objects through the global window variable.
"""
from jupyterlab.galata import configure_jupyter_server
from pathlib import Path
from tempfile import mkdtemp

from ploomber_core.telemetry import telemetry

temp_dir = mkdtemp(prefix="galata-test-")
dot_ploomber = str(Path(temp_dir, "dot-ploomber"))

c.ServerApp.port = 8888 # noqa: F821
c.ServerApp.port_retries = 0 # noqa: F821
c.ServerApp.open_browser = False # noqa: F821

c.ServerApp.root_dir = temp_dir # noqa: F821
c.ServerApp.token = "" # noqa: F821
c.ServerApp.password = "" # noqa: F821
c.ServerApp.disable_check_xsrf = True # noqa: F821
c.LabApp.expose_app_in_browser = True # noqa: F821
configure_jupyter_server(c) # noqa: F821

# Uncomment to set server log level to debug level
# c.ServerApp.log_level = "DEBUG"
Expand Down
4 changes: 2 additions & 2 deletions ui-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"test:update": "jlpm playwright test --update-snapshots"
},
"devDependencies": {
"@jupyterlab/galata": "^4.3.0",
"@playwright/test": "^1.34.2",
"@jupyterlab/galata": "^5.0.5",
"@playwright/test": "^1.37.0",
"klaw-sync": "^6.0.0"
}
}
Loading

0 comments on commit 3892a6b

Please sign in to comment.