mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 15:21:48 +03:00
Runtime Cache Integration Part 2 (#800)
This commit is contained in:
parent
7f1f484ada
commit
2a3ec07c87
@ -17,8 +17,7 @@ import org.enso.interpreter.runtime.scope.ModuleScope;
|
|||||||
import org.enso.polyglot.LanguageInfo;
|
import org.enso.polyglot.LanguageInfo;
|
||||||
import org.enso.polyglot.MethodNames;
|
import org.enso.polyglot.MethodNames;
|
||||||
import org.enso.text.buffer.Rope;
|
import org.enso.text.buffer.Rope;
|
||||||
import org.enso.text.editing.JavaEditorAdapter;
|
import org.enso.text.editing.*;
|
||||||
import org.enso.text.editing.model;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -220,7 +219,7 @@ public class ExecutionService {
|
|||||||
* @param edits the edits to apply.
|
* @param edits the edits to apply.
|
||||||
* @return an object for computing the changed IR nodes.
|
* @return an object for computing the changed IR nodes.
|
||||||
*/
|
*/
|
||||||
public Optional<Changeset> modifyModuleSources(File path, List<model.TextEdit> edits) {
|
public Optional<Changeset<Rope>> modifyModuleSources(File path, List<model.TextEdit> edits) {
|
||||||
Optional<Module> moduleMay = context.getModuleForFile(path);
|
Optional<Module> moduleMay = context.getModuleForFile(path);
|
||||||
if (!moduleMay.isPresent()) {
|
if (!moduleMay.isPresent()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@ -229,8 +228,12 @@ public class ExecutionService {
|
|||||||
if (module.getLiteralSource() == null) {
|
if (module.getLiteralSource() == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Changeset changeset =
|
Changeset<Rope> changeset =
|
||||||
new Changeset(module.getLiteralSource().toString(), module.parseIr(context));
|
new Changeset<>(
|
||||||
|
module.getLiteralSource(),
|
||||||
|
module.parseIr(context),
|
||||||
|
TextEditor.ropeTextEditor(),
|
||||||
|
IndexedSource.RopeIndexedSource());
|
||||||
Optional<Rope> editedSource = JavaEditorAdapter.applyEdits(module.getLiteralSource(), edits);
|
Optional<Rope> editedSource = JavaEditorAdapter.applyEdits(module.getLiteralSource(), edits);
|
||||||
editedSource.ifPresent(module::setLiteralSource);
|
editedSource.ifPresent(module::setLiteralSource);
|
||||||
return Optional.of(changeset);
|
return Optional.of(changeset);
|
||||||
|
@ -1,96 +1,246 @@
|
|||||||
package org.enso.compiler.context
|
package org.enso.compiler.context
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
import org.enso.compiler.core.IR
|
import org.enso.compiler.core.IR
|
||||||
import org.enso.compiler.exception.CompilerError
|
import org.enso.compiler.exception.CompilerError
|
||||||
import org.enso.compiler.pass.analyse.DataflowAnalysis
|
import org.enso.compiler.pass.analyse.DataflowAnalysis
|
||||||
import org.enso.syntax.text.Location
|
import org.enso.syntax.text.Location
|
||||||
import org.enso.text.editing.model.{Position, TextEdit}
|
import org.enso.text.editing.model.TextEdit
|
||||||
|
import org.enso.text.editing.{IndexedSource, TextEditor}
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
/**
|
/** Compute invalidated expressions.
|
||||||
* Compute invalidated expressions.
|
|
||||||
*
|
*
|
||||||
* @param source the text source.
|
* @param source the text source
|
||||||
* @param ir the IR node.
|
* @param ir the IR node
|
||||||
|
* @tparam A a source type
|
||||||
*/
|
*/
|
||||||
final class Changeset(val source: CharSequence, ir: IR) {
|
final class Changeset[A: TextEditor: IndexedSource](val source: A, ir: IR) {
|
||||||
|
|
||||||
/**
|
/** Traverses the IR and returns a list of all IR nodes affected by the edit
|
||||||
* Traverses the IR and returns a list of all IR nodes affected by the edit
|
|
||||||
* using the [[DataflowAnalysis]] information.
|
* using the [[DataflowAnalysis]] information.
|
||||||
*
|
*
|
||||||
* @param edit the text edit.
|
* @param edits the text edits
|
||||||
* @throws CompilerError if the IR is missing DataflowAnalysis metadata
|
* @throws CompilerError if the IR is missing DataflowAnalysis metadata
|
||||||
* @return the list of all IR nodes affected by the edit.
|
* @return the set of all IR nodes affected by the edit
|
||||||
*/
|
*/
|
||||||
@throws[CompilerError]
|
@throws[CompilerError]
|
||||||
def compute(edit: TextEdit): Seq[IR.ExternalId] = {
|
def compute(edits: Seq[TextEdit]): Set[IR.ExternalId] = {
|
||||||
val metadata = ir
|
val metadata = ir
|
||||||
.unsafeGetMetadata(
|
.unsafeGetMetadata(
|
||||||
DataflowAnalysis,
|
DataflowAnalysis,
|
||||||
"Empty dataflow analysis metadata during changeset calculation."
|
"Empty dataflow analysis metadata during changeset calculation."
|
||||||
)
|
)
|
||||||
invalidated(edit)
|
invalidated(edits)
|
||||||
.map(toDataflowDependencyType)
|
.map(Changeset.toDataflowDependencyType)
|
||||||
.flatMap(metadata.getExternal)
|
.flatMap(metadata.getExternal)
|
||||||
.flatten
|
.flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Traverses the IR and returns a list of the most specific (the innermost)
|
||||||
* Traverses the IR and returns a list of the most specific (the innermost)
|
|
||||||
* IR nodes directly affected by the edit by comparing the source locations.
|
* IR nodes directly affected by the edit by comparing the source locations.
|
||||||
*
|
*
|
||||||
* @param edit the text edit.
|
* @param edits the text edits
|
||||||
* @return the list of IR nodes directly affected by the edit.
|
* @return the set of IR nodes directly affected by the edit
|
||||||
*/
|
*/
|
||||||
def invalidated(edit: TextEdit): Seq[Changeset.Node] = {
|
def invalidated(edits: Seq[TextEdit]): Set[Changeset.NodeId] = {
|
||||||
@scala.annotation.tailrec
|
@scala.annotation.tailrec
|
||||||
def go(
|
def go(
|
||||||
edit: Location,
|
tree: Changeset.Tree,
|
||||||
queue: mutable.Queue[IR],
|
source: A,
|
||||||
acc: mutable.Builder[Changeset.Node, Vector[Changeset.Node]]
|
edits: mutable.Queue[TextEdit],
|
||||||
): Seq[Changeset.Node] =
|
ids: mutable.Set[Changeset.NodeId]
|
||||||
if (queue.isEmpty) {
|
): Set[Changeset.NodeId] = {
|
||||||
acc.result()
|
if (edits.isEmpty) ids.toSet
|
||||||
} else {
|
else {
|
||||||
val ir = queue.dequeue()
|
val edit = edits.dequeue()
|
||||||
val invalidatedChildren = ir.children.filter(intersect(edit, _))
|
val locationEdit = Changeset.toLocationEdit(edit, source)
|
||||||
if (invalidatedChildren.isEmpty) {
|
val invalidatedSet = Changeset.invalidated(tree, locationEdit.location)
|
||||||
if (intersect(edit, ir)) {
|
val newTree = Changeset.updateLocations(tree, locationEdit)
|
||||||
go(edit, queue, acc += Changeset.Node(ir))
|
val newSource = TextEditor[A].edit(source, edit)
|
||||||
} else {
|
go(newTree, newSource, edits, ids ++= invalidatedSet.map(_.id))
|
||||||
go(edit, queue ++= ir.children, acc)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
go(edit, queue ++= invalidatedChildren, acc)
|
|
||||||
}
|
}
|
||||||
|
val tree = Changeset.buildTree(ir)
|
||||||
|
go(tree, source, mutable.Queue.from(edits), mutable.HashSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
go(
|
|
||||||
toLocation(edit, source),
|
|
||||||
mutable.Queue(ir),
|
|
||||||
Vector.newBuilder[Changeset.Node]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
object Changeset {
|
||||||
* Checks if the IR is affected by the edit.
|
|
||||||
|
/** An identifier of IR node.
|
||||||
*
|
*
|
||||||
* @param edit location of the edit.
|
* @param internalId internal IR id
|
||||||
* @param ir the IR node.
|
* @param externalId external IR id
|
||||||
* @return true if the node is affected by the edit.
|
|
||||||
*/
|
*/
|
||||||
def intersect(edit: Location, ir: IR): Boolean = {
|
case class NodeId(
|
||||||
ir.location.map(_.location).exists(intersect(edit, _))
|
internalId: IR.Identifier,
|
||||||
|
externalId: Option[IR.ExternalId]
|
||||||
|
)
|
||||||
|
|
||||||
|
object NodeId {
|
||||||
|
|
||||||
|
/** Create a [[NodeId]] identifier from [[IR]].
|
||||||
|
*
|
||||||
|
* @param ir the source IR
|
||||||
|
* @return the identifier
|
||||||
|
*/
|
||||||
|
def apply(ir: IR): NodeId =
|
||||||
|
new NodeId(ir.getId, ir.getExternalId)
|
||||||
|
|
||||||
|
implicit val ordering: Ordering[NodeId] = (x: NodeId, y: NodeId) => {
|
||||||
|
val cmpInternal = Ordering[UUID].compare(x.internalId, y.internalId)
|
||||||
|
if (cmpInternal == 0) {
|
||||||
|
Ordering[Option[UUID]].compare(x.externalId, y.externalId)
|
||||||
|
} else {
|
||||||
|
cmpInternal
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// === Changeset Internals ==================================================
|
||||||
* Checks if the node location intersects the edit location.
|
|
||||||
|
/** Internal representation of an [[IR]]. */
|
||||||
|
private type Tree = mutable.TreeSet[Node]
|
||||||
|
|
||||||
|
/** The location that has been edited.
|
||||||
*
|
*
|
||||||
* @param edit location of the edit.
|
* @param location the location of the edit
|
||||||
* @param node location of the node.
|
* @param length the length of the inserted text
|
||||||
* @return true if the node and edit locations are intersecting.
|
*/
|
||||||
|
private case class LocationEdit(location: Location, length: Int) {
|
||||||
|
|
||||||
|
/** The difference in length between the edited text and the inserted text.
|
||||||
|
* Determines how much the rest of the text will be shifted after applying
|
||||||
|
* the edit.
|
||||||
|
*/
|
||||||
|
val locationDifference: Int = {
|
||||||
|
val editRange = location.end - location.start
|
||||||
|
length - editRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Internal representation of an `IR` node in the changeset.
|
||||||
|
*
|
||||||
|
* @param id the node id
|
||||||
|
* @param location the node location
|
||||||
|
*/
|
||||||
|
private case class Node(id: NodeId, location: Location) {
|
||||||
|
|
||||||
|
/** Shift the node location.
|
||||||
|
*
|
||||||
|
* @param offset the offset relative to the previous node location
|
||||||
|
* @return the node with a new location
|
||||||
|
*/
|
||||||
|
def shift(offset: Int): Node = {
|
||||||
|
val newLocation = location.copy(
|
||||||
|
start = location.start + offset,
|
||||||
|
end = location.end + offset
|
||||||
|
)
|
||||||
|
copy(location = newLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Node {
|
||||||
|
|
||||||
|
/** Create a node from [[IR]].
|
||||||
|
*
|
||||||
|
* @param ir the source IR
|
||||||
|
* @return the node if `ir` contains a location
|
||||||
|
*/
|
||||||
|
def fromIr(ir: IR): Option[Node] =
|
||||||
|
ir.location.map(loc => Node(NodeId(ir), loc.location))
|
||||||
|
|
||||||
|
/** Create an artificial node with fixed [[NodeId]]. It is used to select
|
||||||
|
* nodes by location in the tree.
|
||||||
|
*
|
||||||
|
* @param location the location of the node
|
||||||
|
* @return a select node
|
||||||
|
*/
|
||||||
|
def select(location: Location): Node =
|
||||||
|
new Node(NodeId(UUID.nameUUIDFromBytes(Array()), None), location)
|
||||||
|
|
||||||
|
implicit val ordering: Ordering[Node] = (x: Node, y: Node) => {
|
||||||
|
val compareStart =
|
||||||
|
Ordering[Int].compare(x.location.start, y.location.start)
|
||||||
|
if (compareStart == 0) {
|
||||||
|
val compareEnd = Ordering[Int].compare(y.location.end, x.location.end)
|
||||||
|
if (compareEnd == 0) Ordering[NodeId].compare(x.id, y.id)
|
||||||
|
else compareEnd
|
||||||
|
} else compareStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build an internal representation of the [[IR]].
|
||||||
|
*
|
||||||
|
* @param ir the source IR
|
||||||
|
* @return the tree representation of the IR
|
||||||
|
*/
|
||||||
|
private def buildTree(ir: IR): Tree = {
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def go(input: mutable.Queue[IR], acc: Tree): Tree =
|
||||||
|
if (input.isEmpty) acc
|
||||||
|
else {
|
||||||
|
val ir = input.dequeue()
|
||||||
|
if (ir.children.isEmpty) {
|
||||||
|
Node.fromIr(ir).foreach(acc.add)
|
||||||
|
}
|
||||||
|
go(input ++= ir.children, acc)
|
||||||
|
}
|
||||||
|
go(mutable.Queue(ir), mutable.TreeSet())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the tree locations after applying the edit.
|
||||||
|
*
|
||||||
|
* @param tree the source tree
|
||||||
|
* @param edit the edit to apply
|
||||||
|
* @return the tree with updated locations
|
||||||
|
*/
|
||||||
|
private def updateLocations(tree: Tree, edit: LocationEdit): Tree = {
|
||||||
|
val range = tree.rangeFrom(Node.select(edit.location)).toSeq
|
||||||
|
range.foreach { updated =>
|
||||||
|
tree -= updated
|
||||||
|
tree += updated.shift(edit.locationDifference)
|
||||||
|
}
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calculate the invalidated subset of the tree affected by the edit by
|
||||||
|
* comparing the source locations.
|
||||||
|
*
|
||||||
|
* @param tree the source tree
|
||||||
|
* @param edit the location of the edit
|
||||||
|
* @return the invalidated nodes of the tree
|
||||||
|
*/
|
||||||
|
private def invalidated(tree: Tree, edit: Location): Tree = {
|
||||||
|
val invalidated = mutable.TreeSet[Changeset.Node]()
|
||||||
|
tree.iterator.foreach { node =>
|
||||||
|
if (intersect(edit, node)) {
|
||||||
|
invalidated += node
|
||||||
|
tree -= node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidated
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if the node location intersects the edit location.
|
||||||
|
*
|
||||||
|
* @param edit location of the edit
|
||||||
|
* @param node the node
|
||||||
|
* @return true if the node and edit locations are intersecting
|
||||||
|
*/
|
||||||
|
private def intersect(edit: Location, node: Changeset.Node): Boolean = {
|
||||||
|
intersect(edit, node.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if the node location intersects the edit location.
|
||||||
|
*
|
||||||
|
* @param edit location of the edit
|
||||||
|
* @param node location of the node
|
||||||
|
* @return true if the node and edit locations are intersecting
|
||||||
*/
|
*/
|
||||||
private def intersect(edit: Location, node: Location): Boolean = {
|
private def intersect(edit: Location, node: Location): Boolean = {
|
||||||
inside(node.start, edit) ||
|
inside(node.start, edit) ||
|
||||||
@ -99,69 +249,53 @@ final class Changeset(val source: CharSequence, ir: IR) {
|
|||||||
inside(edit.end, node)
|
inside(edit.end, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Check if the character position index is inside the location.
|
||||||
* Checks if the character position index is inside the location.
|
|
||||||
*
|
*
|
||||||
* @param index the character position.
|
* @param index the character position
|
||||||
* @param location the location.
|
* @param location the location
|
||||||
* @return true if the index is inside the location.
|
* @return true if the index is inside the location
|
||||||
*/
|
*/
|
||||||
private def inside(index: Int, location: Location): Boolean =
|
private def inside(index: Int, location: Location): Boolean =
|
||||||
index >= location.start && index <= location.end
|
index >= location.start && index <= location.end
|
||||||
|
|
||||||
/**
|
/** Convert [[TextEdit]] to [[Changeset.LocationEdit]] edit in the provided
|
||||||
* Converts [[TextEdit]] location to [[Location]] in the provided source.
|
|
||||||
*
|
|
||||||
* @param edit the text edit.
|
|
||||||
* @param source the source text.
|
|
||||||
* @return location of the text edit in the source text.
|
|
||||||
*/
|
|
||||||
private def toLocation(edit: TextEdit, source: CharSequence): Location = {
|
|
||||||
val start = edit.range.start
|
|
||||||
val end = edit.range.end
|
|
||||||
Location(toIndex(start, source), toIndex(end, source))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts position relative to a line to an absolute position in the
|
|
||||||
* source.
|
* source.
|
||||||
*
|
*
|
||||||
* @param pos character position.
|
* @param edit the text edit
|
||||||
* @param source the source text.
|
* @param source the source text
|
||||||
* @return absolute position in the source.
|
* @return the edit location in the source text
|
||||||
*/
|
*/
|
||||||
private def toIndex(pos: Position, source: CharSequence): Int = {
|
private def toLocationEdit[A: IndexedSource](
|
||||||
val prefix = source.toString.linesIterator.take(pos.line)
|
edit: TextEdit,
|
||||||
prefix.mkString("\n").length + pos.character
|
source: A
|
||||||
|
): LocationEdit = {
|
||||||
|
LocationEdit(toLocation(edit, source), edit.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Convert [[TextEdit]] location to [[Location]] in the provided source.
|
||||||
* Converts invalidated node to the dataflow dependency type.
|
|
||||||
*
|
*
|
||||||
* @param node the invalidated node.
|
* @param edit the text edit
|
||||||
* @return the dataflow dependency type.
|
* @param source the source text
|
||||||
|
* @return location of the text edit in the source text
|
||||||
|
*/
|
||||||
|
private def toLocation[A: IndexedSource](
|
||||||
|
edit: TextEdit,
|
||||||
|
source: A
|
||||||
|
): Location = {
|
||||||
|
Location(
|
||||||
|
IndexedSource[A].toIndex(edit.range.start, source),
|
||||||
|
IndexedSource[A].toIndex(edit.range.end, source)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert invalidated node to the dataflow dependency type.
|
||||||
|
*
|
||||||
|
* @param node the invalidated node
|
||||||
|
* @return the dataflow dependency type
|
||||||
*/
|
*/
|
||||||
private def toDataflowDependencyType(
|
private def toDataflowDependencyType(
|
||||||
node: Changeset.Node
|
node: NodeId
|
||||||
): DataflowAnalysis.DependencyInfo.Type.Static =
|
): DataflowAnalysis.DependencyInfo.Type.Static =
|
||||||
DataflowAnalysis.DependencyInfo.Type
|
DataflowAnalysis.DependencyInfo.Type
|
||||||
.Static(node.internalId, node.externalId)
|
.Static(node.internalId, node.externalId)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Changeset {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An invalidated IR node.
|
|
||||||
*
|
|
||||||
* @param internalId internal IR id.
|
|
||||||
* @param externalId external IR id.
|
|
||||||
*/
|
|
||||||
case class Node(internalId: IR.Identifier, externalId: Option[IR.ExternalId])
|
|
||||||
|
|
||||||
object Node {
|
|
||||||
|
|
||||||
def apply(ir: IR): Node =
|
|
||||||
new Node(ir.getId, ir.getExternalId)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -28,7 +28,7 @@ class EditFileCmd(request: Api.EditFileNotification)
|
|||||||
.toScala
|
.toScala
|
||||||
val invalidateExpressionsCommand = changesetOpt.map { changeset =>
|
val invalidateExpressionsCommand = changesetOpt.map { changeset =>
|
||||||
CacheInvalidation.Command.InvalidateKeys(
|
CacheInvalidation.Command.InvalidateKeys(
|
||||||
request.edits.flatMap(changeset.compute)
|
changeset.compute(request.edits)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val invalidateStaleCommand = changesetOpt.map { changeset =>
|
val invalidateStaleCommand = changesetOpt.map { changeset =>
|
||||||
|
@ -3,6 +3,7 @@ package org.enso.compiler.test.context
|
|||||||
import org.enso.compiler.context.Changeset
|
import org.enso.compiler.context.Changeset
|
||||||
import org.enso.compiler.core.IR
|
import org.enso.compiler.core.IR
|
||||||
import org.enso.compiler.test.CompilerTest
|
import org.enso.compiler.test.CompilerTest
|
||||||
|
import org.enso.text.buffer.Rope
|
||||||
import org.enso.text.editing.model.{Position, Range, TextEdit}
|
import org.enso.text.editing.model.{Position, Range, TextEdit}
|
||||||
|
|
||||||
class ChangesetTest extends CompilerTest {
|
class ChangesetTest extends CompilerTest {
|
||||||
@ -17,7 +18,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||||
val two = rhs.right.value
|
val two = rhs.right.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
two.getId
|
two.getId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -30,7 +31,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||||
val two = rhs.right.value
|
val two = rhs.right.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
two.getId
|
two.getId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -43,7 +44,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||||
val two = rhs.right.value
|
val two = rhs.right.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
two.getId
|
two.getId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -55,7 +56,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
||||||
val x = ir.name
|
val x = ir.name
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
x.getId
|
x.getId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -67,7 +68,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
||||||
val x = ir.name
|
val x = ir.name
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
x.getId
|
x.getId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val plus = rhs.operator
|
val plus = rhs.operator
|
||||||
val two = rhs.right.value
|
val two = rhs.right.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
plus.getId,
|
plus.getId,
|
||||||
two.getId
|
two.getId
|
||||||
)
|
)
|
||||||
@ -96,7 +97,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val x = ir.name
|
val x = ir.name
|
||||||
val one = rhs.left.value
|
val one = rhs.left.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
x.getId,
|
x.getId,
|
||||||
one.getId
|
one.getId
|
||||||
)
|
)
|
||||||
@ -111,7 +112,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val one =
|
val one =
|
||||||
ir.expression.asInstanceOf[IR.Application.Operator.Binary].left.value
|
ir.expression.asInstanceOf[IR.Application.Operator.Binary].left.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
x.getId,
|
x.getId,
|
||||||
one.getId
|
one.getId
|
||||||
)
|
)
|
||||||
@ -131,7 +132,7 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val plus = secondLine.operator
|
val plus = secondLine.operator
|
||||||
val x = secondLine.right.value
|
val x = secondLine.right.value
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
y.getId,
|
y.getId,
|
||||||
plus.getId,
|
plus.getId,
|
||||||
x.getId
|
x.getId
|
||||||
@ -154,7 +155,56 @@ class ChangesetTest extends CompilerTest {
|
|||||||
val y = thirdLine.left.value
|
val y = thirdLine.left.value
|
||||||
val plus = thirdLine.operator
|
val plus = thirdLine.operator
|
||||||
|
|
||||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||||
|
z.getId,
|
||||||
|
y.getId,
|
||||||
|
plus.getId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"multiple single expression" in {
|
||||||
|
val code = """x = 1 + 2"""
|
||||||
|
val edits = Seq(
|
||||||
|
TextEdit(Range(Position(0, 0), Position(0, 0)), "inde"),
|
||||||
|
TextEdit(Range(Position(0, 8), Position(0, 9)), "40"),
|
||||||
|
TextEdit(Range(Position(0, 11), Position(0, 12)), "-"),
|
||||||
|
TextEdit(Range(Position(0, 8), Position(0, 10)), "44")
|
||||||
|
)
|
||||||
|
|
||||||
|
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
||||||
|
val x = ir.name
|
||||||
|
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||||
|
val one = rhs.left.value
|
||||||
|
val plus = rhs.operator
|
||||||
|
|
||||||
|
invalidated(ir, code, edits: _*) should contain theSameElementsAs Seq(
|
||||||
|
x.getId,
|
||||||
|
one.getId,
|
||||||
|
plus.getId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"multiple multiline" in {
|
||||||
|
val code =
|
||||||
|
"""foo x =
|
||||||
|
| z = 1
|
||||||
|
| y = z
|
||||||
|
| y + x""".stripMargin.linesIterator.mkString("\n")
|
||||||
|
val edits = Seq(
|
||||||
|
TextEdit(Range(Position(0, 0), Position(0, 0)), "bar = 123\n\n"),
|
||||||
|
TextEdit(Range(Position(4, 8), Position(5, 7)), "42\n y -")
|
||||||
|
)
|
||||||
|
|
||||||
|
val ir = code.toIrExpression.get.asInstanceOf[IR.Function.Binding]
|
||||||
|
val secondLine = ir.body.children(1).asInstanceOf[IR.Expression.Binding]
|
||||||
|
val z = secondLine.expression
|
||||||
|
val thirdLine =
|
||||||
|
ir.body.children(2).asInstanceOf[IR.Application.Operator.Binary]
|
||||||
|
val y = thirdLine.left.value
|
||||||
|
val plus = thirdLine.operator
|
||||||
|
|
||||||
|
invalidated(ir, code, edits: _*) should contain theSameElementsAs Seq(
|
||||||
|
ir.name.getId,
|
||||||
z.getId,
|
z.getId,
|
||||||
y.getId,
|
y.getId,
|
||||||
plus.getId
|
plus.getId
|
||||||
@ -162,6 +212,6 @@ class ChangesetTest extends CompilerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def invalidated(edit: TextEdit, code: String, ir: IR): Seq[IR.Identifier] =
|
def invalidated(ir: IR, code: String, edits: TextEdit*): Set[IR.Identifier] =
|
||||||
new Changeset(code, ir).invalidated(edit).map(_.internalId)
|
new Changeset(Rope(code), ir).invalidated(edits).map(_.internalId)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import org.enso.interpreter.instrument.{
|
|||||||
}
|
}
|
||||||
import org.enso.interpreter.test.Metadata
|
import org.enso.interpreter.test.Metadata
|
||||||
import org.enso.pkg.{Package, PackageManager}
|
import org.enso.pkg.{Package, PackageManager}
|
||||||
import org.enso.polyglot.runtime.Runtime.Api.VisualisationUpdate
|
|
||||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiRequest}
|
import org.enso.polyglot.runtime.Runtime.{Api, ApiRequest}
|
||||||
import org.enso.polyglot.{
|
import org.enso.polyglot.{
|
||||||
LanguageInfo,
|
LanguageInfo,
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.enso.text.editing
|
||||||
|
|
||||||
|
import org.enso.text.buffer.Rope
|
||||||
|
import org.enso.text.editing.model.Position
|
||||||
|
|
||||||
|
/** A source which character positions can be accessed by index.
|
||||||
|
*
|
||||||
|
* @tparam A a source type
|
||||||
|
*/
|
||||||
|
trait IndexedSource[A] {
|
||||||
|
|
||||||
|
/** Converts position relative to a line to an absolute position in the
|
||||||
|
* source.
|
||||||
|
*
|
||||||
|
* @param pos character position.
|
||||||
|
* @param source the source text.
|
||||||
|
* @return absolute position in the source.
|
||||||
|
*/
|
||||||
|
def toIndex(pos: Position, source: A): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
object IndexedSource {
|
||||||
|
|
||||||
|
def apply[A](implicit is: IndexedSource[A]): IndexedSource[A] = is
|
||||||
|
|
||||||
|
implicit val CharSequenceIndexedSource: IndexedSource[CharSequence] =
|
||||||
|
(pos: Position, source: CharSequence) => {
|
||||||
|
val prefix = source.toString.linesIterator.take(pos.line)
|
||||||
|
prefix.mkString("\n").length + pos.character
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val RopeIndexedSource: IndexedSource[Rope] =
|
||||||
|
(pos: Position, source: Rope) => {
|
||||||
|
val prefix = source.lines.take(pos.line)
|
||||||
|
prefix.characters.length + pos.character
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user