Skip to content

Commit 7345d9c

Browse files
committed
metals initial version: 41e96ee33f82 copied into dotty
1 parent 42726d6 commit 7345d9c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+9426
-0
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
package scala.meta.internal.mtags
2+
3+
import scala.annotation.tailrec
4+
import scala.util.control.NonFatal
5+
6+
import scala.meta.internal.jdk.CollectionConverters.*
7+
import scala.meta.internal.pc.MetalsInteractive
8+
import scala.meta.internal.pc.SemanticdbSymbols
9+
import scala.meta.pc.OffsetParams
10+
import scala.meta.pc.RangeParams
11+
import scala.meta.pc.SymbolDocumentation
12+
import scala.meta.pc.SymbolSearch
13+
14+
import dotty.tools.dotc.ast.tpd.*
15+
import dotty.tools.dotc.core.Contexts.*
16+
import dotty.tools.dotc.core.Denotations.*
17+
import dotty.tools.dotc.core.Flags.*
18+
import dotty.tools.dotc.core.NameOps.*
19+
import dotty.tools.dotc.core.Names.*
20+
import dotty.tools.dotc.core.StdNames.*
21+
import dotty.tools.dotc.core.SymDenotations.NoDenotation
22+
import dotty.tools.dotc.core.Symbols.*
23+
import dotty.tools.dotc.core.Types.AppliedType
24+
import dotty.tools.dotc.core.Types.Type
25+
import dotty.tools.dotc.interactive.Interactive
26+
import dotty.tools.dotc.interactive.InteractiveDriver
27+
import dotty.tools.dotc.util.SourcePosition
28+
import dotty.tools.dotc.util.Spans
29+
import dotty.tools.dotc.util.Spans.Span
30+
import org.eclipse.{lsp4j as l}
31+
32+
object MtagsEnrichments extends ScalametaCommonEnrichments:
33+
34+
extension (driver: InteractiveDriver)
35+
36+
def sourcePosition(
37+
params: OffsetParams
38+
): SourcePosition =
39+
val uri = params.uri
40+
val source = driver.openedFiles(uri)
41+
val span = params match
42+
case p: RangeParams if p.offset != p.endOffset =>
43+
p.trimWhitespaceInRange.fold {
44+
Spans.Span(p.offset, p.endOffset)
45+
} {
46+
case trimmed: RangeParams =>
47+
Spans.Span(trimmed.offset, trimmed.endOffset)
48+
case offset =>
49+
Spans.Span(p.offset, p.offset)
50+
}
51+
case _ => Spans.Span(params.offset)
52+
53+
new SourcePosition(source, span)
54+
end sourcePosition
55+
56+
def localContext(params: OffsetParams): Context =
57+
if driver.currentCtx.run.units.isEmpty then
58+
throw new RuntimeException(
59+
"No source files were passed to the Scala 3 presentation compiler"
60+
)
61+
val unit = driver.currentCtx.run.units.head
62+
val pos = driver.sourcePosition(params)
63+
val newctx = driver.currentCtx.fresh.setCompilationUnit(unit)
64+
val tpdPath =
65+
Interactive.pathTo(newctx.compilationUnit.tpdTree, pos.span)(using
66+
newctx
67+
)
68+
MetalsInteractive.contextOfPath(tpdPath)(using newctx)
69+
end localContext
70+
71+
end extension
72+
73+
extension (pos: SourcePosition)
74+
def offsetToPos(offset: Int): l.Position =
75+
// dotty's `SourceFile.column` method treats tabs incorrectly.
76+
// If a line starts with tabs, they just don't count as symbols, resulting in a wrong editRange.
77+
// see: https://github.com/scalameta/metals/pull/3702
78+
val lineStartOffest = pos.source.startOfLine(offset)
79+
val line = pos.source.offsetToLine(lineStartOffest)
80+
val column = offset - lineStartOffest
81+
new l.Position(line, column)
82+
83+
def toLsp: l.Range =
84+
new l.Range(
85+
offsetToPos(pos.start),
86+
offsetToPos(pos.end),
87+
)
88+
89+
def withEnd(end: Int): SourcePosition =
90+
pos.withSpan(pos.span.withEnd(end))
91+
92+
def withStart(end: Int): SourcePosition =
93+
pos.withSpan(pos.span.withStart(end))
94+
95+
def focusAt(point: Int): SourcePosition =
96+
pos.withSpan(pos.span.withPoint(point).focus)
97+
98+
def toLocation: Option[l.Location] =
99+
for
100+
uri <- InteractiveDriver.toUriOption(pos.source)
101+
range <- if pos.exists then Some(pos.toLsp) else None
102+
yield new l.Location(uri.toString, range)
103+
104+
def encloses(other: SourcePosition): Boolean =
105+
pos.start <= other.start && pos.end >= other.end
106+
107+
def encloses(other: RangeParams): Boolean =
108+
pos.start <= other.offset() && pos.end >= other.endOffset()
109+
end extension
110+
111+
extension (pos: RangeParams)
112+
def encloses(other: SourcePosition): Boolean =
113+
pos.offset() <= other.start && pos.endOffset() >= other.end
114+
115+
extension (sym: Symbol)(using Context)
116+
def fullNameBackticked: String = fullNameBackticked(Set.empty)
117+
118+
def fullNameBackticked(exclusions: Set[String]): String =
119+
@tailrec
120+
def loop(acc: List[String], sym: Symbol): List[String] =
121+
if sym == NoSymbol || sym.isRoot || sym.isEmptyPackage then acc
122+
else if sym.isPackageObject then loop(acc, sym.owner)
123+
else
124+
val v = this.nameBackticked(sym)(exclusions)
125+
loop(v :: acc, sym.owner)
126+
loop(Nil, sym).mkString(".")
127+
128+
def decodedName: String = sym.name.decoded
129+
130+
def companion: Symbol =
131+
if sym.is(Module) then sym.companionClass else sym.companionModule
132+
133+
def nameBackticked: String = nameBackticked(Set.empty)
134+
135+
def nameBackticked(exclusions: Set[String]): String =
136+
KeywordWrapper.Scala3.backtickWrap(sym.decodedName, exclusions)
137+
138+
def withUpdatedTpe(tpe: Type): Symbol =
139+
val upd = sym.copy(info = tpe)
140+
val paramsWithFlags =
141+
sym.paramSymss
142+
.zip(upd.paramSymss)
143+
.map((l1, l2) =>
144+
l1.zip(l2)
145+
.map((s1, s2) =>
146+
s2.flags = s1.flags
147+
s2
148+
)
149+
)
150+
upd.rawParamss = paramsWithFlags
151+
upd
152+
end withUpdatedTpe
153+
154+
// Returns true if this symbol is locally defined from an old version of the source file.
155+
def isStale: Boolean =
156+
sym.sourcePos.span.exists && {
157+
val source = ctx.source
158+
if source ne sym.source then
159+
!source.content.startsWith(
160+
sym.decodedName.toString(),
161+
sym.sourcePos.span.point,
162+
)
163+
else false
164+
}
165+
end extension
166+
167+
extension (name: Name)(using Context)
168+
def decoded: String = name.stripModuleClassSuffix.show
169+
170+
extension (s: String)
171+
def backticked: String =
172+
KeywordWrapper.Scala3.backtickWrap(s)
173+
174+
def stripBackticks: String = s.stripPrefix("`").stripSuffix("`")
175+
176+
extension (search: SymbolSearch)
177+
def symbolDocumentation(symbol: Symbol)(using
178+
Context
179+
): Option[SymbolDocumentation] =
180+
def toSemanticdbSymbol(symbol: Symbol) =
181+
SemanticdbSymbols.symbolName(
182+
if !symbol.is(JavaDefined) && symbol.isPrimaryConstructor then
183+
symbol.owner
184+
else symbol
185+
)
186+
val sym = toSemanticdbSymbol(symbol)
187+
val documentation = search.documentation(
188+
sym,
189+
() => symbol.allOverriddenSymbols.map(toSemanticdbSymbol).toList.asJava,
190+
)
191+
if documentation.isPresent then Some(documentation.get())
192+
else None
193+
end symbolDocumentation
194+
end extension
195+
196+
extension (tree: Tree)
197+
def qual: Tree =
198+
tree match
199+
case Apply(q, _) => q.qual
200+
case TypeApply(q, _) => q.qual
201+
case AppliedTypeTree(q, _) => q.qual
202+
case Select(q, _) => q
203+
case _ => tree
204+
205+
def seenFrom(sym: Symbol)(using Context): (Type, Symbol) =
206+
try
207+
val pre = tree.qual
208+
val denot = sym.denot.asSeenFrom(pre.tpe.widenTermRefExpr)
209+
(denot.info, sym.withUpdatedTpe(denot.info))
210+
catch case NonFatal(e) => (sym.info, sym)
211+
end extension
212+
213+
extension (imp: Import)
214+
def selector(span: Span)(using Context): Option[Symbol] =
215+
for sel <- imp.selectors.find(_.span.contains(span))
216+
yield imp.expr.symbol.info.member(sel.name).symbol
217+
218+
extension (denot: Denotation)
219+
def allSymbols: List[Symbol] =
220+
denot match
221+
case MultiDenotation(denot1, denot2) =>
222+
List(
223+
denot1.allSymbols,
224+
denot2.allSymbols,
225+
).flatten
226+
case NoDenotation => Nil
227+
case _ =>
228+
List(denot.symbol)
229+
230+
extension (path: List[Tree])
231+
def expandRangeToEnclosingApply(
232+
pos: SourcePosition
233+
)(using Context): List[Tree] =
234+
def tryTail(enclosing: List[Tree]): Option[List[Tree]] =
235+
enclosing match
236+
case Nil => None
237+
case head :: tail =>
238+
head match
239+
case t: GenericApply
240+
if t.fun.srcPos.span.contains(
241+
pos.span
242+
) && !t.tpe.isErroneous =>
243+
tryTail(tail).orElse(Some(enclosing))
244+
case in: Inlined =>
245+
tryTail(tail).orElse(Some(enclosing))
246+
case New(_) =>
247+
tail match
248+
case Nil => None
249+
case Select(_, _) :: next =>
250+
tryTail(next)
251+
case _ =>
252+
None
253+
case sel @ Select(qual, nme.apply) if qual.span == sel.nameSpan =>
254+
tryTail(tail).orElse(Some(enclosing))
255+
case _ =>
256+
None
257+
path match
258+
case head :: tail =>
259+
tryTail(tail).getOrElse(path)
260+
case _ =>
261+
List(EmptyTree)
262+
end expandRangeToEnclosingApply
263+
end extension
264+
265+
extension (tpe: Type)
266+
def metalsDealias(using Context): Type =
267+
tpe.dealias match
268+
case app @ AppliedType(tycon, params) =>
269+
// we dealias applied type params by hand, because `dealias` doesn't do it
270+
AppliedType(tycon, params.map(_.metalsDealias))
271+
case dealised => dealised
272+
273+
end MtagsEnrichments

0 commit comments

Comments
 (0)