PageRenderTime 26ms CodeModel.GetById 6ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/scala-2.13/de/sciss/scalainterpreter/impl/AbstractScalaCompleter.scala

https://github.com/Sciss/ScalaInterpreterPane
Scala | 266 lines | 139 code | 32 blank | 95 comment | 50 complexity | a5720975c72c966de19064267a0a8776 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /*
  2. * Scala (https://www.scala-lang.org)
  3. *
  4. * Copyright EPFL and Lightbend, Inc.
  5. *
  6. * Licensed under Apache License 2.0
  7. * (http://www.apache.org/licenses/LICENSE-2.0).
  8. *
  9. * See the NOTICE file distributed with this work for
  10. * additional information regarding copyright ownership.
  11. */
  12. package de.sciss.scalainterpreter.impl
  13. import de.sciss.scalainterpreter.{Completer, Completion}
  14. import scala.tools.nsc.interpreter.{IMain, PresentationCompilationResult}
  15. import scala.util.control.NonFatal
  16. // cf. PresentationCompilerCompleter
  17. object AbstractScalaCompleter {
  18. private final case class Request(line: String, cursor: Int)
  19. private val NoRequest = Request("", -1)
  20. private final val DEBUG = false
  21. }
  22. abstract class AbstractScalaCompleter(protected final val intp: IMain) extends Completer {
  23. import AbstractScalaCompleter._
  24. import intp.{PresentationCompileResult => PCResult}
  25. // private type Handler = Result => Completion.Result // Candidates
  26. private[this] var lastRequest = NoRequest
  27. private[this] var tabCount = 0
  28. private[this] var lastCommonPrefixCompletion = Option.empty[String]
  29. private def longestCommonPrefix(xs: List[String]): String = xs match {
  30. case Nil => ""
  31. case w :: Nil => w
  32. case _ =>
  33. // XXX TODO --- that does not look very efficient
  34. def lcp(ss: List[String]): String = {
  35. val w :: ws = ss
  36. if (w == "") ""
  37. else if (ws exists (s => s == "" || (s charAt 0) != (w charAt 0))) ""
  38. else w.substring(0, 1) + lcp(ss map (_ substring 1))
  39. }
  40. lcp(xs)
  41. }
  42. protected def presentationCompile(cursor: Int, buf: String): Option[PresentationCompilationResult]
  43. override def complete(buf: String, cursor: Int, _tabCount: Int): Completion.Result = {
  44. val request = Request(buf, cursor)
  45. if (_tabCount >= 0) {
  46. tabCount = _tabCount
  47. } else if (request == lastRequest) {
  48. tabCount += 1
  49. } else {
  50. tabCount = 0
  51. }
  52. lastRequest = request
  53. if (DEBUG) println(s"""complete("$buf", cursor = $cursor, _tabCount = ${_tabCount}); tabCount = $tabCount""")
  54. // secret handshakes
  55. // val slashPrint = """.*// *print *""".r
  56. // val slashPrintRaw = """.*// *printRaw *""".r
  57. // val slashTypeAt = """.*// *typeAt *(\d+) *(\d+) *""".r
  58. // val Cursor = IMain.DummyCursorFragment + " "
  59. // def print(result: Result) = {
  60. // val offset = result.preambleLength
  61. // val pos1 = result.unit.source.position(offset).withEnd(offset + buf.length)
  62. // import result.compiler._
  63. // val tree = new Locator(pos1) locateIn result.unit.body match {
  64. // case Template(_, _, _ /* constructor */ :: (rest :+ last)) => if (rest.isEmpty) last else Block(rest, last)
  65. // case t => t
  66. // }
  67. // val printed = showCode(tree) + " // : " + tree.tpe.safeToString
  68. // Candidates(cursor, "" :: printed :: Nil)
  69. // }
  70. // def typeAt(result: Result, start: Int, end: Int) = {
  71. // val tpString = result.compiler.exitingTyper(result.typedTreeAt(buf, start, end).tpe.toString)
  72. // Candidates(cursor, "" :: tpString :: Nil)
  73. // }
  74. def candidates(result: PCResult): Completion.Result /* Candidates */ = {
  75. import result.compiler._
  76. import CompletionResult._
  77. def defStringCandidates(matching: List[Member], name: Name): Completion.Result /* Candidates */ = {
  78. val defStrings: List[Completion.Def] = for {
  79. member <- matching
  80. if member.symNameDropLocal == name
  81. sym <- member.sym.alternatives
  82. sugared = sym.sugaredSymbolOrSelf
  83. } yield {
  84. val tp = member.prefix.memberType(sym)
  85. // println(
  86. // s"isAliasType ${sugared.isAliasType}; " +
  87. // s"isCaseAccessorMethod ${sugared.isCaseAccessorMethod}; " +
  88. // s"isCaseApplyOrUnapply ${sugared.isCaseApplyOrUnapply}; " +
  89. // s"isModule ${sugared.isModule}; " +
  90. // s"isOmittablePrefix ${sugared.isOmittablePrefix}; " +
  91. // s"isType ${sugared.isType}; " +
  92. // s"isValue ${sugared.isValue}; " +
  93. // s"isVariable ${sugared.isVariable}"
  94. // )
  95. val info: String =
  96. if (sugared.isType) {
  97. typeParamsString(tp)
  98. }
  99. else if (sugared.isModule) {
  100. // val modSym = sugared.asModule
  101. ""
  102. } else tp match {
  103. case PolyType(_, ret) =>
  104. val info0 = typeParamsString(tp)
  105. // XXX TODO -- a bit of DRY
  106. val info1 = ret match {
  107. case MethodType(params, _) => params.map(_.defString).mkString("(", ",", ")")
  108. case _ => ""
  109. }
  110. info0 + info1
  111. case MethodType(params, _) => params.map(_.defString).mkString("(", ",", ")")
  112. case _ => ""
  113. }
  114. val n = sugared.nameString
  115. // val s = sugared.defStringSeenAs(tp)
  116. Completion.Def(n, info, isModule = sugared.isModule)
  117. }
  118. // XXX TODO : why is this used in the original code, but does not appear in the results?
  119. // val empty: Completion.Candidate = new Completion.Candidate {
  120. // def stringRep: String = ""
  121. // } // ""
  122. val dist = defStrings.distinct
  123. // println("distinct:")
  124. // dist.foreach(println)
  125. Completion.Result(cursor, /* empty :: */ dist)
  126. }
  127. val pcResult = result.completionsAt(cursor)
  128. val found = pcResult match {
  129. case NoResults => Completion.NoResult // NoCandidates
  130. case r =>
  131. def shouldHide(m: Member): Boolean = {
  132. val isUniversal = definitions.isUniversalMember(m.sym)
  133. def viaUniversalExtensionMethod: Boolean = m match {
  134. case t: TypeMember if t.implicitlyAdded && t.viaView.info.params.head.info.bounds.isEmptyBounds => true
  135. case _ => false
  136. }
  137. (
  138. isUniversal && nme.isReplWrapperName(m.prefix.typeSymbol.name)
  139. || isUniversal && tabCount == 0 && r.name.isEmpty
  140. || viaUniversalExtensionMethod && tabCount == 0 && r.name.isEmpty
  141. )
  142. }
  143. val matching: List[Member] = r.matchingResults().filterNot(shouldHide)
  144. val tabAfterCommonPrefixCompletion = lastCommonPrefixCompletion.contains(buf.substring(0, cursor)) &&
  145. matching.exists(_.symNameDropLocal == r.name)
  146. val doubleTab = tabCount > 0 && matching.forall(_.symNameDropLocal == r.name)
  147. // println(s"tabAfterCommonPrefixCompletion = $tabAfterCommonPrefixCompletion, doubleTab = $doubleTab")
  148. val mkDef = tabAfterCommonPrefixCompletion || doubleTab
  149. // XXX TODO: removed due to https://github.com/scala/scala/pull/9656
  150. def tryCamelStuff: Completion.Result = Completion.NoResult
  151. // def tryCamelStuff: Completion.Result = {
  152. // // Lenient matching based on camel case and on eliding JavaBean "get" / "is" boilerplate
  153. // val camelMatches : List[Member ] = r.matchingResults(CompletionResult.camelMatch(_)).filterNot(shouldHide)
  154. // val memberCompletions : List[String ] = camelMatches.map(_.symNameDropLocal.decoded).distinct.sorted
  155. //
  156. // def allowCompletion: Boolean =
  157. // (memberCompletions.size == 1) ||
  158. // CompletionResult.camelMatch(r.name).apply {
  159. // val pre = longestCommonPrefix(memberCompletions)
  160. // r.name.newName(pre)
  161. // }
  162. //
  163. // val memberCompletionsF: List[Completion.Candidate] =
  164. // memberCompletions.map(Completion.Simple)
  165. //
  166. // if (memberCompletions.isEmpty) {
  167. // Completion.NoResult
  168. // } else if (allowCompletion) {
  169. // Completion.Result(cursor - r.positionDelta, memberCompletionsF)
  170. // } else {
  171. // // XXX TODO : why is this used in the original code, but does not appear in the results?
  172. // // val empty: Completion.Candidate = new Completion.Candidate {
  173. // // def stringRep: String = ""
  174. // // } // ""
  175. // Completion.Result(cursor, /* empty :: */ memberCompletionsF)
  176. // }
  177. // }
  178. if (mkDef) {
  179. val attempt = defStringCandidates(matching, r.name)
  180. if (attempt.candidates.nonEmpty) attempt else tryCamelStuff
  181. } else if (matching.isEmpty) {
  182. tryCamelStuff
  183. } else if (matching.nonEmpty && matching.forall(_.symNameDropLocal == r.name)) {
  184. Completion.NoResult // don't offer completion if the only option has been fully typed already
  185. } else {
  186. // regular completion
  187. val memberCompletions: List[String] = matching.map { member =>
  188. val raw: Name = member.symNameDropLocal
  189. raw.decoded
  190. } .distinct.sorted
  191. val memberCompletionsF: List[Completion.Candidate] = memberCompletions.map(Completion.Simple)
  192. Completion.Result(cursor - r.positionDelta, memberCompletionsF)
  193. }
  194. }
  195. lastCommonPrefixCompletion =
  196. if (found != Completion.NoResult && buf.length >= found.cursor) {
  197. val pre = buf.substring(0, found.cursor)
  198. val cs = found.candidates.collect {
  199. case Completion.Simple(s) => s
  200. }
  201. val suf = longestCommonPrefix(cs)
  202. Some(pre + suf)
  203. } else {
  204. None
  205. }
  206. found
  207. }
  208. // Note: in Scala 2.13, `presentationCompile` takes
  209. // care of inserting the `Cursor` cookie into the buffer!
  210. // val bufMarked = buf.patch(cursor, Cursor, 0)
  211. try {
  212. val either = presentationCompile(cursor, buf)
  213. either match {
  214. case None => Completion.NoResult // NoCandidates
  215. case Some(result: PCResult) => try { // urg. let's hope we get this specific one
  216. // buf match {
  217. // case slashPrint() if cursor == buf.length =>
  218. // val c = print(result)
  219. // c.copy(candidates = c.candidates.map(intp.naming.unmangle))
  220. // case slashPrintRaw() if cursor == buf.length => print(result)
  221. // case slashTypeAt(start, end) if cursor == buf.length => typeAt(result, start.toInt, end.toInt)
  222. // case _ =>
  223. candidates(result)
  224. // }
  225. } finally result.cleanup()
  226. case Some(_) => Completion.NoResult
  227. }
  228. } catch {
  229. case NonFatal(_) =>
  230. // if (isReplDebug) e.printStackTrace()
  231. Completion.NoResult // NoCandidates
  232. }
  233. }
  234. }