mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 06:01:37 +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.MethodNames;
|
||||
import org.enso.text.buffer.Rope;
|
||||
import org.enso.text.editing.JavaEditorAdapter;
|
||||
import org.enso.text.editing.model;
|
||||
import org.enso.text.editing.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
@ -220,7 +219,7 @@ public class ExecutionService {
|
||||
* @param edits the edits to apply.
|
||||
* @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);
|
||||
if (!moduleMay.isPresent()) {
|
||||
return Optional.empty();
|
||||
@ -229,8 +228,12 @@ public class ExecutionService {
|
||||
if (module.getLiteralSource() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
Changeset changeset =
|
||||
new Changeset(module.getLiteralSource().toString(), module.parseIr(context));
|
||||
Changeset<Rope> changeset =
|
||||
new Changeset<>(
|
||||
module.getLiteralSource(),
|
||||
module.parseIr(context),
|
||||
TextEditor.ropeTextEditor(),
|
||||
IndexedSource.RopeIndexedSource());
|
||||
Optional<Rope> editedSource = JavaEditorAdapter.applyEdits(module.getLiteralSource(), edits);
|
||||
editedSource.ifPresent(module::setLiteralSource);
|
||||
return Optional.of(changeset);
|
||||
|
@ -1,96 +1,246 @@
|
||||
package org.enso.compiler.context
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.exception.CompilerError
|
||||
import org.enso.compiler.pass.analyse.DataflowAnalysis
|
||||
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
|
||||
|
||||
/**
|
||||
* Compute invalidated expressions.
|
||||
/** Compute invalidated expressions.
|
||||
*
|
||||
* @param source the text source.
|
||||
* @param ir the IR node.
|
||||
* @param source the text source
|
||||
* @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.
|
||||
*
|
||||
* @param edit the text edit.
|
||||
* @param edits the text edits
|
||||
* @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]
|
||||
def compute(edit: TextEdit): Seq[IR.ExternalId] = {
|
||||
def compute(edits: Seq[TextEdit]): Set[IR.ExternalId] = {
|
||||
val metadata = ir
|
||||
.unsafeGetMetadata(
|
||||
DataflowAnalysis,
|
||||
"Empty dataflow analysis metadata during changeset calculation."
|
||||
)
|
||||
invalidated(edit)
|
||||
.map(toDataflowDependencyType)
|
||||
invalidated(edits)
|
||||
.map(Changeset.toDataflowDependencyType)
|
||||
.flatMap(metadata.getExternal)
|
||||
.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.
|
||||
*
|
||||
* @param edit the text edit.
|
||||
* @return the list of IR nodes directly affected by the edit.
|
||||
* @param edits the text edits
|
||||
* @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
|
||||
def go(
|
||||
edit: Location,
|
||||
queue: mutable.Queue[IR],
|
||||
acc: mutable.Builder[Changeset.Node, Vector[Changeset.Node]]
|
||||
): Seq[Changeset.Node] =
|
||||
if (queue.isEmpty) {
|
||||
acc.result()
|
||||
} else {
|
||||
val ir = queue.dequeue()
|
||||
val invalidatedChildren = ir.children.filter(intersect(edit, _))
|
||||
if (invalidatedChildren.isEmpty) {
|
||||
if (intersect(edit, ir)) {
|
||||
go(edit, queue, acc += Changeset.Node(ir))
|
||||
} else {
|
||||
go(edit, queue ++= ir.children, acc)
|
||||
}
|
||||
} else {
|
||||
go(edit, queue ++= invalidatedChildren, acc)
|
||||
}
|
||||
tree: Changeset.Tree,
|
||||
source: A,
|
||||
edits: mutable.Queue[TextEdit],
|
||||
ids: mutable.Set[Changeset.NodeId]
|
||||
): Set[Changeset.NodeId] = {
|
||||
if (edits.isEmpty) ids.toSet
|
||||
else {
|
||||
val edit = edits.dequeue()
|
||||
val locationEdit = Changeset.toLocationEdit(edit, source)
|
||||
val invalidatedSet = Changeset.invalidated(tree, locationEdit.location)
|
||||
val newTree = Changeset.updateLocations(tree, locationEdit)
|
||||
val newSource = TextEditor[A].edit(source, edit)
|
||||
go(newTree, newSource, edits, ids ++= invalidatedSet.map(_.id))
|
||||
}
|
||||
|
||||
go(
|
||||
toLocation(edit, source),
|
||||
mutable.Queue(ir),
|
||||
Vector.newBuilder[Changeset.Node]
|
||||
)
|
||||
}
|
||||
val tree = Changeset.buildTree(ir)
|
||||
go(tree, source, mutable.Queue.from(edits), mutable.HashSet())
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the IR is affected by the edit.
|
||||
}
|
||||
|
||||
object Changeset {
|
||||
|
||||
/** An identifier of IR node.
|
||||
*
|
||||
* @param edit location of the edit.
|
||||
* @param ir the IR node.
|
||||
* @return true if the node is affected by the edit.
|
||||
* @param internalId internal IR id
|
||||
* @param externalId external IR id
|
||||
*/
|
||||
def intersect(edit: Location, ir: IR): Boolean = {
|
||||
ir.location.map(_.location).exists(intersect(edit, _))
|
||||
case class NodeId(
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node location intersects the edit location.
|
||||
// === Changeset Internals ==================================================
|
||||
|
||||
/** Internal representation of an [[IR]]. */
|
||||
private type Tree = mutable.TreeSet[Node]
|
||||
|
||||
/** The location that has been edited.
|
||||
*
|
||||
* @param edit location of the edit.
|
||||
* @param node location of the node.
|
||||
* @return true if the node and edit locations are intersecting.
|
||||
* @param location the location of the edit
|
||||
* @param length the length of the inserted text
|
||||
*/
|
||||
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 = {
|
||||
inside(node.start, edit) ||
|
||||
@ -99,69 +249,53 @@ final class Changeset(val source: CharSequence, ir: IR) {
|
||||
inside(edit.end, node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the character position index is inside the location.
|
||||
/** Check if the character position index is inside the location.
|
||||
*
|
||||
* @param index the character position.
|
||||
* @param location the location.
|
||||
* @return true if the index is inside the location.
|
||||
* @param index the character position
|
||||
* @param location the location
|
||||
* @return true if the index is inside the location
|
||||
*/
|
||||
private def inside(index: Int, location: Location): Boolean =
|
||||
index >= location.start && index <= location.end
|
||||
|
||||
/**
|
||||
* 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
|
||||
/** Convert [[TextEdit]] to [[Changeset.LocationEdit]] edit in the provided
|
||||
* source.
|
||||
*
|
||||
* @param pos character position.
|
||||
* @param source the source text.
|
||||
* @return absolute position in the source.
|
||||
* @param edit the text edit
|
||||
* @param source the source text
|
||||
* @return the edit location in the source text
|
||||
*/
|
||||
private def toIndex(pos: Position, source: CharSequence): Int = {
|
||||
val prefix = source.toString.linesIterator.take(pos.line)
|
||||
prefix.mkString("\n").length + pos.character
|
||||
private def toLocationEdit[A: IndexedSource](
|
||||
edit: TextEdit,
|
||||
source: A
|
||||
): LocationEdit = {
|
||||
LocationEdit(toLocation(edit, source), edit.text.length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts invalidated node to the dataflow dependency type.
|
||||
/** Convert [[TextEdit]] location to [[Location]] in the provided source.
|
||||
*
|
||||
* @param node the invalidated node.
|
||||
* @return the dataflow dependency type.
|
||||
* @param edit the text edit
|
||||
* @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(
|
||||
node: Changeset.Node
|
||||
node: NodeId
|
||||
): DataflowAnalysis.DependencyInfo.Type.Static =
|
||||
DataflowAnalysis.DependencyInfo.Type
|
||||
.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
|
||||
val invalidateExpressionsCommand = changesetOpt.map { changeset =>
|
||||
CacheInvalidation.Command.InvalidateKeys(
|
||||
request.edits.flatMap(changeset.compute)
|
||||
changeset.compute(request.edits)
|
||||
)
|
||||
}
|
||||
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.core.IR
|
||||
import org.enso.compiler.test.CompilerTest
|
||||
import org.enso.text.buffer.Rope
|
||||
import org.enso.text.editing.model.{Position, Range, TextEdit}
|
||||
|
||||
class ChangesetTest extends CompilerTest {
|
||||
@ -17,7 +18,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||
val two = rhs.right.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
two.getId
|
||||
)
|
||||
}
|
||||
@ -30,7 +31,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||
val two = rhs.right.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
two.getId
|
||||
)
|
||||
}
|
||||
@ -43,7 +44,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val rhs = ir.expression.asInstanceOf[IR.Application.Operator.Binary]
|
||||
val two = rhs.right.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
two.getId
|
||||
)
|
||||
}
|
||||
@ -55,7 +56,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
||||
val x = ir.name
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
x.getId
|
||||
)
|
||||
}
|
||||
@ -67,7 +68,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val ir = code.toIrExpression.get.asInstanceOf[IR.Expression.Binding]
|
||||
val x = ir.name
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
x.getId
|
||||
)
|
||||
}
|
||||
@ -81,7 +82,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val plus = rhs.operator
|
||||
val two = rhs.right.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
plus.getId,
|
||||
two.getId
|
||||
)
|
||||
@ -96,7 +97,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val x = ir.name
|
||||
val one = rhs.left.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
x.getId,
|
||||
one.getId
|
||||
)
|
||||
@ -111,7 +112,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val one =
|
||||
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,
|
||||
one.getId
|
||||
)
|
||||
@ -131,7 +132,7 @@ class ChangesetTest extends CompilerTest {
|
||||
val plus = secondLine.operator
|
||||
val x = secondLine.right.value
|
||||
|
||||
invalidated(edit, code, ir) should contain theSameElementsAs Seq(
|
||||
invalidated(ir, code, edit) should contain theSameElementsAs Seq(
|
||||
y.getId,
|
||||
plus.getId,
|
||||
x.getId
|
||||
@ -154,7 +155,56 @@ class ChangesetTest extends CompilerTest {
|
||||
val y = thirdLine.left.value
|
||||
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,
|
||||
y.getId,
|
||||
plus.getId
|
||||
@ -162,6 +212,6 @@ class ChangesetTest extends CompilerTest {
|
||||
}
|
||||
}
|
||||
|
||||
def invalidated(edit: TextEdit, code: String, ir: IR): Seq[IR.Identifier] =
|
||||
new Changeset(code, ir).invalidated(edit).map(_.internalId)
|
||||
def invalidated(ir: IR, code: String, edits: TextEdit*): Set[IR.Identifier] =
|
||||
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.pkg.{Package, PackageManager}
|
||||
import org.enso.polyglot.runtime.Runtime.Api.VisualisationUpdate
|
||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiRequest}
|
||||
import org.enso.polyglot.{
|
||||
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