mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
improve LSP
This commit is contained in:
1 parent
87480b150a
commit
a60ba27bad
9 files changed
+569
-147
No files matched your search
@@ -3,6 +3,7 @@
|
||||
.idea/developer-tools.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf/
|
||||
.junie/memory/
|
||||
build/
|
||||
dist/
|
||||
output/
|
||||
|
||||
@@ -31,4 +31,8 @@ class AsyncExecutor {
|
||||
workerThread.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS)
|
||||
}
|
||||
}
|
||||
|
||||
fun flush() {
|
||||
workerThread.submit { }.get()
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ fun main(args: Array<String>) {
|
||||
val launcher = LSPLauncher.createServerLauncher(server, inStream, outStream, threads) { it }
|
||||
|
||||
server.connect(launcher.remoteProxy)
|
||||
Logger.getLogger("").info("Prog8 Language Server started. Prog8 version: $VERSION")
|
||||
launcher.startListening()
|
||||
|
||||
println("Prog8 Language Server started. Prog8 version: $VERSION")
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import org.eclipse.lsp4j.services.LanguageClient
|
||||
import org.eclipse.lsp4j.services.TextDocumentService
|
||||
import prog8lsp.SymbolLookup.SymbolAtPosition
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.Logger
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -34,20 +35,20 @@ data class AstCache(
|
||||
|
||||
class Prog8TextDocumentService: TextDocumentService {
|
||||
private var client: LanguageClient? = null
|
||||
private val async = AsyncExecutor()
|
||||
internal val async = AsyncExecutor()
|
||||
private val logger = Logger.getLogger(Prog8TextDocumentService::class.simpleName).apply {
|
||||
level = if (isLspVerbose) Level.CONFIG else Level.INFO
|
||||
}
|
||||
private val symbolExtractor = SymbolExtractor()
|
||||
|
||||
// In-memory document store
|
||||
private val documents = mutableMapOf<String, Prog8Document>()
|
||||
private val documents = ConcurrentHashMap<String, Prog8Document>()
|
||||
|
||||
// Store last diagnostics for code actions
|
||||
private val lastDiagnostics = mutableMapOf<String, List<Diagnostic>>()
|
||||
private val lastDiagnostics = ConcurrentHashMap<String, List<Diagnostic>>()
|
||||
|
||||
// Cached ASTs per document (to avoid reparsing on every request)
|
||||
internal val astCache = mutableMapOf<String, AstCache>()
|
||||
internal val astCache = ConcurrentHashMap<String, AstCache>()
|
||||
|
||||
fun connect(client: LanguageClient) {
|
||||
this.client = client
|
||||
@@ -64,14 +65,16 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
)
|
||||
documents[params.textDocument.uri] = document
|
||||
|
||||
// Parse and cache AST for the document
|
||||
astCache[params.textDocument.uri] = AstCache(
|
||||
module = Prog8Parser.parseModule(params.textDocument.text).module,
|
||||
isStale = false
|
||||
)
|
||||
async.execute {
|
||||
// Parse and cache AST for the document
|
||||
astCache[params.textDocument.uri] = AstCache(
|
||||
module = Prog8Parser.parseModule(params.textDocument.text).module,
|
||||
isStale = false
|
||||
)
|
||||
|
||||
// Trigger diagnostics when a document is opened
|
||||
validateDocument(document)
|
||||
// Trigger diagnostics when a document is opened
|
||||
validateDocument(document)
|
||||
}
|
||||
}
|
||||
|
||||
override fun didChange(params: DidChangeTextDocumentParams) {
|
||||
@@ -94,7 +97,9 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
}
|
||||
|
||||
// Trigger diagnostics when a document changes
|
||||
validateDocument(document)
|
||||
async.execute {
|
||||
validateDocument(document)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +316,8 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
|
||||
// Try to find symbol at position
|
||||
cache.module?.let { module ->
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character)
|
||||
val wordAtCursor = getWordAtPosition(document, params.position)
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character, wordAtCursor)
|
||||
if (symbol != null) {
|
||||
val hover = Hover()
|
||||
val contents = buildString {
|
||||
@@ -379,7 +385,8 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
|
||||
// Find symbol at position and return its definition location
|
||||
cache.module?.let { module ->
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character)
|
||||
val wordAtCursor = getWordAtPosition(document, params.position)
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character, wordAtCursor)
|
||||
if (symbol != null) {
|
||||
logger.config("Found symbol: ${symbol.name}")
|
||||
val locations = mutableListOf<Location>()
|
||||
@@ -414,10 +421,11 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
|
||||
cache.module?.let { module ->
|
||||
// Find the symbol at the cursor position
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character)
|
||||
val wordAtCursor = getWordAtPosition(document, params.position)
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character, wordAtCursor)
|
||||
if (symbol != null) {
|
||||
// Now search the entire document for all references to this symbol
|
||||
val allRefs = SymbolLookup.findAllReferences(module, symbol.name, params.context.isIncludeDeclaration, params.textDocument.uri)
|
||||
val allRefs = SymbolLookup.findAllReferences(module, symbol, params.context.isIncludeDeclaration, params.textDocument.uri)
|
||||
locations.addAll(allRefs)
|
||||
}
|
||||
}
|
||||
@@ -443,10 +451,11 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
|
||||
cache.module?.let { module ->
|
||||
// Find the symbol at the cursor position
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character)
|
||||
val wordAtCursor = getWordAtPosition(document, params.position)
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character, wordAtCursor)
|
||||
if (symbol != null) {
|
||||
// Find all references in the document
|
||||
val allRefs = SymbolLookup.findAllReferences(module, symbol.name, true, params.textDocument.uri)
|
||||
val allRefs = SymbolLookup.findAllReferences(module, symbol, true, params.textDocument.uri)
|
||||
// Convert locations to document highlights
|
||||
for (location in allRefs) {
|
||||
highlights.add(DocumentHighlight(location.range))
|
||||
@@ -672,18 +681,40 @@ class Prog8TextDocumentService: TextDocumentService {
|
||||
}
|
||||
|
||||
override fun rename(params: RenameParams): CompletableFuture<WorkspaceEdit> = async.compute {
|
||||
logger.config("Rename symbol in ${params.textDocument.uri} at ${params.position}")
|
||||
logger.config("Rename symbol in ${params.textDocument.uri} at ${params.position} to ${params.newName}")
|
||||
|
||||
// Get document from our store
|
||||
val document = documents[params.textDocument.uri]
|
||||
if (document != null) {
|
||||
// Implement actual rename functionality
|
||||
// This would involve:
|
||||
// 1. Finding all references to the symbol at the given position
|
||||
// 2. Creating TextEdit objects to rename each reference
|
||||
// 3. Adding the edits to a WorkspaceEdit
|
||||
|
||||
return@compute WorkspaceEdit()
|
||||
// Get or parse AST
|
||||
val cache = astCache.getOrPut(params.textDocument.uri) {
|
||||
AstCache(module = null, isStale = true)
|
||||
}
|
||||
if (cache.module == null || cache.isStale) {
|
||||
cache.module = Prog8Parser.parseModule(document.text).module
|
||||
cache.isStale = false
|
||||
}
|
||||
|
||||
cache.module?.let { module ->
|
||||
// 1. Find the symbol at the given position
|
||||
val wordAtCursor = getWordAtPosition(document, params.position)
|
||||
val symbol = SymbolLookup.findSymbolAt(module, params.position.line, params.position.character, wordAtCursor)
|
||||
if (symbol != null) {
|
||||
// 2. Find all references to the symbol
|
||||
// Now scope-aware!
|
||||
val allRefs = SymbolLookup.findAllReferences(module, symbol, true, params.textDocument.uri)
|
||||
|
||||
// 3. Create TextEdit objects to rename each reference
|
||||
val edits = allRefs.map { loc ->
|
||||
TextEdit(loc.range, params.newName)
|
||||
}
|
||||
|
||||
// 4. Return the edits in a WorkspaceEdit
|
||||
val workspaceEdit = WorkspaceEdit()
|
||||
workspaceEdit.changes = mapOf(params.textDocument.uri to edits)
|
||||
return@compute workspaceEdit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceEdit()
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package prog8lsp
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.Position
|
||||
@@ -35,26 +37,26 @@ object SymbolLookup {
|
||||
* Find the symbol at the given line/column position.
|
||||
* Returns null if no symbol is found at that position.
|
||||
*/
|
||||
fun findSymbolAt(module: Module, line: Int, column: Int): SymbolAtPosition? {
|
||||
fun findSymbolAt(module: Module, line: Int, column: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// LSP uses 0-based, Prog8 AST uses 1-based
|
||||
val targetLine = line + 1
|
||||
val targetCol = column + 1
|
||||
|
||||
// First pass: look for declarations at this position
|
||||
for (stmt in module.statements) {
|
||||
val result = findSymbolInStatement(stmt, targetLine, targetCol)
|
||||
val result = findSymbolInStatement(stmt, targetLine, targetCol, wordAtCursor)
|
||||
if (result != null) return result
|
||||
}
|
||||
|
||||
// Second pass: look for references and resolve them
|
||||
return findReferenceInModule(module, targetLine, targetCol)
|
||||
return findReferenceInModule(module, targetLine, targetCol, wordAtCursor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for identifier references in the module and resolve them to declarations.
|
||||
* Simple unscoped lookup - just matches by name.
|
||||
*/
|
||||
private fun findReferenceInModule(module: Module, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findReferenceInModule(module: Module, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// Collect all blocks and subroutines for simple name-based lookup
|
||||
val blocks = mutableMapOf<String, SymbolAtPosition>()
|
||||
val subroutines = mutableMapOf<String, SymbolAtPosition>()
|
||||
@@ -66,7 +68,7 @@ object SymbolLookup {
|
||||
// Search for references
|
||||
for (stmt in module.statements) {
|
||||
if (stmt is Block) {
|
||||
val refResult = findReferenceInBlock(stmt, targetLine, targetCol, blocks, subroutines)
|
||||
val refResult = findReferenceInBlock(stmt, targetLine, targetCol, wordAtCursor, blocks, subroutines)
|
||||
if (refResult != null) return refResult
|
||||
}
|
||||
}
|
||||
@@ -125,6 +127,7 @@ object SymbolLookup {
|
||||
block: Block,
|
||||
targetLine: Int,
|
||||
targetCol: Int,
|
||||
wordAtCursor: String,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
): SymbolAtPosition? {
|
||||
@@ -132,12 +135,12 @@ object SymbolLookup {
|
||||
|
||||
for (stmt in block.statements) {
|
||||
// Check for references before adding declarations
|
||||
val refResult = findReferenceInStatement(stmt, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
val refResult = findReferenceInStatement(stmt, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
if (refResult != null) return refResult
|
||||
|
||||
// If this is a subroutine, search inside it too
|
||||
if (stmt is Subroutine) {
|
||||
val subRefResult = findReferenceInSubroutine(stmt, targetLine, targetCol, blocks, subroutines)
|
||||
val subRefResult = findReferenceInSubroutine(stmt, targetLine, targetCol, wordAtCursor, blocks, subroutines)
|
||||
if (subRefResult != null) return subRefResult
|
||||
}
|
||||
|
||||
@@ -161,6 +164,7 @@ object SymbolLookup {
|
||||
sub: Subroutine,
|
||||
targetLine: Int,
|
||||
targetCol: Int,
|
||||
wordAtCursor: String,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
): SymbolAtPosition? {
|
||||
@@ -181,7 +185,7 @@ object SymbolLookup {
|
||||
|
||||
for (stmt in sub.statements) {
|
||||
// Check for references before adding declarations
|
||||
val refResult = findReferenceInStatement(stmt, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
val refResult = findReferenceInStatement(stmt, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
if (refResult != null) return refResult
|
||||
|
||||
// Add variable declarations to visible set
|
||||
@@ -200,19 +204,19 @@ object SymbolLookup {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findSymbolInStatement(stmt: Statement, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findSymbolInStatement(stmt: Statement, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
return when (stmt) {
|
||||
is Block -> findSymbolInBlock(stmt, targetLine, targetCol)
|
||||
is Subroutine -> findSymbolInSubroutine(stmt, targetLine, targetCol)
|
||||
is VarDecl -> findSymbolInVarDecl(stmt, targetLine, targetCol)
|
||||
is StructDecl -> findSymbolInStruct(stmt, targetLine, targetCol)
|
||||
is Block -> findSymbolInBlock(stmt, targetLine, targetCol, wordAtCursor)
|
||||
is Subroutine -> findSymbolInSubroutine(stmt, targetLine, targetCol, wordAtCursor)
|
||||
is VarDecl -> findSymbolInVarDecl(stmt, targetLine, targetCol, wordAtCursor)
|
||||
is StructDecl -> findSymbolInStruct(stmt, targetLine, targetCol, wordAtCursor)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSymbolInBlock(block: Block, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findSymbolInBlock(block: Block, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// Check if position is on the block name itself
|
||||
if (isPositionOnName(block.position, targetLine, targetCol, block.name)) {
|
||||
if (isPositionOnName(block.position, targetLine, targetCol, block.name, wordAtCursor)) {
|
||||
return SymbolAtPosition(
|
||||
name = block.name,
|
||||
kind = SymbolKind.BLOCK,
|
||||
@@ -224,16 +228,16 @@ object SymbolLookup {
|
||||
|
||||
// Search in block's statements
|
||||
for (stmt in block.statements) {
|
||||
val result = findSymbolInStatement(stmt, targetLine, targetCol)
|
||||
val result = findSymbolInStatement(stmt, targetLine, targetCol, wordAtCursor)
|
||||
if (result != null) return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findSymbolInSubroutine(sub: Subroutine, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findSymbolInSubroutine(sub: Subroutine, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// Check if position is on the subroutine name (declaration)
|
||||
if (isPositionOnName(sub.position, targetLine, targetCol, sub.name)) {
|
||||
if (isPositionOnName(sub.position, targetLine, targetCol, sub.name, wordAtCursor)) {
|
||||
val params = sub.parameters.joinToString(", ") { "${it.name}: ${it.type}" }
|
||||
val returnType = sub.returntypes.joinToString(", ") { it.toString() }
|
||||
val signature = if (returnType.isNotEmpty()) {
|
||||
@@ -251,6 +255,25 @@ object SymbolLookup {
|
||||
)
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
for (param in sub.parameters) {
|
||||
if (isPositionOnName(sub.position, targetLine, targetCol, param.name, wordAtCursor)) {
|
||||
return SymbolAtPosition(
|
||||
name = param.name,
|
||||
kind = SymbolKind.PARAMETER,
|
||||
definitionPosition = sub.position,
|
||||
type = param.type.toString(),
|
||||
signature = "${param.name}: ${param.type}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Search in subroutine's body
|
||||
for (stmt in sub.statements) {
|
||||
val result = findSymbolInStatement(stmt, targetLine, targetCol, wordAtCursor)
|
||||
if (result != null) return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -262,6 +285,7 @@ object SymbolLookup {
|
||||
stmt: Statement,
|
||||
targetLine: Int,
|
||||
targetCol: Int,
|
||||
wordAtCursor: String,
|
||||
visibleVars: Map<String, SymbolAtPosition>,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
@@ -270,36 +294,36 @@ object SymbolLookup {
|
||||
is Assignment -> {
|
||||
// Check if cursor is on the target identifier
|
||||
if (stmt.target.identifier != null &&
|
||||
isPositionOnName(stmt.target.identifier!!.position, targetLine, targetCol, stmt.target.identifier!!.nameInSource.lastOrNull() ?: "")) {
|
||||
isPositionOnName(stmt.target.identifier!!.position, targetLine, targetCol, stmt.target.identifier!!.nameInSource.lastOrNull() ?: "", wordAtCursor)) {
|
||||
val varName = stmt.target.identifier!!.nameInSource.lastOrNull() ?: ""
|
||||
return visibleVars[varName]
|
||||
}
|
||||
// Check if cursor is on an identifier in the value expression
|
||||
findReferenceInExpression(stmt.value, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
findReferenceInExpression(stmt.value, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is VarDecl -> {
|
||||
// Check if cursor is on the initializer expression
|
||||
stmt.value?.let { value ->
|
||||
findReferenceInExpression(value, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
findReferenceInExpression(value, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
is FunctionCallStatement -> {
|
||||
// Check if cursor is on the function name (handles both qualified and unqualified names)
|
||||
val funcName = stmt.target.nameInSource.lastOrNull() ?: ""
|
||||
if (isPositionOnName(stmt.target.position, targetLine, targetCol, funcName)) {
|
||||
if (isPositionOnName(stmt.target.position, targetLine, targetCol, funcName, wordAtCursor)) {
|
||||
// Simple unscoped lookup - just match by name
|
||||
return subroutines[funcName]
|
||||
}
|
||||
// Also check if cursor is on the qualifier (e.g., "data" in "data.set_both")
|
||||
if (stmt.target.nameInSource.size > 1) {
|
||||
val qualifier = stmt.target.nameInSource.firstOrNull() ?: ""
|
||||
if (isPositionOnName(stmt.target.position, targetLine, targetCol, qualifier)) {
|
||||
if (isPositionOnName(stmt.target.position, targetLine, targetCol, qualifier, wordAtCursor)) {
|
||||
return blocks[qualifier]
|
||||
}
|
||||
}
|
||||
// Check arguments for variable references
|
||||
for (arg in stmt.args) {
|
||||
val refResult = findReferenceInExpression(arg, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
val refResult = findReferenceInExpression(arg, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
if (refResult != null) return refResult
|
||||
}
|
||||
null
|
||||
@@ -312,6 +336,7 @@ object SymbolLookup {
|
||||
expr: prog8.ast.expressions.Expression,
|
||||
targetLine: Int,
|
||||
targetCol: Int,
|
||||
wordAtCursor: String,
|
||||
visibleVars: Map<String, SymbolAtPosition>,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
@@ -319,46 +344,46 @@ object SymbolLookup {
|
||||
return when (expr) {
|
||||
is prog8.ast.expressions.IdentifierReference -> {
|
||||
val identName = expr.nameInSource.lastOrNull() ?: ""
|
||||
if (isPositionOnName(expr.position, targetLine, targetCol, identName)) {
|
||||
if (isPositionOnName(expr.position, targetLine, targetCol, identName, wordAtCursor)) {
|
||||
// Simple unscoped lookup: check vars first, then blocks, then subroutines
|
||||
return visibleVars[identName] ?: blocks[identName] ?: subroutines[identName]
|
||||
}
|
||||
// Also check if cursor is on a qualifier in a qualified name
|
||||
for (part in expr.nameInSource) {
|
||||
if (isPositionOnName(expr.position, targetLine, targetCol, part)) {
|
||||
if (isPositionOnName(expr.position, targetLine, targetCol, part, wordAtCursor)) {
|
||||
return visibleVars[part] ?: blocks[part] ?: subroutines[part]
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
is prog8.ast.expressions.BinaryExpression -> {
|
||||
findReferenceInExpression(expr.left, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
?: findReferenceInExpression(expr.right, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
findReferenceInExpression(expr.left, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
?: findReferenceInExpression(expr.right, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.FunctionCallExpression -> {
|
||||
// Check function name (handles qualified names)
|
||||
val funcName = expr.target.nameInSource.lastOrNull() ?: ""
|
||||
if (isPositionOnName(expr.target.position, targetLine, targetCol, funcName)) {
|
||||
if (isPositionOnName(expr.target.position, targetLine, targetCol, funcName, wordAtCursor)) {
|
||||
return subroutines[funcName]
|
||||
}
|
||||
// Check qualifiers in qualified names
|
||||
for (part in expr.target.nameInSource.dropLast(1)) {
|
||||
if (isPositionOnName(expr.target.position, targetLine, targetCol, part)) {
|
||||
if (isPositionOnName(expr.target.position, targetLine, targetCol, part, wordAtCursor)) {
|
||||
return blocks[part]
|
||||
}
|
||||
}
|
||||
// Check arguments
|
||||
expr.args.firstNotNullOfOrNull {
|
||||
findReferenceInExpression(it, targetLine, targetCol, visibleVars, blocks, subroutines)
|
||||
findReferenceInExpression(it, targetLine, targetCol, wordAtCursor, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSymbolInVarDecl(varDecl: VarDecl, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findSymbolInVarDecl(varDecl: VarDecl, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// Check if position is on the variable name
|
||||
if (isPositionOnName(varDecl.position, targetLine, targetCol, varDecl.name)) {
|
||||
if (isPositionOnName(varDecl.position, targetLine, targetCol, varDecl.name, wordAtCursor)) {
|
||||
val kind = if (varDecl.type == VarDeclType.CONST) {
|
||||
SymbolKind.CONSTANT
|
||||
} else {
|
||||
@@ -397,9 +422,9 @@ object SymbolLookup {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findSymbolInStruct(struct: StructDecl, targetLine: Int, targetCol: Int): SymbolAtPosition? {
|
||||
private fun findSymbolInStruct(struct: StructDecl, targetLine: Int, targetCol: Int, wordAtCursor: String): SymbolAtPosition? {
|
||||
// Check if position is on the struct name
|
||||
if (isPositionOnName(struct.position, targetLine, targetCol, struct.name)) {
|
||||
if (isPositionOnName(struct.position, targetLine, targetCol, struct.name, wordAtCursor)) {
|
||||
return SymbolAtPosition(
|
||||
name = struct.name,
|
||||
kind = SymbolKind.STRUCT,
|
||||
@@ -414,11 +439,11 @@ object SymbolLookup {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isPositionOnName(position: Position, targetLine: Int, targetCol: Int, name: String): Boolean {
|
||||
private fun isPositionOnName(position: prog8.code.core.Position, targetLine: Int, targetCol: Int, name: String, wordAtCursor: String): Boolean {
|
||||
if (position.line != targetLine) return false
|
||||
// Check if column is within the name's column range
|
||||
// AST positions are 1-based, name starts at startCol
|
||||
return targetCol >= position.startCol && targetCol <= position.startCol + name.length - 1
|
||||
if (targetCol < position.startCol || targetCol > position.endCol) return false
|
||||
return name == wordAtCursor
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -492,98 +517,275 @@ object SymbolLookup {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all references to a symbol by name in the module.
|
||||
* Find all references to a symbol in the module, with scope awareness.
|
||||
*/
|
||||
fun findAllReferences(module: Module, symbolName: String, includeDeclaration: Boolean, uri: String): List<org.eclipse.lsp4j.Location> {
|
||||
fun findAllReferences(module: Module, targetSymbol: SymbolAtPosition, includeDeclaration: Boolean, uri: String): List<org.eclipse.lsp4j.Location> {
|
||||
val locations = mutableListOf<org.eclipse.lsp4j.Location>()
|
||||
|
||||
// Collect global symbols for lookup
|
||||
val blocks = mutableMapOf<String, SymbolAtPosition>()
|
||||
val subroutines = mutableMapOf<String, SymbolAtPosition>()
|
||||
for (stmt in module.statements) {
|
||||
findReferencesInStatement(stmt, symbolName, locations, includeDeclaration, uri)
|
||||
collectSymbols(stmt, blocks, subroutines)
|
||||
}
|
||||
|
||||
// Search in all top-level blocks
|
||||
for (stmt in module.statements) {
|
||||
if (stmt is Block) {
|
||||
findReferencesInBlockScoped(stmt, targetSymbol, includeDeclaration, locations, uri, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
private fun findReferencesInStatement(
|
||||
stmt: prog8.ast.statements.Statement,
|
||||
symbolName: String,
|
||||
locations: MutableList<org.eclipse.lsp4j.Location>,
|
||||
private fun findReferencesInBlockScoped(
|
||||
block: Block,
|
||||
targetSymbol: SymbolAtPosition,
|
||||
includeDeclaration: Boolean,
|
||||
uri: String
|
||||
locations: MutableList<org.eclipse.lsp4j.Location>,
|
||||
uri: String,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
) {
|
||||
when (stmt) {
|
||||
is prog8.ast.statements.VarDecl -> {
|
||||
if (includeDeclaration && stmt.name == symbolName) {
|
||||
val visibleVars = mutableMapOf<String, SymbolAtPosition>()
|
||||
|
||||
// Check if the block itself is the target
|
||||
if (includeDeclaration && block.name == targetSymbol.name && block.position == targetSymbol.definitionPosition) {
|
||||
locations.add(block.position.toLspLocation(uri))
|
||||
}
|
||||
|
||||
for (stmt in block.statements) {
|
||||
// Check for references in the statement BEFORE adding declaration to visibleVars
|
||||
// (Prog8 doesn't allow using a variable before declaration in the same block)
|
||||
findReferencesInStatementScoped(stmt, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
|
||||
// If it's a declaration, add to visibleVars and check if it's the target
|
||||
if (stmt is VarDecl) {
|
||||
val declPrefix = if (stmt.type == VarDeclType.CONST) "const " else ""
|
||||
val currentDecl = SymbolAtPosition(
|
||||
name = stmt.name,
|
||||
kind = if (stmt.type == VarDeclType.CONST) SymbolKind.CONSTANT else SymbolKind.VARIABLE,
|
||||
definitionPosition = stmt.position,
|
||||
type = stmt.type.toString(),
|
||||
signature = "${declPrefix}${stmt.name}: ${stmt.type}"
|
||||
)
|
||||
visibleVars[stmt.name] = currentDecl
|
||||
|
||||
if (includeDeclaration && stmt.name == targetSymbol.name && stmt.position == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
}
|
||||
stmt.value?.let { value ->
|
||||
findReferencesInExpression(value, symbolName, locations, uri)
|
||||
}
|
||||
|
||||
// If it's a subroutine, search inside it
|
||||
if (stmt is Subroutine) {
|
||||
findReferencesInSubroutineScoped(stmt, targetSymbol, includeDeclaration, locations, uri, blocks, subroutines)
|
||||
|
||||
// Also check if the subroutine itself is the target
|
||||
if (includeDeclaration && stmt.name == targetSymbol.name && stmt.position == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
is prog8.ast.statements.Assignment -> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferencesInSubroutineScoped(
|
||||
sub: Subroutine,
|
||||
targetSymbol: SymbolAtPosition,
|
||||
includeDeclaration: Boolean,
|
||||
locations: MutableList<org.eclipse.lsp4j.Location>,
|
||||
uri: String,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
) {
|
||||
val visibleVars = mutableMapOf<String, SymbolAtPosition>()
|
||||
|
||||
// Add parameters to visible variables and check if any is the target
|
||||
for (param in sub.parameters) {
|
||||
val currentParam = SymbolAtPosition(
|
||||
name = param.name,
|
||||
kind = SymbolKind.PARAMETER,
|
||||
definitionPosition = sub.position, // Parameters use sub's position as declaration
|
||||
type = param.type.toString(),
|
||||
signature = "${param.name}: ${param.type}"
|
||||
)
|
||||
visibleVars[param.name] = currentParam
|
||||
|
||||
if (includeDeclaration && param.name == targetSymbol.name && sub.position == targetSymbol.definitionPosition) {
|
||||
// For parameters, we highlight the parameter in the signature if we can find it
|
||||
// For now, just use the sub's position
|
||||
locations.add(sub.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
|
||||
for (stmt in sub.statements) {
|
||||
findReferencesInStatementScoped(stmt, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
|
||||
if (stmt is VarDecl && stmt.origin != VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
val declPrefix = if (stmt.type == VarDeclType.CONST) "const " else ""
|
||||
val currentDecl = SymbolAtPosition(
|
||||
name = stmt.name,
|
||||
kind = if (stmt.type == VarDeclType.CONST) SymbolKind.CONSTANT else SymbolKind.VARIABLE,
|
||||
definitionPosition = stmt.position,
|
||||
type = stmt.type.toString(),
|
||||
signature = "${declPrefix}${stmt.name}: ${stmt.type}"
|
||||
)
|
||||
visibleVars[stmt.name] = currentDecl
|
||||
|
||||
if (includeDeclaration && stmt.name == targetSymbol.name && stmt.position == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
|
||||
// Subroutines can be nested
|
||||
if (stmt is Subroutine) {
|
||||
findReferencesInSubroutineScoped(stmt, targetSymbol, includeDeclaration, locations, uri, blocks, subroutines)
|
||||
if (includeDeclaration && stmt.name == targetSymbol.name && stmt.position == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferencesInStatementScoped(
|
||||
stmt: Statement,
|
||||
targetSymbol: SymbolAtPosition,
|
||||
locations: MutableList<org.eclipse.lsp4j.Location>,
|
||||
uri: String,
|
||||
visibleVars: Map<String, SymbolAtPosition>,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
) {
|
||||
when (stmt) {
|
||||
is Assignment -> {
|
||||
stmt.target.identifier?.let { ident ->
|
||||
if (ident.nameInSource.lastOrNull() == symbolName) {
|
||||
val resolved = resolveIdentifierScoped(ident.nameInSource.lastOrNull() ?: "", visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(ident.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
findReferencesInExpression(stmt.value, symbolName, locations, uri)
|
||||
findReferencesInExpressionScoped(stmt.value, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.statements.FunctionCallStatement -> {
|
||||
val funcName = stmt.target.nameInSource.lastOrNull()
|
||||
if (funcName == symbolName) {
|
||||
is VarDecl -> {
|
||||
stmt.value?.let { value ->
|
||||
findReferencesInExpressionScoped(value, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
is FunctionCallStatement -> {
|
||||
val funcName = stmt.target.nameInSource.lastOrNull() ?: ""
|
||||
val resolved = resolveIdentifierScoped(funcName, visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.target.position.toLspLocation(uri))
|
||||
}
|
||||
for (arg in stmt.args) {
|
||||
findReferencesInExpression(arg, symbolName, locations, uri)
|
||||
findReferencesInExpressionScoped(arg, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
is prog8.ast.statements.Block -> {
|
||||
if (includeDeclaration && stmt.name == symbolName) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
is IfElse -> {
|
||||
findReferencesInExpressionScoped(stmt.condition, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
for (s in stmt.truepart.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
for (s in stmt.elsepart.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is ForLoop -> {
|
||||
// Loop variable
|
||||
val resolved = resolveIdentifierScoped(stmt.loopVar.nameInSource.lastOrNull() ?: "", visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(stmt.loopVar.position.toLspLocation(uri))
|
||||
}
|
||||
for (innerStmt in stmt.statements) {
|
||||
findReferencesInStatement(innerStmt, symbolName, locations, includeDeclaration, uri)
|
||||
findReferencesInExpressionScoped(stmt.iterable, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
for (s in stmt.body.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is When -> {
|
||||
findReferencesInExpressionScoped(stmt.condition, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
for (choice in stmt.choices) {
|
||||
choice.values?.forEach { findReferencesInExpressionScoped(it, targetSymbol, locations, uri, visibleVars, blocks, subroutines) }
|
||||
for (s in choice.statements.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
is prog8.ast.statements.Subroutine -> {
|
||||
if (includeDeclaration && stmt.name == symbolName) {
|
||||
locations.add(stmt.position.toLspLocation(uri))
|
||||
}
|
||||
for (innerStmt in stmt.statements) {
|
||||
findReferencesInStatement(innerStmt, symbolName, locations, includeDeclaration, uri)
|
||||
}
|
||||
is RepeatLoop -> {
|
||||
stmt.iterations?.let { findReferencesInExpressionScoped(it, targetSymbol, locations, uri, visibleVars, blocks, subroutines) }
|
||||
for (s in stmt.body.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is WhileLoop -> {
|
||||
findReferencesInExpressionScoped(stmt.condition, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
for (s in stmt.body.statements) findReferencesInStatementScoped(s, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is Return -> {
|
||||
for (expr in stmt.values) findReferencesInExpressionScoped(expr, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findReferencesInExpression(
|
||||
private fun findReferencesInExpressionScoped(
|
||||
expr: prog8.ast.expressions.Expression,
|
||||
symbolName: String,
|
||||
targetSymbol: SymbolAtPosition,
|
||||
locations: MutableList<org.eclipse.lsp4j.Location>,
|
||||
uri: String
|
||||
uri: String,
|
||||
visibleVars: Map<String, SymbolAtPosition>,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
) {
|
||||
when (expr) {
|
||||
is prog8.ast.expressions.IdentifierReference -> {
|
||||
if (expr.nameInSource.lastOrNull() == symbolName) {
|
||||
val identName = expr.nameInSource.lastOrNull() ?: ""
|
||||
val resolved = resolveIdentifierScoped(identName, visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(expr.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
is prog8.ast.expressions.BinaryExpression -> {
|
||||
findReferencesInExpression(expr.left, symbolName, locations, uri)
|
||||
findReferencesInExpression(expr.right, symbolName, locations, uri)
|
||||
findReferencesInExpressionScoped(expr.left, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
findReferencesInExpressionScoped(expr.right, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.PrefixExpression -> {
|
||||
findReferencesInExpressionScoped(expr.expression, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.FunctionCallExpression -> {
|
||||
if (expr.target.nameInSource.lastOrNull() == symbolName) {
|
||||
val funcName = expr.target.nameInSource.lastOrNull() ?: ""
|
||||
val resolved = resolveIdentifierScoped(funcName, visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(expr.target.position.toLspLocation(uri))
|
||||
}
|
||||
for (arg in expr.args) {
|
||||
findReferencesInExpression(arg, symbolName, locations, uri)
|
||||
findReferencesInExpressionScoped(arg, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
}
|
||||
is prog8.ast.expressions.TypecastExpression -> {
|
||||
findReferencesInExpressionScoped(expr.expression, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.IfExpression -> {
|
||||
findReferencesInExpressionScoped(expr.condition, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
findReferencesInExpressionScoped(expr.truevalue, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
findReferencesInExpressionScoped(expr.falsevalue, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.ArrayIndexedExpression -> {
|
||||
expr.plainarrayvar?.let {
|
||||
val resolved = resolveIdentifierScoped(it.nameInSource.lastOrNull() ?: "", visibleVars, blocks, subroutines)
|
||||
if (resolved?.definitionPosition == targetSymbol.definitionPosition) {
|
||||
locations.add(it.position.toLspLocation(uri))
|
||||
}
|
||||
}
|
||||
expr.nestedArray?.let { findReferencesInExpressionScoped(it, targetSymbol, locations, uri, visibleVars, blocks, subroutines) }
|
||||
findReferencesInExpressionScoped(expr.indexer.indexExpr, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
}
|
||||
is prog8.ast.expressions.RangeExpression -> {
|
||||
findReferencesInExpressionScoped(expr.from, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
findReferencesInExpressionScoped(expr.to, targetSymbol, locations, uri, visibleVars, blocks, subroutines)
|
||||
expr.step?.let { findReferencesInExpressionScoped(it, targetSymbol, locations, uri, visibleVars, blocks, subroutines) }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveIdentifierScoped(
|
||||
name: String,
|
||||
visibleVars: Map<String, SymbolAtPosition>,
|
||||
blocks: Map<String, SymbolAtPosition>,
|
||||
subroutines: Map<String, SymbolAtPosition>
|
||||
): SymbolAtPosition? {
|
||||
return visibleVars[name] ?: blocks[name] ?: subroutines[name]
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the function call at the given position and return the function name and parameter index.
|
||||
@@ -597,49 +799,61 @@ object SymbolLookup {
|
||||
val targetLine = line + 1
|
||||
val targetCol = column + 1
|
||||
|
||||
for (stmt in module.statements) {
|
||||
val result = findFunctionCallInStatement(stmt, targetLine, targetCol)
|
||||
if (result != null) return result
|
||||
var bestCall: IFunctionCall? = null
|
||||
|
||||
fun checkCall(call: IFunctionCall) {
|
||||
val pos = (call as Node).position
|
||||
// Check if cursor is on the name OR within the parentheses
|
||||
if (pos.line == targetLine && targetCol >= pos.startCol && targetCol <= pos.endCol) {
|
||||
if (bestCall == null) {
|
||||
bestCall = call
|
||||
} else {
|
||||
val bestPos = (bestCall as Node).position
|
||||
// Pick the innermost call (one with the smallest range)
|
||||
if (pos.endCol - pos.startCol <= bestPos.endCol - bestPos.startCol) {
|
||||
bestCall = call
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val visitor = object : IAstVisitor {
|
||||
override fun visit(module: Module) {
|
||||
module.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
checkCall(functionCallExpr)
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
checkCall(functionCallStatement)
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
}
|
||||
|
||||
module.accept(visitor)
|
||||
|
||||
bestCall?.let { call ->
|
||||
val funcName = call.target.nameInSource.lastOrNull() ?: ""
|
||||
var paramIndex = 0
|
||||
|
||||
// Find which parameter we are in by checking argument positions.
|
||||
// Arguments in the AST are always in order of appearance in source.
|
||||
for ((i, arg) in call.args.withIndex()) {
|
||||
if (targetCol > arg.position.endCol) {
|
||||
paramIndex = i + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return FunctionCallInfo(funcName, paramIndex)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findFunctionCallInStatement(
|
||||
stmt: prog8.ast.statements.Statement,
|
||||
targetLine: Int,
|
||||
targetCol: Int
|
||||
): FunctionCallInfo? {
|
||||
return when (stmt) {
|
||||
is prog8.ast.statements.FunctionCallStatement -> {
|
||||
if (stmt.target.position.line == targetLine &&
|
||||
targetCol >= stmt.target.position.startCol &&
|
||||
targetCol <= stmt.target.position.endCol) {
|
||||
// Cursor is on the function name, count commas to find param index
|
||||
return FunctionCallInfo(
|
||||
stmt.target.nameInSource.lastOrNull() ?: "",
|
||||
0 // Default to first parameter
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
is prog8.ast.statements.Block -> {
|
||||
for (innerStmt in stmt.statements) {
|
||||
val result = findFunctionCallInStatement(innerStmt, targetLine, targetCol)
|
||||
if (result != null) return result
|
||||
}
|
||||
null
|
||||
}
|
||||
is prog8.ast.statements.Subroutine -> {
|
||||
for (innerStmt in stmt.statements) {
|
||||
val result = findFunctionCallInStatement(innerStmt, targetLine, targetCol)
|
||||
if (result != null) return result
|
||||
}
|
||||
null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all subroutines from the module for signature help.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package prog8lsp
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
|
||||
class GotoDefTest: FunSpec({
|
||||
lateinit var harness: LspTestHarness
|
||||
val testUri = "file:///test.p8"
|
||||
|
||||
beforeTest {
|
||||
harness = LspTestHarness()
|
||||
harness.setup()
|
||||
}
|
||||
|
||||
afterTest {
|
||||
harness.shutdown()
|
||||
}
|
||||
|
||||
test("go to definition - variable use site") {
|
||||
val code = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte val1 = 99
|
||||
ubyte val2 = val1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
harness.openDocument(testUri, code)
|
||||
|
||||
// val1 in "ubyte val2 = val1"
|
||||
// line 3 (0-indexed), char 21
|
||||
// u b y t e _ v a l 2 _ = _ v a l 1
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
|
||||
// In trimIndent(), it is:
|
||||
// sub start() {
|
||||
// 01234567890123
|
||||
// ubyte val2 = val1
|
||||
// 012345678901234567890
|
||||
// val1 starts at col 17
|
||||
|
||||
val locations = harness.definition(testUri, 3, 22)
|
||||
|
||||
locations.size shouldBe 1
|
||||
locations[0].range.start.line shouldBe 2 // Declaration at line 2 (0-indexed)
|
||||
}
|
||||
})
|
||||
@@ -71,6 +71,9 @@ class LspTestHarness {
|
||||
private val textDocumentService: Prog8TextDocumentService
|
||||
get() = server.textDocumentService as Prog8TextDocumentService
|
||||
|
||||
private val async: AsyncExecutor
|
||||
get() = textDocumentService.async
|
||||
|
||||
fun setup() {
|
||||
server.connect(client)
|
||||
client.clear()
|
||||
@@ -95,6 +98,7 @@ class LspTestHarness {
|
||||
TextDocumentItem(uri, "prog8", version, text)
|
||||
)
|
||||
textDocumentService.didOpen(params)
|
||||
async.flush()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,6 +117,7 @@ class LspTestHarness {
|
||||
listOf(TextDocumentContentChangeEvent(text))
|
||||
)
|
||||
textDocumentService.didChange(params)
|
||||
async.flush()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package prog8lsp
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import org.eclipse.lsp4j.RenameParams
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier
|
||||
|
||||
class RenameTest: FunSpec({
|
||||
lateinit var harness: LspTestHarness
|
||||
val testUri = "file:///test.p8"
|
||||
|
||||
beforeTest {
|
||||
harness = LspTestHarness()
|
||||
harness.setup()
|
||||
}
|
||||
|
||||
afterTest {
|
||||
harness.shutdown()
|
||||
}
|
||||
|
||||
test("rename variable - scope awareness check") {
|
||||
val code = """
|
||||
main {
|
||||
sub a() {
|
||||
ubyte x = 1
|
||||
ubyte y = x
|
||||
}
|
||||
sub b() {
|
||||
ubyte x = 2
|
||||
ubyte z = x
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
harness.openDocument(testUri, code)
|
||||
|
||||
// Rename 'x' in sub a() to 'newName'
|
||||
// Line 2: ubyte x = 1
|
||||
// Line 3: ubyte y = x
|
||||
|
||||
val renameParams = RenameParams()
|
||||
renameParams.textDocument = TextDocumentIdentifier(testUri)
|
||||
renameParams.position = org.eclipse.lsp4j.Position(2, 14) // 'x' in "ubyte x = 1"
|
||||
renameParams.newName = "newName"
|
||||
|
||||
val workspaceEdit = harness.server.textDocumentService.rename(renameParams).get()
|
||||
|
||||
val edits = workspaceEdit.changes[testUri]!!
|
||||
// If scope-aware, it should only have 2 edits (line 2 and line 3)
|
||||
// If not scope-aware, it might have 4 edits (line 2, 3, 6, 7)
|
||||
|
||||
edits.size shouldBe 2
|
||||
edits.any { it.range.start.line == 2 } shouldBe true
|
||||
edits.any { it.range.start.line == 3 } shouldBe true
|
||||
edits.any { it.range.start.line == 6 } shouldBe false
|
||||
edits.any { it.range.start.line == 7 } shouldBe false
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
package prog8lsp
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
|
||||
class SignatureHelpTest: FunSpec({
|
||||
lateinit var harness: LspTestHarness
|
||||
val testUri = "file:///test.p8"
|
||||
|
||||
beforeTest {
|
||||
harness = LspTestHarness()
|
||||
harness.setup()
|
||||
}
|
||||
|
||||
afterTest {
|
||||
harness.shutdown()
|
||||
}
|
||||
|
||||
test("signature help - basic function call") {
|
||||
val code = """
|
||||
main {
|
||||
sub start() {
|
||||
foo(1, 2)
|
||||
}
|
||||
sub foo(ubyte a, ubyte b) {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
harness.openDocument(testUri, code)
|
||||
|
||||
// Cursor after '('
|
||||
val help1 = harness.signatureHelp(testUri, 2, 11)
|
||||
help1 shouldNotBe null
|
||||
help1!!.signatures.size shouldBe 1
|
||||
help1.signatures[0].label shouldBe "sub foo(a: ubyte, b: ubyte)"
|
||||
help1.activeParameter shouldBe 0
|
||||
|
||||
// Cursor after ','
|
||||
val help2 = harness.signatureHelp(testUri, 2, 14)
|
||||
help2 shouldNotBe null
|
||||
help2!!.activeParameter shouldBe 1
|
||||
}
|
||||
|
||||
test("signature help - function call in expression") {
|
||||
val code = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte x = foo(10, 20)
|
||||
}
|
||||
sub foo(ubyte a, ubyte b) -> ubyte {
|
||||
return a + b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
harness.openDocument(testUri, code)
|
||||
|
||||
// Cursor inside parentheses of foo(10, 20)
|
||||
val help = harness.signatureHelp(testUri, 2, 21)
|
||||
help shouldNotBe null
|
||||
help!!.signatures.size shouldBe 1
|
||||
help.signatures[0].label shouldBe "sub foo(a: ubyte, b: ubyte) -> ubyte"
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user