提交 42b45bb3 编写于 作者: R rebornix

Delete cell

上级 7d478216
......@@ -22,6 +22,12 @@
"*"
],
"contributes": {
"commands": [
{
"command": "notebook.saveToMarkdown",
"title": "Notebook: Save to Markdown"
}
],
"notebookProvider": [
{
"viewType": "jupyter",
......
......@@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { NotebookProvider } from './notebookProvider';
export function activate(context: vscode.ExtensionContext) {
......@@ -11,5 +14,29 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.window.registerNotebookProvider('jupyter', new NotebookProvider(context.extensionPath, true)));
context.subscriptions.push(vscode.window.registerNotebookProvider('jupyterTest', new NotebookProvider(context.extensionPath, false)));
vscode.commands.registerCommand('notebook.saveToMarkdown', () => {
if (vscode.window.activeNotebookDocument) {
let document = vscode.window.activeNotebookDocument;
let uri = document.uri;
let fsPath = uri.fsPath;
let baseName = path.basename(fsPath, path.extname(fsPath));
let newFSPath = path.join(path.dirname(fsPath), baseName + '.md');
let content = '';
for (let i = 0; i < document.cells.length; i++) {
let cell = document.cells[i];
let language = cell.language ?? '';
if (cell.cell_type === 'markdown') {
content += cell.getContent() + '\n';
} else {
content += '```' + language + '\n' + cell.getContent() + '```\n\n';
}
}
fs.writeFileSync(newFSPath, content);
}
});
}
......@@ -53,7 +53,7 @@ export class JupyterNotebook {
private _extensionPath: string,
public document: vscode.NotebookDocument,
editor: vscode.NotebookEditor,
notebookJSON: any,
public notebookJSON: any,
private fillOutputs: boolean
) {
let cells = notebookJSON.cells.map(((raw_cell: any) => {
......@@ -88,7 +88,7 @@ export class JupyterNotebook {
let managedCell = editor.createCell(
raw_cell.source ? raw_cell.source.join('') : '',
notebookJSON.metadata.language_info.name,
notebookJSON?.metadata?.language_info?.name ?? 'python',
raw_cell.cell_type,
outputs
);
......@@ -97,7 +97,7 @@ export class JupyterNotebook {
return managedCell;
}));
editor.document.languages = ['javascript'];
editor.document.languages = ['python'];
editor.document.cells = cells;
}
......@@ -188,7 +188,20 @@ export class NotebookProvider implements vscode.NotebookProvider {
try {
let content = await vscode.workspace.fs.readFile(editor.document.uri);
let jupyterNotebook = new JupyterNotebook(this._extensionPath, editor.document, editor, JSON.parse(content.toString()), this.fillOutputs);
let json: any = {};
try {
json = JSON.parse(content.toString());
} catch {
json = {
cells: [{
cell_type: 'markdown',
source: [
'# header'
]
}]
};
}
let jupyterNotebook = new JupyterNotebook(this._extensionPath, editor.document, editor, json, this.fillOutputs);
this._notebooks.set(editor.document.uri.toString(), jupyterNotebook);
} catch {
......@@ -204,6 +217,44 @@ export class NotebookProvider implements vscode.NotebookProvider {
}
async save(document: vscode.NotebookDocument): Promise<boolean> {
let cells: any[] = [];
for (let i = 0; i < document.cells.length; i++) {
if (document.cells[i].cell_type === 'markdown') {
cells.push({
source: document.cells[i].getContent().split(/\r|\n|\r\n/g).map(str => str + '\n'),
metadata: {
language_info: {
name: document.cells[i].language ?? 'markdown'
}
},
cell_type: document.cells[i].cell_type
});
} else {
cells.push({
source: document.cells[i].getContent().split(/\r|\n|\r\n/g).map(str => str + '\n'),
metadata: {
language_info: {
name: document.cells[i].language ?? 'markdown'
}
},
cell_type: document.cells[i].cell_type,
outputs: []
});
}
}
let raw = this._notebooks.get(document.uri.toString());
if (raw) {
raw.notebookJSON.cells = cells;
let content = JSON.stringify(raw.notebookJSON, null, 4);
await vscode.workspace.fs.writeFile(document.uri, new TextEncoder().encode(content));
} else {
let content = JSON.stringify({ cells: cells }, null, 4);
await vscode.workspace.fs.writeFile(document.uri, new TextEncoder().encode(content));
}
return true;
}
}
\ No newline at end of file
......@@ -566,6 +566,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
// Render
private render(renderTop: number, renderHeight: number, renderLeft: number, scrollWidth: number): void {
this.isRendering = true;
const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
const renderRange = this.getRenderRange(renderTop, renderHeight);
......@@ -597,6 +598,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.lastRenderTop = renderTop;
this.lastRenderHeight = renderHeight;
this.isRendering = false;
}
// DOM operations
......
......@@ -1359,6 +1359,8 @@ declare module 'vscode' {
notebookType: string,
provider: NotebookProvider
): Disposable;
export let activeNotebookDocument: NotebookDocument | undefined;
}
//#endregion
......
......@@ -148,8 +148,17 @@ export class MainThreadNotebookDocument extends Disposable implements INotebook
return;
}
async removeCell(viewType: string, uri: URI, index: number): Promise<boolean> {
return true;
async deleteCell(uri: URI, index: number): Promise<boolean> {
let deleteExtHostCell = await this._proxy.$deleteCell(this.viewType, uri, index);
if (deleteExtHostCell) {
let cell = this.cells[index];
this._cellListeners.get(cell.handle)?.dispose();
this._cellListeners.delete(cell.handle);
this.cells.splice(index, 1);
return true;
}
return false;
}
async save(): Promise<boolean> {
......@@ -181,6 +190,9 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this._register(this._notebookService.onDidChangeActiveEditor(e => {
this._proxy.$updateActiveEditor(e.viewType, e.uri);
}));
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise<void> {
......@@ -337,6 +349,16 @@ export class MainThreadNotebookController implements IMainNotebookController {
return;
}
async deleteCell(uri: URI, index: number): Promise<boolean> {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
if (mainthreadNotebook) {
return mainthreadNotebook.deleteCell(uri, index);
}
return false;
}
executeNotebookActiveCell(uri: URI): void {
let mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
......
......@@ -556,6 +556,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
},
get activeNotebookDocument(): vscode.NotebookDocument | undefined {
return extHostNotebook.activeNotebookDocument;
},
get activeColorTheme(): vscode.ColorTheme {
checkProposedApiEnabled(extension);
return extHostTheming.activeColorTheme;
......
......@@ -1411,7 +1411,9 @@ export interface ExtHostNotebookShape {
$executeNotebook(viewType: string, uri: URI): Promise<void>;
$executeNotebookCell(viewType: string, uri: URI, cellHandle: number): Promise<void>;
$createRawCell(viewType: string, uri: URI, index: number, language: string, type: 'markdown' | 'code'): Promise<modes.ICell | undefined>;
$deleteCell(viewType: string, uri: URI, index: number): Promise<boolean>;
$saveNotebook(viewType: string, uri: URI): Promise<boolean>;
$updateActiveEditor(viewType: string, uri: URI): Promise<void>;
}
export interface ExtHostStorageShape {
......
......@@ -74,6 +74,8 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
private _cells: ExtHostCell[] = [];
private _cellDisposableMapping = new Map<number, DisposableStore>();
get cells() {
return this._cells;
}
......@@ -81,7 +83,12 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
set cells(newCells: ExtHostCell[]) {
this._cells = newCells;
this._cells.forEach(cell => {
cell.onDidChangeOutputs(() => {
if (!this._cellDisposableMapping.has(cell.handle)) {
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
}
let store = this._cellDisposableMapping.get(cell.handle)!;
store.add(cell.onDidChangeOutputs(() => {
this._proxy.$updateNotebookCell(this.viewType, this.uri, {
handle: cell.handle,
source: cell.source,
......@@ -90,7 +97,7 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
outputs: cell.outputs,
isDirty: false
});
});
}));
});
}
......@@ -130,7 +137,14 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
insertRawCell(index: number, cell: ExtHostCell) {
this.cells.splice(index, 0, cell);
cell.onDidChangeOutputs(() => {
if (!this._cellDisposableMapping.has(cell.handle)) {
this._cellDisposableMapping.set(cell.handle, new DisposableStore());
}
let store = this._cellDisposableMapping.get(cell.handle)!;
store.add(cell.onDidChangeOutputs(() => {
this._proxy.$updateNotebookCell(this.viewType, this.uri, {
handle: cell.handle,
source: cell.source,
......@@ -139,7 +153,20 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
outputs: cell.outputs,
isDirty: false
});
});
}));
}
deleteCell(index: number): boolean {
if (index >= this.cells.length) {
return false;
}
let cell = this.cells[index];
this._cellDisposableMapping.get(cell.handle)?.dispose();
this._cellDisposableMapping.delete(cell.handle);
this.cells.splice(index, 1);
return true;
}
getActiveCell(cellHandle: number) {
......@@ -246,6 +273,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
}
private _activeNotebookDocument: ExtHostNotebookDocument | undefined;
get activeNotebookDocument() {
return this._activeNotebookDocument;
}
public registerNotebookProvider(
extension: IExtensionDescription,
viewType: string,
......@@ -319,7 +352,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return provider.provider.executeCell(document!, cell!);
}
}
}
async $createRawCell(viewType: string, uri: URI, index: number, language: string, type: 'markdown' | 'code'): Promise<ICell | undefined> {
......@@ -344,6 +376,22 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return;
}
async $deleteCell(viewType: string, uri: URI, index: number): Promise<boolean> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
let document = this._documents.get(URI.revive(uri).toString());
if (document) {
return document.deleteCell(index);
}
return false;
}
return false;
}
async $saveNotebook(viewType: string, uri: URI): Promise<boolean> {
let provider = this._notebookProviders.get(viewType);
let document = this._documents.get(URI.revive(uri).toString());
......@@ -355,4 +403,14 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return false;
}
async $updateActiveEditor(viewType: string, uri: URI): Promise<void> {
let document = this._documents.get(URI.revive(uri).toString());
if (document) {
this._activeNotebookDocument = document;
} else {
this._activeNotebookDocument = undefined;
}
}
}
......@@ -131,7 +131,7 @@ export class ViewCell extends Disposable {
}
setText(strs: string[]) {
this.cell.source = strs.map(str => str + '\n');
this.cell.source = strs;
this._html = null;
}
......@@ -180,7 +180,7 @@ export class ViewCell extends Disposable {
private getMDRenderer() {
if (!this._mdRenderer) {
this._mdRenderer = new marked.Renderer();
this._mdRenderer = new marked.Renderer({ gfm: true });
}
return this._mdRenderer;
......@@ -393,7 +393,7 @@ class StatefullMarkdownCell extends Disposable {
const lineNum = viewCell.lineCount;
const lineHeight = handler.getFontInfo()?.lineHeight ?? 18;
const totalHeight = Math.max(lineNum + 1, 5) * lineHeight;
const totalHeight = Math.max(lineNum, 1) * lineHeight;
if (this.editor) {
// not first time, we don't need to create editor or bind listeners
......@@ -432,7 +432,22 @@ class StatefullMarkdownCell extends Disposable {
templateData.cellContainer.innerHTML = viewCell.getHTML() || '';
model.onDidChangeContent(() => {
let oldLineCnt = viewCell.lineCount;
viewCell.setText(model.getLinesContent());
let newLineCnt = model.getLineCount();
if (newLineCnt !== oldLineCnt) {
let width = this.editor!.getLayoutInfo().width;
let height = newLineCnt * lineHeight;
this.editor!.layout(
{
width: width,
height: height
}
);
}
templateData.cellContainer.innerHTML = viewCell.getHTML() || '';
const clientHeight = templateData.cellContainer.clientHeight;
......
......@@ -103,6 +103,13 @@ export class NotebookContribution implements IWorkbenchContribution {
this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group));
this.registerCommands();
this.editorService.onDidActiveEditorChange(() => {
if (this.editorService.activeEditor && this.editorService.activeEditor! instanceof NotebookEditorInput) {
let editorInput = this.editorService.activeEditor! as NotebookEditorInput;
this.notebookService.updateActiveNotebookDocument(editorInput.viewType!, editorInput.getResource()!);
}
});
}
private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
......
......@@ -32,6 +32,30 @@
width: 100%;
}
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table {
border-spacing: 0;
border-collapse: collapse;
}
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table th,
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table th,
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table td {
border: 1px solid #80808059;
}
.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table tr {
border-top: 1px solid #c6cbd1;
}
.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table tr {
border-top: 1px solid #80808059;
}
.monaco-workbench .part.editor > .content .notebook-editor .output {
padding-left: 8px;
padding-right: 8px;
......@@ -60,6 +84,7 @@
max-width: 100%;
}
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row {
overflow: visible !important;
}
......@@ -84,9 +109,14 @@
visibility: visible;
}
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row:hover {
/* .monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row:hover {
outline: none !important;
}
} */
/* .monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row.focused {
outline: none !important;
} */
.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row .menu:hover {
cursor: pointer;
}
......
......@@ -139,6 +139,7 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler {
keyboardSupport: false,
mouseSupport: true,
multipleSelectionSupport: false,
enableKeyboardNavigation: true,
overrideStyles: {
listBackground: editorBackground,
listActiveSelectionBackground: editorBackground,
......@@ -380,6 +381,10 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler {
if (type === 'markdown') {
newCell.isEditing = true;
}
DOM.scheduleAtNextAnimationFrame(() => {
this.list?.reveal(insertIndex, 0.33);
});
}
editNotebookCell(listIndex: number | undefined, cell: ViewCell): void {
......@@ -390,9 +395,11 @@ export class NotebookEditor extends BaseEditor implements NotebookHandler {
cell.isEditing = false;
}
deleteNotebookCell(listIndex: number | undefined, cell: ViewCell) {
async deleteNotebookCell(listIndex: number | undefined, cell: ViewCell): Promise<void> {
let index = this.model!.getNotebook().cells.indexOf(cell.cell);
// await this.notebookService.createNotebookCell(this.viewType!, this.notebook!.uri, insertIndex, language, type);
await this.notebookService.deleteNotebookCell(this.viewType!, this.notebook!.uri, index);
this.viewCells!.splice(index, 1);
this.model!.deleteCell(cell.cell);
this.list?.splice(index, 1);
......
......@@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri';
import { notebookExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';
function MODEL_ID(resource: URI): string {
return resource.toString();
......@@ -23,12 +24,14 @@ export interface IMainNotebookController {
updateNotebook(uri: URI, notebook: INotebook): void;
updateNotebookActiveCell(uri: URI, cellHandle: number): void;
createRawCell(uri: URI, index: number, language: string, type: 'markdown' | 'code'): Promise<ICell | undefined>;
deleteCell(uri: URI, index: number): Promise<boolean>
executeNotebookActiveCell(uri: URI): void;
destoryNotebookDocument(notebook: INotebook): void;
}
export interface INotebookService {
_serviceBrand: undefined;
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>;
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void;
unregisterNotebookProvider(viewType: string): void;
resolveNotebook(viewType: string, uri: URI): Promise<INotebook | undefined>;
......@@ -38,7 +41,9 @@ export interface INotebookService {
getNotebookProviderResourceRoots(): URI[];
updateNotebookActiveCell(viewType: string, resource: URI, cellHandle: number): void;
createNotebookCell(viewType: string, resource: URI, index: number, language: string, type: 'markdown' | 'code'): Promise<ICell | undefined>;
deleteNotebookCell(viewType: string, resource: URI, index: number): Promise<boolean>;
destoryNotebookDocument(viewType: string, notebook: INotebook): void;
updateActiveNotebookDocument(viewType: string, resource: URI): void;
}
export class NotebookInfoStore {
......@@ -87,6 +92,8 @@ export class NotebookService extends Disposable implements INotebookService {
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, extensionData: NotebookExtensionDescription }>();
public notebookProviderInfoStore: NotebookInfoStore = new NotebookInfoStore();
private readonly _models: { [modelId: string]: ModelData; };
private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>();
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event;
constructor() {
super();
......@@ -157,6 +164,16 @@ export class NotebookService extends Disposable implements INotebookService {
return;
}
deleteNotebookCell(viewType: string, resource: URI, index: number): Promise<boolean> {
let provider = this._notebookProviders.get(viewType);
if (provider) {
return provider.controller.deleteCell(resource, index);
}
return false;
}
async executeNotebook(viewType: string, uri: URI): Promise<void> {
let provider = this._notebookProviders.get(viewType);
......@@ -196,6 +213,10 @@ export class NotebookService extends Disposable implements INotebookService {
}
}
updateActiveNotebookDocument(viewType: string, resource: URI): void {
this._onDidChangeActiveEditor.fire({ viewType, uri: resource });
}
private _onWillDispose(model: INotebook): void {
let modelId = MODEL_ID(model.uri);
let modelData = this._models[modelId];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册