diff --git a/src/NotebookProvider.ts b/src/NotebookProvider.ts index 9ef273c02..9115fb1d0 100644 --- a/src/NotebookProvider.ts +++ b/src/NotebookProvider.ts @@ -4,6 +4,7 @@ import { prettyPrint } from '../out/cljs-lib/cljs-lib'; import * as tokenCursor from './cursor-doc/token-cursor'; import * as repl from './api/repl'; import _ = require('lodash'); +import { isInteger } from 'lodash'; export class NotebookProvider implements vscode.NotebookSerializer { private readonly decoder = new TextDecoder(); @@ -30,36 +31,145 @@ export class NotebookProvider implements vscode.NotebookSerializer { } } -function parseClojure(content: string): vscode.NotebookCellData[] { - const cursor = tokenCursor.createStringCursor(content); - const topLevelRanges = cursor.rangesForTopLevelForms().flat(); - if (topLevelRanges.length) { - topLevelRanges[0] = 0; +function substring(content: string, [start, end]) { + if (isInteger(start) && isInteger(end)) { + return content.substring(start, end); } + return ''; +} - // grab only the ends of ranges, so we can include all of the file in the notebook - const fullRanges = _.filter(topLevelRanges, (_, index) => { - return index % 2 !== 0; - }); - - // last range should include end of file - fullRanges[fullRanges.length - 1] = content.length; +function parseClojure(content: string): vscode.NotebookCellData[] { + const cursor = tokenCursor.createStringCursor(content); + let offset = 0; + let cells = []; + + while (cursor.forwardSexp()) { + const start = offset; + const end = cursor.offsetStart; + offset = end; + + const endForm = cursor.doc.getTokenCursor(end - 1); + const afterForm = cursor.doc.getTokenCursor(end); + + if (endForm.getFunctionName() === 'comment') { + const commentRange = afterForm.rangeForCurrentForm(0); + const commentStartCursor = cursor.doc.getTokenCursor(commentRange[0]); + const commentCells = []; + let previouseEnd = start; + + commentStartCursor.downList(); + commentStartCursor.forwardSexp(); + + while (commentStartCursor.forwardSexp()) { + const range = commentStartCursor.rangeForDefun(commentStartCursor.offsetStart); + + let leading = ''; + const indent = commentStartCursor.doc.getRowCol(range[0])[1]; // will break with tabs? + + leading = content.substring(previouseEnd, range[0]); + previouseEnd = range[1]; + + commentCells.push({ + value: substring(content, range), + kind: vscode.NotebookCellKind.Code, + languageId: 'clojure', + metadata: { + leading: leading, + indent, + range, + richComment: true, + trailing: '', + }, + }); + } + + _.last(commentCells).metadata.trailing = content.substring(previouseEnd, end); + + cells = cells.concat(commentCells); + + continue; + } - // start of file to end of top level sexp pairs - const allRanges = _.zip(_.dropRight([_.first(topLevelRanges), ...fullRanges], 1), fullRanges); + const range = cursor.rangeForDefun(cursor.offsetStart); + + const leading = content.substring(start, range[0]); + + if (leading.indexOf(';; ') === -1) { + cells.push({ + value: leading, + kind: vscode.NotebookCellKind.Markup, + languageId: 'markdown', + metadata: { + indent: 0, + range, + leading: '', + trailing: '', + }, + }); + } else { + cells.push({ + value: leading.replace(/;; /g, ''), + kind: vscode.NotebookCellKind.Markup, + languageId: 'markdown', + metadata: { + indent: 0, + range, + markdownComment: true, + leading: '', + trailing: '', + }, + }); + } - const ranges = allRanges.map(([start, end]) => { - return { - value: content.substring(start, end), + cells.push({ + value: substring(content, range), kind: vscode.NotebookCellKind.Code, languageId: 'clojure', - }; - }); - return ranges; + metadata: { + indent: 0, + range, + leading: '', + trailing: '', + }, + }); + } + + _.last(cells).metadata.trailing = content.substring( + _.last(cells).metadata.range[1], + content.length + ); + + console.log(cells); + + return cells; } function writeCellsToClojure(cells: vscode.NotebookCellData[]) { - return cells.map((x) => x.value).join(''); + return cells.reduce((acc, x, index) => { + if (x.kind === vscode.NotebookCellKind.Code) { + let result = ''; + + // created inside the notebook + if (undefined === x.metadata.leading) { + const indent = index > 0 ? _.repeat(' ', cells[index - 1].metadata.indent) : ''; + + result = '\n\n' + indent + x.value; + } else { + result = x.metadata.leading + x.value + x.metadata.trailing; + } + + return acc.concat(result); + } else { + if (x.metadata.markdownComment) { + let result = x.value.replace(/\n(?!$)/g, '\n;; '); + if (index === 0) { + result = ';; ' + result; + } + return acc.concat(result); + } + return acc.concat(x.value); + } + }, ''); } export class NotebookKernel { diff --git a/src/cursor-doc/token-cursor.ts b/src/cursor-doc/token-cursor.ts index 97cfab567..a592ca0b8 100644 --- a/src/cursor-doc/token-cursor.ts +++ b/src/cursor-doc/token-cursor.ts @@ -794,6 +794,18 @@ export class LispTokenCursor extends TokenCursor { return ranges; } + rangesForCommentForms(offset: number): [number, number][] { + const cursor = this.doc.getTokenCursor(offset); + const ranges: [number, number][] = []; + while (cursor.forwardSexp()) { + const end = cursor.offsetStart; + cursor.backwardSexp(); + ranges.push([cursor.offsetStart, end]); + cursor.forwardSexp(); + } + return ranges; + } + isWhiteSpace(): boolean { return tokenIsWhiteSpace(this.getToken()); } diff --git a/test-data/notebook2.clj b/test-data/notebook2.clj new file mode 100644 index 000000000..dcdde78ed --- /dev/null +++ b/test-data/notebook2.clj @@ -0,0 +1,79 @@ +(ns notebook) + +"string1" "string2" +;; A line comment +;; block + +(def hover-map + {:stuff "in-here" + :ohter "yeah" + ;; Yada yada + :deeper {:a 1, "foo" :bar, [1 2 3] (vec (range 2000))}}) +;; Foo + +;; Line comment, line 1 +;; +;; Line comment, line 2 +(defn foo [] + (println "bar")) + +(foo) + +;; A line comment + +hover-map + +"
testing