Skip to content

Commit b389ccb

Browse files
authored
Merge pull request #137 from jahn96/renameSnippet
Rename snippet
2 parents 476316c + 907185b commit b389ccb

File tree

6 files changed

+141
-12
lines changed

6 files changed

+141
-12
lines changed

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"author": "Jay Ahn, Kiran Pinnipati",
1616
"files": [
1717
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
18-
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
18+
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
19+
"schema/*.json"
1920
],
2021
"main": "lib/index.js",
2122
"types": "lib/index.d.ts",
@@ -43,7 +44,6 @@
4344
"@jupyterlab/application": "^2.1.2",
4445
"@jupyterlab/apputils": "^2.2.4",
4546
"@jupyterlab/cells": "^2.2.4",
46-
"@jupyterlab/coreutils": "^4.1.0",
4747
"@jupyterlab/docmanager": "^2.1.2",
4848
"@jupyterlab/docregistry": "^2.1.2",
4949
"@jupyterlab/fileeditor": "^2.1.2",
@@ -77,7 +77,8 @@
7777
"style/*.css"
7878
],
7979
"jupyterlab": {
80-
"extension": true
80+
"extension": true,
81+
"schemaDir": "schema"
8182
},
8283
"husky": {
8384
"hooks": {

schema/settings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"jupyter.lab.shortcuts": [
3+
{
4+
"command": "codeSnippet:save-as-snippet",
5+
"keys": ["Accel Shift A"],
6+
"selector": ".jp-Cell"
7+
},
8+
{
9+
"command": "codeSnippet:save-as-snippet",
10+
"keys": ["Accel Shift A"],
11+
"selector": ".jp-FileEditor"
12+
}
13+
],
14+
"type": "object"
15+
}

src/CodeSnippetDisplay.tsx

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export class CodeSnippetDisplay extends React.Component<
168168
this._dragData = null;
169169
this.handleDragMove = this.handleDragMove.bind(this);
170170
this._evtMouseUp = this._evtMouseUp.bind(this);
171+
this.handleRenameSnippet = this.handleRenameSnippet.bind(this);
171172
}
172173

173174
// Handle code snippet insert into an editor
@@ -321,9 +322,85 @@ export class CodeSnippetDisplay extends React.Component<
321322
);
322323
}
323324
}
324-
return <span>{name}</span>;
325+
return <span onDoubleClick={this.handleRenameSnippet}>{name}</span>;
325326
};
326327

328+
// rename snippet on double click
329+
// TODO: duplicate name check!
330+
private async handleRenameSnippet(
331+
event: React.MouseEvent<HTMLSpanElement, MouseEvent>
332+
): Promise<void> {
333+
const contentsService = CodeSnippetContentsService.getInstance();
334+
console.log(event.currentTarget);
335+
console.log(event.target);
336+
const target = event.target as HTMLElement;
337+
const oldPath = 'snippets/' + target.innerHTML + '.json';
338+
339+
const new_element = document.createElement('input');
340+
new_element.setAttribute('type', 'text');
341+
new_element.id = 'jp-codeSnippet-rename';
342+
new_element.innerHTML = target.innerHTML;
343+
344+
target.replaceWith(new_element);
345+
new_element.value = target.innerHTML;
346+
347+
new_element.focus();
348+
new_element.setSelectionRange(0, new_element.value.length);
349+
350+
new_element.onblur = async (): Promise<void> => {
351+
console.log(target.innerHTML);
352+
console.log(new_element.value);
353+
if (target.innerHTML !== new_element.value) {
354+
const newPath = 'snippets/' + new_element.value + '.json';
355+
try {
356+
await contentsService.rename(oldPath, newPath);
357+
} catch (error) {
358+
new_element.replaceWith(target);
359+
360+
await showDialog({
361+
title: 'Duplicate Name of Code Snippet',
362+
body: <p> {`"${newPath}" already exists.`} </p>,
363+
buttons: [Dialog.okButton({ label: 'Dismiss' })]
364+
});
365+
return;
366+
}
367+
this.props._codeSnippetWidgetModel.renameSnippet(
368+
target.innerHTML,
369+
new_element.value
370+
);
371+
target.innerHTML = new_element.value;
372+
}
373+
new_element.replaceWith(target);
374+
};
375+
new_element.onkeydown = (event: KeyboardEvent): void => {
376+
switch (event.code) {
377+
case 'Enter' || 'NumpadEnter': // Enter
378+
event.stopPropagation();
379+
event.preventDefault();
380+
new_element.blur();
381+
break;
382+
case 'Escape': // Escape
383+
event.stopPropagation();
384+
event.preventDefault();
385+
new_element.blur();
386+
break;
387+
case 'ArrowUp': // Up arrow
388+
event.stopPropagation();
389+
event.preventDefault();
390+
new_element.selectionStart = new_element.selectionEnd = 0;
391+
break;
392+
case 'ArrowDown': // Down arrow
393+
event.stopPropagation();
394+
event.preventDefault();
395+
new_element.selectionStart = new_element.selectionEnd =
396+
new_element.value.length;
397+
break;
398+
default:
399+
break;
400+
}
401+
};
402+
}
403+
327404
private handleDragSnippet(
328405
event: React.MouseEvent<HTMLDivElement, MouseEvent>
329406
): void {

src/CodeSnippetWidgetModel.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ export class CodeSnippetWidgetModel implements ICodeSnippetWidgetModel {
3333
}
3434
}
3535

