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)