diff --git a/demos/tree-sitter/edit-domain-code.md b/demos/tree-sitter/edit-domain-code.md new file mode 100644 index 000000000..91400b613 --- /dev/null +++ b/demos/tree-sitter/edit-domain-code.md @@ -0,0 +1,80 @@ +# Edit Domain Code + + \ No newline at end of file diff --git a/demos/tree-sitter/edit-history.md b/demos/tree-sitter/edit-history.md index a5694e06a..470a6f6ec 100644 --- a/demos/tree-sitter/edit-history.md +++ b/demos/tree-sitter/edit-history.md @@ -16,7 +16,7 @@ // editor1.value = `let a = 3 + 4` editor1.value = `var a = 3` // editor2.value = `let a = 3 + 4\na++` - editor2.value = `{var a = 3}` + editor2.value = `var a = 3\nl` editor1.editor.on("change", (() => update()).debounce(500)); editor2.editor.on("change", (() => update()).debounce(500)); @@ -31,10 +31,12 @@ scriptGenerator.generate() + debugger + list.innerHTML = "" for(let action of scriptGenerator.actions) { - list.appendChild(
  • {action.type} {action.node && action.node.type} + list.appendChild(
  • {action.type} {action.node && action.node.type} {action.pos} {action.parent && action.parent.type}
  • ) } diff --git a/demos/tree-sitter/matches.md b/demos/tree-sitter/matches.md index 1d3e5973c..e9ed8d317 100644 --- a/demos/tree-sitter/matches.md +++ b/demos/tree-sitter/matches.md @@ -13,9 +13,18 @@ var vis = await () // editor1.value = `let a = 3 + 4` - editor1.value = `let a = 3` + editor1.value = `class Test { + foo(i) { + if (i == 0) return "Foo!" + } +}` // editor2.value = `let a = 3 + 4\na++` - editor2.value = `{let a = 2+4}` + editor2.value = `class Test { + foo(i) { + if (i == 0) return "Bar" + else if (i == -1) return "Foo!" + } +}` editor1.editor.on("change", (() => update()).debounce(500)); editor2.editor.on("change", (() => update()).debounce(500)); diff --git a/src/client/domain-code.js b/src/client/domain-code.js index 38ffcd20f..26fb32cb0 100644 --- a/src/client/domain-code.js +++ b/src/client/domain-code.js @@ -11,11 +11,15 @@ MD*/ import tinycolor from 'src/external/tinycolor.js'; - -import {Parser, JavaScript, visit as treeSitterVisit} from "src/client/tree-sitter.js" +import Strings from "src/client/strings.js" +import {Parser, JavaScript, visit as treeSitterVisit, match, debugPrint} from "src/client/tree-sitter.js" import {loc} from "utils" + +import { ChawatheScriptGenerator} from 'src/client/domain-code/chawathe-script-generator.js'; + + // from: https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript const cyrb53 = (str, seed = 0) => { let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; @@ -55,6 +59,11 @@ export class DomainObject { return false } + get depth() { + if(!this.parent) return 0 + return this.parent.depth + 1 + } + renderAll(codeMirror) { this.visit(ea => ea.renderOn(codeMirror)) } @@ -63,6 +72,12 @@ export class DomainObject { // do nothing } + debugPrint() { + let s = "" + this.visit(ea => s += Strings.indent(ea.type + " " + ea.id, ea.depth, " ") + "\n") + return s + } + visit(func) { func(this) for(let ea of this.children) { @@ -158,103 +173,115 @@ export class DomainObject { } } - static edit(rootDomainObject, sourceNew, edit ) { + static edit(rootDomainObject, sourceNew, notUsedEdit, debugInfo={} ) { - let { startIndex, oldEndIndex, newEndIndex } = edit - // 1. detect editit history (diff oldTree -> newTree) - // a) deleted nodes from oldTree - // b) added nodes in new tree + let originalAST = rootDomainObject.treeSitter.tree + let originalSource = originalAST.rootNode.text - // 2. apply diff to domain tree + let newAST = TreeSitterDomainObject.astFromSource(sourceNew) - - function assert(b) { if (!b) throw new Error() } - // #TODO use incremental re-parse via edit() - const newTree = TreeSitterDomainObject.astFromSource(sourceNew) + if(!originalAST) {throw new Error("originalAST missing")} + if(!newAST) {throw new Error("originalAST missing")} - var tsQueue = [newTree.rootNode] - var doQueue = [rootDomainObject] + if (debugInfo.newAST) debugInfo.newAST(newAST) + + let mappings = match(originalAST.rootNode, newAST.rootNode, 0, 100) + var scriptGenerator = new ChawatheScriptGenerator() + scriptGenerator.initWith(originalAST.rootNode, newAST.rootNode, mappings) + scriptGenerator.generate() + + if (debugInfo.mappings) debugInfo.mappings(mappings) + if (debugInfo.actions) debugInfo.actions(scriptGenerator.actions) - while (tsQueue.length > 0) { - const tsNode = tsQueue.pop(); - const doNode = doQueue.pop(); - assert(tsNode.type === doNode.type); - - const lostChildren = [] - const missingOldChildren = [...doNode.children] - - // go over all new children, if we find a new child without an old child, create a new one - // if we do find a match, update the treeSitter reference - for (let i = 0; i < tsNode.childCount; i++) { - const tsChild = tsNode.child(i) - const doChild = doNode.children.find(child => tsChild.text === child.treeSitter.text) - if (!doChild) { - lostChildren.push([i, tsChild]) - } else { - doChild.treeSitter = tsChild - missingOldChildren.splice(missingOldChildren.indexOf(tsChild), 1) + let newTreeSitterNodeByOldId = new Map() + for(let mapping of mappings) { + newTreeSitterNodeByOldId.set(mapping.node1.id, mapping.node2) + } + + let newTreeSitterNodeById = new Map() + treeSitterVisit(newAST.rootNode, ea => newTreeSitterNodeById.set(ea.id, ea)) + + + let obsolteDomainObjects = [] + + let domainObjectByOldId = new Map() + let domainObjectById = new Map() + rootDomainObject.visit(domainObject => { + domainObjectByOldId.set(domainObject.id, domainObject) + debugInfo.log && debugInfo.log("initial domainObjectById set " + domainObject.id ) + }) + + // modify only after traversion + // for(let domainObject of domainObjectByOldId.values()) { + // var newNode = newTreeSitterNodeByOldId.get(domainObject.id) + // if (newNode) { + // domainObject.treeSitter = newNode + // domainObjectById.set(domainObject.id, domainObject) + // } else { + // obsolteDomainObjects.push(domainObject) + // } + // } + + + for(let action of scriptGenerator.actions) { + if (action.type === "insert") { + // can be old or new id + let parentDomainObject = domainObjectByOldId.get(action.parent.id) + if (!parentDomainObject) { + parentDomainObject = domainObjectById.get(action.parent.id) + } + + if (!parentDomainObject) { + throw new Error(`parent domain object (${action.parent.type} ${action.parent.id}) not found`) } + var newDomainObject = new TreeSitterDomainObject(action.node) + newDomainObject.children = [] + newDomainObject.parent = parentDomainObject + + parentDomainObject.children.splice(action.pos, 0, newDomainObject) + + domainObjectById.set(newDomainObject.id, newDomainObject) + debugInfo.log && debugInfo.log("domainObjectById set " + newDomainObject.type + " " + newDomainObject.id ) } - - for (const [i, tsChild] of lostChildren) { - // we didn't find the exact child but maybe we can keep some of its children, if the - // type of the node is still the same (e.g., it's still a function) - const candidate = missingOldChildren.find(old => old.treeSitter.type === tsChild.type) - if (candidate) { - tsQueue.push(tsChild) - doQueue.push(candidate) - missingOldChildren.splice(missingOldChildren.indexOf(candidate), 1) - } else { - doNode.children.splice(i, 0, TreeSitterDomainObject.fromTreeSitterAST(tsChild)) + if (action.type === "delete") { + // can be old or new id + let domainObject = domainObjectByOldId.get(action.node.id) + if (!domainObject) { + domainObject = domainObjectById.get(action.node.id) } + var index = domainObject.parent.children.indexOf(domainObject) + + domainObject.parent.children.splice(index, 1) + + debugInfo.log && debugInfo.log("delelet " + domainObject.type + " " + domainObject.id ) } - - for (const missing of missingOldChildren) { - doNode.children.splice(doNode.children.indexOf(missing), 1) + if (action.type === "update") { + let domainObject = domainObjectByOldId.get(action.node.id) + if (!domainObject) { + domainObject = domainObjectById.get(action.node.id) + } + if (!domainObject) { + throw new Error("could not find treeSitter node") + } + + // we ignore the value change of the update but take the actual other treesitter node that is responsible + let otherTreeSitter = newTreeSitterNodeById.get(action.other.id) + + if (!otherTreeSitter) { + throw new Error("could not find other treeSitter node again") + } + domainObject.treeSitter = otherTreeSitter + + } + if (action.type === "move") { + throw new Error("implementation needed") } - } - - - /* - const treeSitterNodesByLocationAndHash = new Map(); - const remainingNewTreeSitterNodes = new Set() - treeSitterVisit(newTree.rootNode, node => { - let hash = cyrb53(node.text) - let locAndHash = node.startIndex + "-" + node.endIndex + "-" + hash - treeSitterNodesByLocationAndHash.set(locAndHash, node) - remainingNewTreeSitterNodes.add(node) - }) - - function getTreeSitterNodesByLocationAndHash(from, to, hash) { - return treeSitterNodesByLocationAndHash.get(from + "-" + to + "-" + hash) - } - - let addedTreeSitterNodes = [] - let removedDomainObjects = [] - rootDomainObject.visit(domainObject => { - let adjustedStartIndex = this.adjustIndex(domainObject.treeSitter.startIndex, edit) - let adjustedEndIndex = this.adjustIndex(domainObject.treeSitter.endIndex, edit) - let hash = cyrb53(domainObject.treeSitter.text) - - var correspondingNewTreeSitterNode = getTreeSitterNodesByLocationAndHash(adjustedStartIndex, adjustedEndIndex, hash) - if (correspondingNewTreeSitterNode) { - // take new correspondingNewTreeSitterNode as your own - remainingNewTreeSitterNodes.delete(correspondingNewTreeSitterNode) - } else { - removedDomainObjects.push(domainObject) - } - }) - - for(let node of remainingNewTreeSitterNodes) { - // create new domain object and put at right position - // var newDomainObject = TreeSitterDomainObject.fromTreeSitterAST() - }*/ + } - } printStructure() { @@ -286,6 +313,10 @@ export class TreeSitterDomainObject extends DomainObject { return this._treeSitterHistory && this._treeSitterHistory.last } + get id() { + return this.treeSitter.id + } + get type() { return this.treeSitter && this.treeSitter.type } @@ -322,17 +353,8 @@ export class TreeSitterDomainObject extends DomainObject { oldEndPosition: loc(to).asTreeSitter(), newEndPosition: loc(newTo).asTreeSitter(), } - // lively.openInspector(edit) - - - this.treeSitter.tree.edit(edit); - - - var newAST = TreeSitterDomainObject.parser.parse(livelyCodeMirror.value, this.treeSitter.tree); - this.debugNewAST = newAST - - DomainObject.updateFromTreeSitter(this.rootNode(), newAST.rootNode, edit) + DomainObject.edit(this.rootNode(), livelyCodeMirror.value, edit) livelyCodeMirror.dispatchEvent(new CustomEvent("domain-code-changed", {detail: {node: this, edit: edit}})) } @@ -432,7 +454,9 @@ export class ReplacementDomainObject extends DomainObject { return this.target && this.target.endPosition } - + get id() { + return this.target.id + } get inspectorClassName() { if (this.type) { diff --git a/src/client/domain-code/chawathe-script-generator.js b/src/client/domain-code/chawathe-script-generator.js index d04662cd7..8636c1551 100644 --- a/src/client/domain-code/chawathe-script-generator.js +++ b/src/client/domain-code/chawathe-script-generator.js @@ -33,7 +33,17 @@ MD*/ import {addMapping, getSrcForDst, getDstForSrc, isSrcMapped, isDstMapped, label, hasMapping} from "src/client/tree-sitter.js" function positionInParent(node) { - return node.parent.children.indexOf(node) + // return node.parent.children.indexOf(node) // object identity might be a problem? + + if (!node.parent) { + return -1 + } + + // search for myself based on explicit id and not implicit identitity + for(let i=0; i < node.parent.children.length; i++) { + if (node.parent.children[i].id == node.id) return i + } + return -1 } function insertChild(node, child, index) { @@ -162,10 +172,11 @@ export class Update extends Action { return "update" } - constructor(node, value) { + constructor(node, value, other) { super() this.node = node - this.value + this.value = value + this.other = other } } @@ -262,8 +273,8 @@ export class ChawatheScriptGenerator { // this.origDst.parent = dstFakeRoot this.actions = new EditScript(); - this.dstInOrder = new Set(); - this.srcInOrder = new Set(); + this.dstInOrder = new Map(); + this.srcInOrder = new Map(); // cpyMappings.addMapping(srcFakeRoot, dstFakeRoot); @@ -297,7 +308,7 @@ export class ChawatheScriptGenerator { const node = this.copyToOrig.get(w.id) if (!node) {debugger} - this.actions.add(new Update(node, getLabel(x))); + this.actions.add(new Update(node, getLabel(x), x)); setLabel(w, getLabel(x)); } if (!equals(z, v)) { @@ -315,8 +326,8 @@ export class ChawatheScriptGenerator { } } - this.srcInOrder.add(w); - this.dstInOrder.add(x); + this.srcInOrder.set(w.id, w); + this.dstInOrder.set(x.id, x); this.alignChildren(w, x); } @@ -330,8 +341,8 @@ export class ChawatheScriptGenerator { } alignChildren(w, x) { - this.srcInOrder.delete(...w.children); - this.dstInOrder.delete(...x.children); + w.children.forEach(ea => this.srcInOrder.delete(ea.id)); + x.children.forEach(ea => this.dstInOrder.delete(ea.id)); const s1 = []; for (const c of w.children) { @@ -354,8 +365,8 @@ export class ChawatheScriptGenerator { const lcsResult = this.lcs(s1, s2); for (const m of lcsResult) { - this.srcInOrder.add(m.node1); - this.dstInOrder.add(m.node2); + this.srcInOrder.set(m.node1.id, m.node1); + this.dstInOrder.set(m.node1.id, m.node2); } for (const b of s2) { @@ -368,8 +379,8 @@ export class ChawatheScriptGenerator { this.actions.add(mv); w.children.splice(k, 0, a); a.setParent(w); - this.srcInOrder.add(a); - this.dstInOrder.add(b); + this.srcInOrder.set(a.id, a); + this.dstInOrder.set(b.id, b); } } } @@ -381,7 +392,7 @@ export class ChawatheScriptGenerator { const siblings = y.children; for (const c of siblings) { - if (this.dstInOrder.has(c)) { + if (this.dstInOrder.has(c.id)) { if (c === x) return 0; else break; } @@ -391,7 +402,7 @@ export class ChawatheScriptGenerator { let v = null; for (let i = 0; i < xpos; i++) { const c = siblings[i]; - if (this.dstInOrder.has(c)) v = c; + if (this.dstInOrder.has(c.id)) v = c; } if (v === null) return 0; diff --git a/src/client/fileindex.js b/src/client/fileindex.js index 463e47e7d..80ac20b5f 100644 --- a/src/client/fileindex.js +++ b/src/client/fileindex.js @@ -170,6 +170,9 @@ export default class FileIndex { comments: '[start+url], url, start, end, firstline', // maybe name is not uniq per file... files: "url,name,type,version,modified,options,title,*tags,*versions,bibkey,*references, *unboundIdentifiers,*authors,*keywords" }).upgrade(function () { }) + db.version(20).stores({ + bibliography: '[url+key], key, url, type, title, *authors,*keywords,*fields, year, *references, organization, microsoftid, doi, scholarid' + }).upgrade(function () { }) return db } @@ -243,7 +246,8 @@ export default class FileIndex { refentry.keywords = (entry.entryTags.keywords || entry.entryTags.Keywords || "").split(", ") refentry.fields = (entry.entryTags.fields || entry.entryTags.Fields || "").split(", ") refentry.organization = entry.entryTags.organization || entry.entryTags.Organization - refentry.microsoftid = entry.entryTags.microsoftid + refentry.microsoftid = entry.entryTags.microsoftid // deprecated + refentry.scholarid = entry.entryTags.scholarid refentry.doi = entry.entryTags.doi } visited.add(refentry.key) diff --git a/src/client/tree-sitter.js b/src/client/tree-sitter.js index 743e672b6..cfee87767 100644 --- a/src/client/tree-sitter.js +++ b/src/client/tree-sitter.js @@ -13,7 +13,7 @@ await lively.loadJavaScriptThroughDOM("treeSitter", lively4url + "/src/external/ export const Parser = window.TreeSitter; await Parser.init() - +import Strings from "src/client/strings.js" export const JavaScript = await Parser.Language.load(lively4url + "/src/external/tree-sitter/tree-sitter-javascript.wasm"); @@ -24,6 +24,13 @@ javascriptParser.setLanguage(JavaScript); import { mapping as zhangShashaMapping } from "src/external/tree-edit-distance/zhang-shasha.js" + +export function debugPrint(node) { + let s = "" + visit(node, ea => s += Strings.indent(ea.type + " " + ea.id, depth(ea), " ") + "\n") + return s +} + export function visit(node, func) { func(node) for (let i = 0; i < node.childCount; i++) { @@ -166,6 +173,12 @@ function open(node, priorityList) { /*MD ![](media/Falleri2014FGA_alorighm1.png){width=400px} MD*/ +export function depth(node) { + if (!node.parent) return 0 + + return depth(node.parent) + 1 +} + export function height(node) { /* "The height of a node t ∈ T is defined as: 1) for a leaf node t, height(t) = 1 and @@ -174,6 +187,10 @@ export function height(node) { if (node.childCount === 0) return 1 + if (!node.children) { + debugger + } + return _.max(node.children.map(ea => height(ea))) + 1 } @@ -222,7 +239,7 @@ export function mapTrees(T1, T2, minHeight) { if (existTxT2 || existTxT1) { candidateMappings.push([t1, t2]); } else { - visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2)) + visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2, {phase: "mapTrees_01"})) } } } @@ -247,7 +264,7 @@ export function mapTrees(T1, T2, minHeight) { while (candidateMappings.length > 0) { const [t1, t2] = candidateMappings.shift(); - visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2)) + visitPairs(t1, t2, (node1, node2) => addMapping(mappings, node1, node2, {phase:"mapTrees_02"})) candidateMappings = candidateMappings.filter(pair => pair[0] !== t1); candidateMappings = candidateMappings.filter(pair => pair[1] !== t2); @@ -334,6 +351,7 @@ function isLeaf(node) { function lastChanceMatch(mappings, src, dst, maxSize) { if (s(src).size < maxSize || s(dst).size < maxSize) { + var debugStartTime = performance.now() let zsMappings = zhangShashaMapping(src, dst, function children(node) { return node.children }, function insertCost() { return 1 }, @@ -345,10 +363,14 @@ function lastChanceMatch(mappings, src, dst, maxSize) { return 1 } }); + debugLastChanceCounter++ + var debugTime = performance.now() - debugStartTime + for (let candidate of zsMappings) { if (candidate.t1 && candidate.t2) { if (!isSrcMapped(mappings, candidate.t1) && !isDstMapped(mappings, candidate.t2)) { - addMapping(mappings, candidate.t1, candidate.t2); + addMapping(mappings, candidate.t1, candidate.t2, + {phase: "lastChanceMatch", lastChanceCounter: debugLastChanceCounter, time: debugTime}); } } } @@ -362,7 +384,7 @@ export function hasMapping(mappings, t1, t2) { return mappings.find(ea => ea.node2.id == t1.id && ea.node2.id == t2.id) } -export function addMapping(mappings, t1, t2) { +export function addMapping(mappings, t1, t2, debugInfo) { if (!t1) { throw new Error("t1 is null") } if (!t2) { throw new Error("t2 is null") } @@ -371,15 +393,18 @@ export function addMapping(mappings, t1, t2) { debugger throw new Error("mapping gone wrong?") } - mappings.push({ node1: t1, node2: t2 }) + mappings.push({ node1: t1, node2: t2, debugInfo: debugInfo}) } -function bottomUpPhase(T1, dst, mappings, minDice, maxSize) { +var debugLastChanceCounter = 0 +function bottomUpPhase(T1, dst, mappings, minDice, maxSize) { + debugLastChanceCounter = 0 + visitPostorder(T1, t => { if (!t.parent) { if (!isSrcMapped(mappings, t)) { - addMapping(mappings, t, dst) + addMapping(mappings, t, dst, {phase: "bottomUpRoot"}) lastChanceMatch(mappings, t, dst, maxSize); } } else if (!isSrcMapped(mappings, t) && !isLeaf(t)) { @@ -396,7 +421,7 @@ function bottomUpPhase(T1, dst, mappings, minDice, maxSize) { if (best !== null) { lastChanceMatch(mappings, t, best, maxSize); - addMapping(mappings, t, best) + addMapping(mappings, t, best, {phase: "bottomUp"}) } } }) @@ -412,6 +437,7 @@ export function match(tree1, tree2, minHeight = 2, maxSize = 100, minDice=0.5) { let matches = mapTrees(tree1, tree2, minHeight) + bottomUpPhase(tree1, tree2, matches, minDice, maxSize) return Array.from(matches); diff --git a/src/components/tools/domain-code-explorer.js b/src/components/tools/domain-code-explorer.js index 076affa04..a6de7b597 100644 --- a/src/components/tools/domain-code-explorer.js +++ b/src/components/tools/domain-code-explorer.js @@ -163,8 +163,10 @@ export default class DomainCodeExplorer extends Morph { this._autoUpdate = false this.sourceEditor.setText(this.editor.getText()) - this.treeSitterRootNode = evt.detail.node.debugNewAST.rootNode - this.astInspector.inspect(this.treeSitterRootNode) + + // TODO + // this.treeSitterRootNode = evt.detail.node.debugNewAST.rootNode + // this.astInspector.inspect(this.treeSitterRootNode) this._autoUpdate = true } diff --git a/src/components/tools/treesitter-matches.js b/src/components/tools/treesitter-matches.js index 0b74e20b7..51060c078 100644 --- a/src/components/tools/treesitter-matches.js +++ b/src/components/tools/treesitter-matches.js @@ -11,7 +11,7 @@ export default class TreesitterMatches extends Morph { get livelyUpdateStrategy() { return 'inplace'; } async update() { - let graphviz = await () + let graphviz = await () function renderTree(rootNode, clusterName) { let dotEdges = [] @@ -28,11 +28,51 @@ export default class TreesitterMatches extends Morph { }` } + + function colorForPhase(phase) { + var colors = { + mapTrees_01: "green", + mapTrees_02: "green", + lastChanceMatch: "blue", + bottomUp: "red", + bottomUpRoot: "orange" + } + + if (!colors[phase]) { + debugger + } + + return colors[phase] || "gray" + } + + function labelFor(match) { + if (match.debugInfo && match.debugInfo.phase === "lastChanceMatch") { + return match.debugInfo.lastChanceCounter + } + return "" + } + + function tooltipFor(match) { + if (!match.debugInfo) return "" + var s = match.debugInfo.phase + if (match.debugInfo.time) { + s += " " + match.debugInfo.time +"ms" + } + return s + } + + function widthFor(match) { + if (match.debugInfo && match.debugInfo.time) { + return match.debugInfo.time * 1 + } + return 1 + } + function renderMatches(matches) { let dotEdges = [] for(let match of matches) { - dotEdges.push(`${match.node1.id} -> ${match.node2.id} [color=green]`) + dotEdges.push(`${match.node1.id} -> ${match.node2.id} [color="${match.debugInfo ? colorForPhase(match.debugInfo.phase) : "gray"}" penwidth="${widthFor(match)}" tooltip="${tooltipFor(match)}" label="${labelFor(match)}"]`) } return dotEdges.join(";\n") } @@ -63,10 +103,19 @@ export default class TreesitterMatches extends Morph { var parser = new Parser(); parser.setLanguage(JavaScript); - let sourceCode1 = `let a = 3 + 4` + let sourceCode1 = `class Test { + foo(i) { + if (i == 0) return "Foo!" + } +}` this.tree1 = parser.parse(sourceCode1); - let sourceCode2 = `let a = 3 + 4\na++` + let sourceCode2 = `class Test { + foo(i) { + if (i == 0) return "Bar" + else if (i == -1) return "Foo!" + } +}` this.tree2 = parser.parse(sourceCode2); this.matches = match(this.tree1.rootNode, this.tree2.rootNode) diff --git a/src/components/widgets/lively-pdf.js b/src/components/widgets/lively-pdf.js index 23b137faf..0f54f6ca0 100644 --- a/src/components/widgets/lively-pdf.js +++ b/src/components/widgets/lively-pdf.js @@ -318,7 +318,7 @@ export default class LivelyPDF extends Morph { text = text.replace(/ $/, "") // remove trailing white space if (text.length == 0) { - text = "DEBUG: " + JSON.stringify(highlight.annotation) + text = " " } result.push({annotation: highlight.annotation, page: highlight.page, text: text}) diff --git a/test/domain-code-test.js b/test/domain-code-test.js index 7fb96e967..10b6940e3 100644 --- a/test/domain-code-test.js +++ b/test/domain-code-test.js @@ -59,8 +59,7 @@ describe('TreeSitter', () => { // treesitterVisit(originalAST.rootNode, node => node.edit(edit)) // to update index var newAST = TreeSitterDomainObject.parser.parse(newSourceCode, originalAST); - window.xnewAST = newAST - + expect(newAST.rootNode.child(1).child(0).type, "first const became let").to.equal("let") @@ -105,18 +104,18 @@ l` expect(root.children[0].children[0].type).equals("assignment_expression") }) - xit('reconciles change when removing statement at end', () => { + it('reconciles change when removing statement at end', () => { let sourceOriginal = `a = 3 l` let sourceNew = `a = 3` let root = TreeSitterDomainObject.fromSource(sourceOriginal) - DomainObject.edit(root, sourceNew, { startIndex: 0, oldEndIndex: 0, newEndIndex: 1 }) + DomainObject.edit(root, sourceNew, { startIndex: 9, oldEndIndex: 9, newEndIndex: 10 }) expect(root.children.length).equals(1); expect(root.children[0].children[0].type).equals("assignment_expression") }) - xit('reconciles change when removing statement at start', () => { + it('reconciles change when removing statement at start', () => { let sourceOriginal = `l a = 3` let sourceNew = `a = 3` @@ -127,7 +126,7 @@ a = 3` expect(root.children[0].children[0].type).equals("assignment_expression") }) - xit('reconciles change when adding new statement at start of a function', () => { + it('reconciles change when adding new statement at start of a function', () => { let sourceOriginal = `function() { let a = 3 @@ -145,6 +144,17 @@ a = 3` expect(block.children.length).equals(4); expect(block.children[2].type).equals("lexical_declaration") }) + + it('reconciles change when updating ', () => { + let sourceOriginal = `let a = 3` + let sourceNew = `const a = 3` + let root = TreeSitterDomainObject.fromSource(sourceOriginal) + DomainObject.edit(root, sourceNew, { startIndex: 0, oldEndIndex: 0, newEndIndex: 1 }) + + expect(root.children[0].children[0].type).equals("const") + }) + + describe('adjustIndex', () => { it('do nothing to index before edits', async () => { @@ -282,6 +292,7 @@ a = 3` }); it('sets a const expr and domain object becomes const', async () => { resetDomainObject() + var letObj = obj.children[1].children[0] var oldIdentifierNode = obj.children[1].children[1].children[0].treeSitter expect(oldIdentifierNode.text, "old identifier").to.equal("a") @@ -289,20 +300,25 @@ a = 3` letObj.setText(livelyCodeMirror, "const") expect(livelyCodeMirror.value, "codemirror is updated").to.match(/const a/) + + + var constObj = obj.children[1].children[0] + + expect(constObj.treeSitter.text,"label changed").to.equal("const") // lively.openComponentInWindow("lively-ast-treesitter-inspector").then(comp => comp.inspect(letObj.debugNewAST.rootNode)) - expect(letObj.debugNewAST.rootNode.child(1).text, "new ast has const").to.equal("const a = 3") - expect(letObj.debugNewAST.rootNode.child(1).child(0).type, "new ast has const").to.equal("const") +// expect(letObj.debugNewAST.rootNode.child(1).text, "new ast has const").to.equal("const a = 3") +// expect(letObj.debugNewAST.rootNode.child(1).child(0).type, "new ast has const").to.equal("const") - var newIdentifierNode = letObj.debugNewAST.rootNode.child(1).child(1).child(0) - expect(newIdentifierNode.text, "new identifier").to.equal("a") - expect(newIdentifierNode.id, "identifier keeps same").to.equal(oldIdentifierNode.id) +// var newIdentifierNode = letObj.debugNewAST.rootNode.child(1).child(1).child(0) +// expect(newIdentifierNode.text, "new identifier").to.equal("a") +// expect(newIdentifierNode.id, "identifier keeps same").to.equal(oldIdentifierNode.id) - var constObj = obj.children[1].children[0] - expect(constObj.type, "old AST changed type ").to.equal("const") +// var constObj = obj.children[1].children[0] +// expect(constObj.type, "old AST changed type ").to.equal("const") // #TODO continue here... we need Franken-ASTs ... // Goals: @@ -352,8 +368,43 @@ a = 3` describe('SmilyReplacementDomainObject', () => { - it('click on let replacement works', () => { + it('click on let replacement works MANAL', () => { + + var sourceCode = +`// hello +let a = 3 + 4 +const b = a` + livelyCodeMirror.value = sourceCode + let domainObject = TreeSitterDomainObject.fromSource(sourceCode) + domainObject.replaceType('let', LetSmilyReplacementDomainObject) + domainObject.replaceType('const', ConstSmilyReplacementDomainObject) + + expect(domainObject.children.length, "childrens").to.equal(3) + + var letReplacement = domainObject.children[1].children[0] + expect(letReplacement.isReplacement).to.be.true + expect(letReplacement.type).to.equal("let") + + // letReplacement.target.setText(livelyCodeMirror, "const") + + // MANUAL + livelyCodeMirror.value = `// hello +const a = 3 + 4 +const b = a` + DomainObject.edit(domainObject, livelyCodeMirror.value) + + + expect(livelyCodeMirror.value).to.match(/const a/) + expect(domainObject.treeSitter.childCount, "childCount after replacement").to.equal(3) + expect(domainObject.children.length, "children after replacement").to.equal(3) + + var newConsDomainObject = domainObject.children[1].children[0] + expect(newConsDomainObject.type, "newConst").to.equal("const") + }); + + it('click on let replacement works via setText', () => { + var sourceCode = `// hello let a = 3 + 4 @@ -371,6 +422,13 @@ const b = a` expect(letReplacement.type).to.equal("let") letReplacement.target.setText(livelyCodeMirror, "const") + + expect(livelyCodeMirror.value).to.equal(`// hello +const a = 3 + 4 +const b = a`) + + + expect(livelyCodeMirror.value).to.match(/const a/) expect(domainObject.treeSitter.childCount, "childCount after replacement").to.equal(3) expect(domainObject.children.length, "children after replacement").to.equal(3) @@ -379,6 +437,7 @@ const b = a` expect(newConsDomainObject.type, "newConst").to.equal("const") }); + // #WIP continue here #KnownToFail xit('click on const and then on let replacement', () => { var sourceCode = @@ -392,10 +451,9 @@ const b = a` domainObject.replaceType('const', ConstSmilyReplacementDomainObject) - var consReplacement = domainObject.children[2].children[0] + var constReplacement = domainObject.children[2].children[0] - debugger - consReplacement.target.setText(livelyCodeMirror, "let") + constReplacement.target.setText(livelyCodeMirror, "let") expect(livelyCodeMirror.value).to.match(/let b/) expect(domainObject.treeSitter.childCount, "childCount after replacement").to.equal(3) @@ -429,8 +487,8 @@ const b = a` var letReplacement = domainObject.children[1].children[0] letReplacement.target.setText(livelyCodeMirror, "const") - var consReplacement = domainObject.children[2].children[0] - consReplacement.target.setText(livelyCodeMirror, "let") + var constReplacement = domainObject.children[2].children[0] + constReplacement.target.setText(livelyCodeMirror, "let") expect(livelyCodeMirror.value).to.match(/let b/) expect(domainObject.treeSitter.childCount, "childCount after replacement").to.equal(3) expect(domainObject.children.length, "children after replacement").to.equal(3)