36+
renameSnippet(oldName: string, newName: string): void {
37+
for (const snippet of this._snippets) {
38+
if (snippet.name === oldName) {
39+
snippet.name = newName;
40+
CodeSnippetContentsService.getInstance().save(
41+
'snippets/' + snippet.name + '.json',
42+
{ type: 'file', format: 'text', content: JSON.stringify(snippet) }
43+
);
44+
break;
45+
}
46+
}
47+
}
48+
3649
addSnippet(newSnippet: ICodeSnippet, index: number): void {
3750
// append a new snippet created from input form to the end
3851
if (newSnippet.id === -1) {

src/index.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
ILayoutRestorer
55
} from '@jupyterlab/application';
66
import { ICommandPalette, WidgetTracker } from '@jupyterlab/apputils';
7+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
8+
79
import { IEditorServices } from '@jupyterlab/codeeditor';
810
import { LabIcon } from '@jupyterlab/ui-components';
911

@@ -23,6 +25,7 @@ import {
2325

2426
const CODE_SNIPPET_EXTENSION_ID = 'code-snippet-extension';
2527

28+
const CODE_SNIPPET_SETTING_ID = 'jupyterlab-code-snippets:settings';
2629
/**
2730
* Snippet Editor Icon
2831
*/
@@ -150,7 +153,7 @@ function activateCodeSnippet(
150153
});
151154

152155
//Add an application command
153-
const saveCommand = 'save as code snippet';
156+
const saveCommand = 'codeSnippet:save-as-snippet';
154157
const toggled = false;
155158
app.commands.addCommand(saveCommand, {
156159
label: 'Save As Code Snippet',
@@ -169,18 +172,16 @@ function activateCodeSnippet(
169172
}
170173
});
171174

172-
//Put the command above in context menu
175+
// Put the saveCommand above in context menu
173176
app.contextMenu.addItem({
174177
command: saveCommand,
175178
selector: '.jp-Cell'
176179
});
177180

178-
// Add keybinding to save
179-
app.commands.addKeyBinding({
181+
// Put the saveCommand in non-notebook file context menu
182+
app.contextMenu.addItem({
180183
command: saveCommand,
181-
args: {},
182-
keys: ['Accel Shift S'],
183-
selector: '.jp-Cell'
184+
selector: '.jp-FileEditor'
184185
});
185186

186187
// Track and restore the widget state
@@ -211,6 +212,18 @@ function activateCodeSnippet(
211212
});
212213
}
213214

215+
const codeSnippetExtensionSetting: JupyterFrontEndPlugin<void> = {
216+
id: CODE_SNIPPET_SETTING_ID,
217+
autoStart: true,
218+
requires: [ISettingRegistry],
219+
activate: (app: JupyterFrontEnd, settingRegistry: ISettingRegistry) => {
220+
void settingRegistry
221+
.load(CODE_SNIPPET_SETTING_ID)
222+
.then(_ => console.log('settingRegistry successfully loaded!'))
223+
.catch(e => console.log(e));
224+
}
225+
};
226+
214227
function getSelectedText(): string {
215228
let selectedText;
216229
// window.getSelection
@@ -224,4 +237,4 @@ function getSelectedText(): string {
224237
return selectedText.toString();
225238
}
226239

227-
export default code_snippet_extension;
240+
export default [code_snippet_extension, codeSnippetExtensionSetting];

style/index.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
text-overflow: ellipsis;
4343
color: var(--jp-ui-font-color0);
4444
display: flex;
45+
align-items: center;
4546
}
4647

4748
.jp-codeSnippetsContainer-button {
@@ -69,6 +70,15 @@
6970
background-color: var(--jp-layout-color2);
7071
}
7172

73+
#jp-codeSnippet-rename {
74+
background-color: var(--jp-layout-color2);
75+
border: 1px solid var(--jp-layout-color1);
76+
border-radius: 4px;
77+
font-size: var(--jp-ui-font-size1);
78+
box-sizing: border-box;
79+
margin: 0px;
80+
}
81+
7282
.jp-codeSnippet-metadata {
7383
flex-basis: 95%;
7484
width: 100%;

0 commit comments

Comments
 (0)