/* NSC -- new Scala compiler * Copyright 2005-2009 LAMP/EPFL * @author Martin Odersky */ // $Id: SemanticTokens.scala 18406 2009-07-30 07:41:22Z odersky $ package scala.tools.nsc package models import java.lang.Character.isJavaIdentifierPart import java.lang.Thread import scala.collection.mutable.{HashMap, HashSet} import scala.tools.nsc.Global import scala.tools.nsc.symtab.{Flags, Names} import scala.tools.nsc.symtab.Flags.DEFERRED import scala.tools.nsc.util.{BatchSourceFile, NoPosition, Position, SourceFile} import scala.util.NameTransformer class SemanticTokens(val compiler: Global) { import compiler._ object walker extends symtab.SymbolWalker { lazy val global : compiler.type = compiler } abstract class Kind {} object OBJECT extends Kind object CLASS extends Kind object TRAIT extends Kind object DEF extends Kind object VAL extends Kind object VAR extends Kind object ARG extends Kind object TPARAM extends Kind type AnyClass = Class[_] // static constants here abstract class Token { def length: Int def prev: HasNext def next: HasPrev } def eatKeyword(source: BatchSourceFile, pos: Int, keywords: List[String]) : Int = { if (keywords.isEmpty) pos else if (pos == source.length) -1 else if (source.beginsWith(pos, " ")) eatKeywords(source, pos + 1) else if (source.beginsWith(pos, keywords.head + " ")) eatKeywords(source, pos + keywords.head.length + 1) else eatKeyword(source, pos, keywords.tail) } def eatKeywords(source: BatchSourceFile, pos: Int): Int = { val keywords = "package" :: "val" :: "var" :: "def" :: "class" :: "trait" :: "override" :: "case" :: "object" :: "sealed" :: "private" :: "protected" :: Nil if (pos != -1) eatKeyword(source, pos, keywords) else pos } trait HasNext extends Token { var next0: HasPrev = _ def next = next0 } trait HasPrev extends Token { var prev0: HasNext = _ def prev = prev0 } abstract class Actual extends HasNext with HasPrev { def convertToGap: (Int, Actual) = { val nextGap = next.isInstanceOf[Gap] val prevGap = prev.isInstanceOf[Gap] if (prevGap) { val ret = prev.length val gap = prev.asInstanceOf[Gap] gap.setLength(gap.length + length) if (nextGap) { gap.setLength(gap.length + next.length) gap.next0 = next.next next.next.prev0 = gap } else { gap.next0 = next next.prev0 = gap } (ret, gap) } else if (nextGap) { val gap = next.asInstanceOf[Gap] gap.setLength(gap.length + length) gap.prev0 = prev prev.next0 = gap (0, gap) } else { prev.next0 = next next.prev0 = prev val gap = new Gap(prev) gap.setLength(length) (0, gap) } } def insert(prev1: HasNext) { next0 = prev1.next prev0 = prev1 prev0.next0 = this next0.prev0 = this } } // Actual final class Gap extends Actual { def this(prev1: HasNext) = { this() insert(prev1) } override def toString() = "gap-" + length var length0: Int = -1 def length: Int = length0 def setLength(length1: Int) = length0 = length1 // already gap override def convertToGap: (Int, Actual) = (0, this) } def Process(unit: CompilationUnit) = new Process(unit) class Process(val unit: CompilationUnit) { private var doLog = true def source = unit.source def dbg(tree: Tree) = { def treePos: Position = if (tree ne null) tree.pos else NoPosition; ( "TREE=" + tree + (if (tree ne null) (" CLASS=" + tree.getClass()) else "") + " SYM=" + tree.symbol + " POS=" + treePos.dbgString )} val symbols = new HashMap[Symbol, Info] class Info(val symbol: Symbol) { var defined : Def = _ val uses = new HashSet[Use] symbols.update(symbol, this) } def info(symbol: Symbol): Info = if (symbols.contains(symbol)) symbols(symbol) else new Info(symbol) abstract class Semantic(val symbol: Symbol) extends Actual { val name = NameTransformer.decode(symbol.name.toString).trim() assert(symbol != NoSymbol) def myOuter = Process.this def tpe: Type = symbol.tpe def length = name.length() def info: Info = if (symbols.contains(symbol)) symbols(symbol) else new Info(symbol) def kind = { val term0 = symbol if (false) null else if (term0.isVariable) VAR else if (term0.isValueParameter) ARG else if (term0.isMethod) DEF else if (term0.isClass) CLASS else if (term0.isModule) OBJECT else if (term0.isValue) VAL else if (term0.isTypeParameter) TPARAM else if (term0.isType ) TPARAM else { // Console.err.println("UNRECOGNIZED SYMBOL: " + term0 + " " + name); null } } } class Def(symbol0: Symbol) extends Semantic(symbol0) { info.defined = this override def toString() = "def-" + name + "-" + symbol.getClass() } class Use(symbol0: Symbol, tpe0: Type) extends Semantic(symbol0) { info.uses += this override def tpe : Type = if (tpe0 ne null) tpe0 else super.tpe; override def toString() = "use-" + name + "-" + symbol.getClass(); } val list = new TokenList //build(unit.body) val map = new scala.collection.mutable.LinkedHashMap[Int,Symbol] map.clear // populate the map. class visitor extends walker.Visitor { def contains(pos : Position) = map.contains(pos.point) def apply(pos : Position) = map(pos.point) def update(pos : Position, sym : Symbol) : Unit = if (pos.isDefined) { val offset = pos.point map(offset) = sym val isDef = pos.point == sym.pos.point list.put(offset, (if (isDef) new Def(sym) else new Use(sym, NoType))); } } walker.walk(unit.body, new visitor)(offset => unit.source.identifier(offset, compiler)) // ok start building.... def build[T <: Tree](trees: List[T]) { for (tree <- trees) build(tree) } def build(tree0: Tree): Unit = try { /* if (tree0.pos != NoPosition) */ tree0 match { case tree: ImplDef => val pos = eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point) if (pos == -1) { } else buildDef(tree.symbol, eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point)); tree match { case cdef: ClassDef => build(cdef.tparams) case _ => ; } build(tree.impl.parents) build(tree.impl.body) case tree: ValOrDefDef => if (!tree.symbol.hasFlag(Flags.ACCESSOR) || tree.symbol.hasFlag(DEFERRED)) { // MO: I added !tree.symbol.hasFlag(DEFERRED) in a refactoring where // getters now can be abstract whereas before they could not. // Adding the condition thus keeps the old behavior. // todo: review whether this is correct, or whether abstract getters should be included. { val pos : Int = if (tree.name.toString().equals("<init>")) -1 else eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.point); if (false) Console.err.println("VALDEF: tree=" + tree + " sym=" + tree.symbol + " pos0=" + tree.symbol.pos + " alias=" + tree.symbol.alias + " pos1=" + pos + " pos2=" + tree.pos.dbgString + " " + tree.symbol.hasFlag(Flags.SYNTHETIC)); if (pos != -1 && !tree.hasFlag(Flags.SYNTHETIC)) buildDef(tree.symbol, pos); } if (tree.isInstanceOf[DefDef]) { val ddef = tree.asInstanceOf[DefDef]; build(ddef.tparams); for (l0 <- ddef.vparamss; arg <- l0) { val pos0 : Int = if (!unit.source.beginsWith(arg.pos.point, "val ")) arg.pos.point; else unit.source.skipWhitespace(arg.pos.point + ("val ").length()); buildDef(arg.symbol, pos0); build(arg.tpt); } } try { //TPT=scala.Iterator[DocGenerator.this.compiler0.CompilationUnit] 260 class scala.tools.nsc.ast.Trees$TypeTree scala.Iterator[DocGenerator.this.compiler0.CompilationUnit] class scala.tools.nsc.symtab.Types$$anon$5 if ((tree.tpt eq null) || (tree.tpt.tpe eq null)) { //Console.err.println("BAD: " + tree.tpt + " in " + tree); } else { //Console.err.println("TPT=" + tree.tpt + " " + tree.tpt.pos + " " + tree.tpt.getClass() + " " + tree.tpt.tpe + " " + tree.tpt.tpe.getClass() + " " + tree.tpt.tpe.getClass().getSuperclass()); build(tree.tpt); } } catch { case e: Error => Console.err.println("VALDEF: " + tree + " " + tree.tpt + " " + tree.pos + " " + tree.tpt.pos); throw e; } //Console.err.println("RHS: " + tree.rhs + " " + tree.rhs.getClass() + " " + tree.rhs.getClass().getSuperclass()); build(tree.rhs); } case tree: PackageDef => //Console.err.println("PACKAGE: " + tree.name); if (false) { val pos = eatKeywords(unit.source.asInstanceOf[BatchSourceFile], tree.pos.pointOrElse(-1)) if (pos != -1) buildDef(tree.symbol, pos) } build(tree.stats) case tree: Function => for (arg <- tree.vparams if arg.pos != NoPosition) { val name = arg.name.toString().trim() val pos: Int = if (unit.source.beginsWith(arg.pos.pointOrElse(-1), "val ")) unit.source.skipWhitespace(arg.pos.pointOrElse(-1) + ("val ").length()) else if (unit.source.asInstanceOf[BatchSourceFile].content(arg.pos.point) == ':') { var posx : Int = arg.pos.point while (unit.source.asInstanceOf[BatchSourceFile].content(posx - 1).isWhitespace) posx = posx - 1 posx - name.length() } else arg.pos.point buildDef(arg.symbol, pos) build(arg.tpt) } build(tree.body) case tree : TypeTree => val treex = tree val tree1 = if (tree.original ne null) tree.original else tree def classes(clazz: AnyClass): List[AnyClass] = if (clazz eq null) Nil else clazz :: classes(clazz.getSuperclass()) if (tree.original eq null) { if (false) Console.err.println("NO_ORIGINAL: " + tree + " " + tree.tpe + " " + classes(tree.tpe.getClass())); } if (tree.tpe ne null) buildT(tree1, tree.tpe); def buildT( tree : Tree, tpe : Type) : Unit = if (tree.pos != NoPosition) tpe match { case tpe0 : TypeRef => tree match { case apt : AppliedTypeTree => buildUse(tpe.typeSymbol, apt.tpt.pos.pointOrElse(-1), tpe0); //Console.err.println("APT: " + treex + " vs. " + treex.original); //Console.err.println("APT: " + treex.pos + " vs. " + treex.original.pos + " " + unit.source.dbg(treex.original.pos)); //Console.err.println("APT: " + apt.tpt + " sym0=" + apt.tpt.symbol + " sym1=" + tpe0.sym + " apt.args=" + apt.args + " tpe0.args=" + tpe0.args); buildTs (apt.args, tpe0.args); case ident : Ident => buildUse(tpe0.sym, ident.pos.pointOrElse(-1), tpe0); case select : Select => if (select.symbol == NoSymbol) try { // build(select); buildUse(tpe0.typeSymbol, selectPos(select), tpe0); //Console.err.println("QUALIFIER: " + select.qualifier + " " + unit.source.dbg(select.qualifier.pos) + " " + tpe0.prefix + " " + tpe0.prefix.getClass() + " " + tpe0.prefix.getClass().getSuperclass() +" " + tpe0.prefix.widen + " " + tpe0.prefix.toLongString); buildT(select.qualifier, tpe0.prefix); } catch { case e : Error => Console.err.println("BUILD_SELECT: " + select + " @ " + tpe0 + " " + (select.pos).dbgString); throw e; } case tpt : TypeTree => if (tpt.symbol ne null) { Console.err.println("SYM0 " + tpt.symbol + " " + (tpt.pos).dbgString); buildUse(tpt.symbol, tpt.pos.pointOrElse(-1), tpe0); } else if (tpe0.typeSymbol ne null) { //Console.err.println("TYPE_SYM1 " + tpe0.symbol + " " + unit.source.dbg(tpt.pos)); buildUse(tpe0.typeSymbol, tpt.pos.pointOrElse(-1), tpe0); } else { Console.err.println("UNKNOWN TPT0: " + (tpt.pos).dbgString + " tpt=" + tpt + " " + tpt.symbol + " tpe0="+ tpe0 + " " + tpe0.typeSymbol + " tpe0.args=" + tpe0.args); } case sft : SelectFromTypeTree => build(sft.qualifier); // XXX: broken if (false) Console.err.println("SFTT: " + sft + " sym=" + sft.symbol + " name=" + sft.name + " qual=" + sft.qualifier + " qual.sym=" + sft.qualifier.symbol + " qual.pos=" + (sft.qualifier.pos).dbgString + " symbol=" + sft.symbol + " type=" + tpe0 + " type.sym=" + tpe0.typeSymbol); case _ => Console.err.println("UNKNOWN TPT2: " + tree + " vs. " + tpe0 + " " + tree.getClass() + " " + (tree.pos).dbgString); } case tpe0 : MethodType => tree match { case tpt: TypeTree => if (tpt.original ne null) buildT(tpt.original, tpe); else { Console.err.println("UNKNOWN TPT3: " + tree + " vs. " + tpe0 + " " + (tree.pos).dbgString); } case ident : Ident => buildT(ident, tpe0.resultType); case select : Select => buildT(select, tpe0.resultType); case _ => Console.err.println("UNKNOWN TPE: " + tree + " vs. " + tpe0 + " " + tree.getClass()); } case tpe0 : RefinedType => tree match { case cpt : CompoundTypeTree => buildTs(cpt.templ.parents, tpe0.parents); case _ : TypeTree => // Console.err.println("UNKNOWN TPE13: " + dbg(tree) + " tpe0=" + tpe0 + " " + tpe0.parents); case _ => if (false) Console.err.println("UNKNOWN TPE5: " + dbg(tree) + " tpe0=" + tpe0 + " " + tpe0.parents); } case tpe0 : ThisType => tree match { case stt : SingletonTypeTree => stt.ref match { case ths : This => build(ths); case _ => Console.err.println("UNKNOWN TPE11: " + tpe0 + " " + stt + " " + stt.ref + " " + stt.ref.getClass() + " " + (tree.pos).dbgString); } case tt : This => case _ : Ident => case _ : Select => case tt : TypeTree => if (false) Console.err.println("UNKNOWN TPE12: " + tpe0 + " " + tree + " " + tree.getClass() + " " + (tree.pos).dbgString); case _ => if (false) Console.err.println("UNKNOWN TPE10: " + tpe0 + " " + tree + " " + tree.getClass() + " " + (tree.pos).dbgString); } case tpe0 : SingleType => tree match { case ident : Ident => buildUse(tpe0.sym, ident.pos.pointOrElse(-1), tpe0); case select : Select => buildUse(tpe0.termSymbol, selectPos(select), tpe0); //Console.err.println("QUALIFIER-0: " + select.qualifier + " " + unit.source.dbg(select.qualifier.pos) + " " + tpe0.prefix + " " + tpe0.prefix.getClass() + " " + tpe0.prefix.getClass().getSuperclass() +" " + tpe0.prefix.widen + " " + tpe0.prefix.toLongString); buildT(select.qualifier, tpe0.prefix); case _ => if (false) Console.err.println("UNKNOWN TPE8: " + tree + " " + (tree.pos).dbgString + " TPE=" + tpe0 + " PRE=" + tpe0.pre + " SYM=" + tpe0.sym); } case ctype : ConstantType => case ErrorType => case _ => { if (false) Console.err.println("UNKNOWN TPE4: " + tree + " " + tpe + " " + tpe.getClass() + " " + (tree.pos).dbgString); } }; def buildTs(trees : List[Tree], types : List[Type]): Unit = if (!trees.isEmpty && !types.isEmpty) { buildT (trees.head, types.head); buildTs(trees.tail, types.tail); } else if (trees.isEmpty != types.isEmpty) { if (false && doLog) { Console.println("" + treex + " vs. " + treex.original); if (treex.original ne null) Console.println("" + treex.tpe + " vs. " + treex.original.tpe); logError("Tree vs. Type mismatch: " + trees + " " + types + " " + (tree.pos).dbgString, null); doLog = false; } }; /* Martin to Sean: I don't understand why AbsTypeDef is different from AliasTypeDef. Why is the order reversed? Why two buildDefs for tree.symbol vs one for AliasTypeDef? case tree: AbsTypeDef => //Console.err.println("ABS: " + tree.symbol + " " + unit.source.dbg(tree.namePos) + " " + tree.pos.dbgString); buildDef(tree.symbol, tree.namePos) buildDef(tree.symbol, tree.pos.pointOrElse(-1)) build(tree.tparams); //@M build(tree.lo) build(tree.hi) */ case tree: Bind => buildDef(tree.symbol, tree.pos.pointOrElse(-1)) build(tree.body) case tree: Ident => buildUse(tree.symbol, tree.pos.pointOrElse(-1), tree.tpe) case tree: Select => try { build(tree.qualifier) } catch { case e : Error => Console.err.println("SELECTQ: " + tree + " " + tree.qualifier + " " + (tree.qualifier.pos).dbgString); throw e; } try { if (tree.pos.isDefined && tree.pos.point >= unit.source.length) { if (false) Console.err.println("BAD_SELECT_QUALIFIER " + tree + " @ " + (tree.pos).dbgString); } else { //Console.err.println("SELECT-0: " + tree.symbol + " " + tree.pos.dbgString + " " + (tree.pos - selectPos(tree))); buildUse(tree.symbol, selectPos(tree), tree.tpe); } } catch { case e : Error => Console.err.println("SELECTU: " + tree + " " + tree.symbol + " " + tree.pos.dbgString); throw e; } case tree: TypeApply => //Console.err.println("TYPE_APPLY: " + tree + " " + tree.pos.dbgString); if (!tree.args.isEmpty) { //Console.err.println("ARGS: " + unit.source.dbg(tree.args0.head.pos)); } build(tree.fun) build(tree.args) case tree: Apply => build(tree.fun) build(tree.args) case tree: GenericApply => build(tree.fun) build(tree.args) case tree: Typed => build(tree.expr) build(tree.tpt) case tree: Block => if (false) { if (!tree.stats.isEmpty) Console.err.println("BLOCKS: " + tree.stats.head + " " + tree.stats.head.getClass()); Console.err.println("BLOCKE: " + tree.expr + " " + tree.expr.getClass()) } build(tree.stats) build(tree.expr) case tree: CaseDef => build(tree.pat) build(tree.guard) build(tree.body) case tree : Sequence => build(tree.trees); case tree : Assign => build(tree.lhs); build(tree.rhs); case tree : If => build(tree.cond); build(tree.thenp); build(tree.elsep); case tree : New => //Console.err.println("NEW: " + tree.tpt + " " + tree.tpt.getClass()); build(tree.tpt); case tree : Match => build(tree.selector); build(tree.cases); case tree : Return => build(tree.expr); case tree : LabelDef => build(tree.rhs); case tree : Throw => build(tree.expr); case tree : Try => build(tree.block); build(tree.catches); build(tree.finalizer); case tree : Alternative => build(tree.trees); case tree : This => if (tree.symbol ne null) buildUse(tree.symbol, tree.pos.pointOrElse(-1), tree.tpe); //Thread.dumpStack(); case tree : TypeDef => //Console.err.println("ALIAS: " + tree); build(tree.rhs); build(tree.tparams); buildDef(tree.symbol, tree.pos.pointOrElse(-1)); case tree : DocDef => build(tree.definition); case tree: Import => build(tree.expr) case tree: AppliedTypeTree => ; case tree: Annotated => ; case tree: SingletonTypeTree => ; case tree: Super => ; case tree: Literal => ; case EmptyTree => ; case _ => ; Console.err.println("BAIL: " + (tree0.pos) + " " + tree0 + " " + tree0.getClass()); } } catch { case t: Throwable => logError("Error occured at " + (tree0.pos), t) } def buildUse(term: Symbol, pos: Int, tpe: Type) = buildSym(term, pos, false, tpe) def buildDef(term: Symbol, pos: Int) = buildSym(term, pos, true, null) def buildSym(term: Symbol, pos: Int, isDef: Boolean, tpe: Type): Unit = if (term.hasFlag(Flags.ACCESSOR)) buildSym(analyzer.underlying(term), pos, isDef, tpe) else if (pos == -1) { //Console.err.println("NOPOS: " + term) //Thread.dumpStack() } else if (term != NoSymbol) { val name = NameTransformer.decode(term.name.toString).trim() val buf = unit.source.asInstanceOf[BatchSourceFile].content val cs = name.toChars var idx = 0 if (cs.length + pos > buf.length) return else while (idx < cs.length) { if (buf(pos + idx) != cs(idx)) { //Console.err.println("MISMATCH: " + name + "[" + idx + "] " + unit.source.dbg(pos)); //Thread.dumpStack(); return; } else idx = idx + 1; } if (cs.length + pos + 1 < buf.length) { if (isJavaIdentifierPart(buf(pos + cs.length))) { //Console.err.println("MISMATCH: " + name + "[last] " + unit.source.dbg(pos)); return; } } try { list.put(pos, (if (isDef) new Def(term) else new Use(term, tpe))); } catch { case e : Error => e.printStackTrace(); } } def selectPos(tree: Select): Int = if (tree.pos == NoPosition) -1 else { val buf = unit.source.asInstanceOf[BatchSourceFile].content if (tree.pos.point >= buf.length) { if (false) { Console.err.println("" + tree + "@" + tree.pos + " not in " + unit.source.file.name + "[" + buf.length + "]"); Thread.dumpStack() throw new Error() } return 0 } val pos : Int = if (buf(tree.pos.point) != '.') tree.pos.point else { def f(x : Int) : Int = { if (buf(x).isWhitespace) f(x + 1) else x } f(tree.pos.point + 1) } pos }; class TokenList { object begin extends HasNext { def prev = this def length = 0 } object end extends HasPrev { def next = this def length = 0 } // initialize begin.next0 = end end.prev0 = begin def tokenAt(offset: Int) = { cursor.seek(offset) if (cursor.token.isInstanceOf[Semantic]) cursor.token.asInstanceOf[Semantic] else null } def put(offset: Int, tok: Actual): Unit = tok match { case tok0: Semantic => put(offset, tok0) case gap: Gap => } def put(offset: Int, tok: Semantic) { cursor.seek(offset); if (cursor.token == end) { assert(offset >= cursor.offset); if (offset > cursor.offset) { // add a gap. val gap = new Gap(end.prev); gap.setLength(offset - cursor.offset); cursor.offset = offset; } // append. tok.insert(end.prev); cursor.offset = cursor.offset + tok.length; } else if (!cursor.token.isInstanceOf[Gap]) { val sem = cursor.token.asInstanceOf[Semantic]; if (sem.symbol == tok.symbol) return; if (sem.symbol != tok.symbol && sem.symbol.getClass() == tok.symbol.getClass() && sem.symbol.pos == tok.symbol.pos) return; } else { val gap = cursor.token.asInstanceOf[Gap]; if (!(offset - cursor.offset + tok.length <= gap.length)) { Console.err.println("LIST =" + this); Console.err.println("OFFSET=" + offset + " " + tok + " " + tok.length); Console.err.println(" " + cursor.offset + " " + gap.length); gap.length0 = offset - cursor.offset + tok.length //throw new Error(); } if (offset == cursor.offset) { // replace or prepend tok.prev0 = gap.prev0; if (tok.length == gap.length) { // replace gap tok.next0 = gap.next0; } else { gap.setLength(gap.length - tok.length); tok.next0 = gap; } tok.next0.prev0 = tok; tok.prev0.next0 = tok; cursor.token = tok; } else { // append val diff = (cursor.offset + gap.length) - (offset + tok.length); gap.setLength(gap.length - tok.length - diff); tok.prev0 = gap; tok.next0 = gap.next; tok.next0.prev0 = tok; tok.prev0.next0 = tok; if (diff != 0) { val gap0 = new Gap(tok); gap0.setLength(diff); } } } } override def toString(): String = { var node = begin.next var str = "" while (node != end) { str = str + " " + node node = node.next } str } object cursor { var token: Token = end var offset: Int = 0 def next: Unit = if (token == end) end else { offset = offset + token.length token = token.next } def prev: Unit = if (token.prev == begin) token else { offset = offset - token.prev.length token = token.prev } def seek(soffset: Int): Unit = if (soffset == 0) { token = begin.next offset = 0 } else { assert(soffset > 0) while (offset > soffset) prev; while (offset + token.length <= soffset && token != end) { val len0 = offset; next; } } def convertToGap = if (token.isInstanceOf[Actual]) { val ret = token.asInstanceOf[Actual].convertToGap; offset = offset - ret._1; token = ret._2; } } // add or delete characters def adjust(offset: Int, /* where */ length: Int, /* how many characters are modified */ to : Int /* length of new string */) = { cursor.seek(offset) if (cursor.token != end) { cursor.convertToGap while (cursor.offset + cursor.token.length < offset + length && cursor.token.next != end) { val save = cursor.offset cursor.next cursor.convertToGap assert(cursor.offset == save) } if (length != to && cursor.token != end) { val diff = to - length; val gap = cursor.token.asInstanceOf[Gap]; gap.setLength(gap.length + diff); }; } } } // TokenList } }