diff --git a/analysis/src/References.ml b/analysis/src/References.ml index 3da82433e..43095c8a0 100644 --- a/analysis/src/References.ml +++ b/analysis/src/References.ml @@ -459,15 +459,29 @@ let forLocalStamp ~full:{file; extra; package} stamp tip = let allReferencesForLocItem ~full:({file; package} as full) locItem = match locItem.locType with | TopLevelModule moduleName -> - let locs = - match Hashtbl.find_opt full.extra.fileReferences moduleName with + let otherModulesReferences = + package.projectFiles + |> Utils.filterMap (fun name -> + match ProcessCmt.fileForModule ~package name with + | None -> None + | Some file -> ProcessCmt.getFullFromCmt ~uri:file.uri) + |> List.map (fun full -> + match Hashtbl.find_opt full.extra.fileReferences moduleName with + | None -> [] + | Some locs -> + locs + |> List.map (fun loc -> + (Uri2.fromPath loc.Location.loc_start.pos_fname, [loc]))) + |> List.flatten + in + let targetModuleReferences = + match Hashtbl.find_opt package.pathsForModule moduleName with | None -> [] - | Some locs -> - locs - |> List.map (fun loc -> - (Uri2.fromPath loc.Location.loc_start.pos_fname, [loc])) + | Some paths -> + let moduleSrcToRef src = (Uri2.fromPath src, [Utils.topLoc src]) in + getSrc paths |> List.map moduleSrcToRef in - locs + List.append targetModuleReferences otherModulesReferences | Typed (_, _, NotFound) | LModule NotFound | Constant _ -> [] | TypeDefinition (_, _, stamp) -> forLocalStamp ~full stamp Type | Typed (_, _, (LocalReference (stamp, tip) | Definition (stamp, tip))) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index ca5ebeddf..948996527 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -130,6 +130,12 @@ let showPaths paths = | IntfAndImpl {cmti; resi; cmt; res} -> Printf.sprintf "IntfAndImpl(%s, %s, %s, %s)" cmti resi cmt res +let getSrc p = + match p with + | Impl {res} -> [res] + | Namespace _ -> [] + | IntfAndImpl {resi; res} -> [resi; res] + let getUri p = match p with | Impl {res} -> Uri2.fromPath res diff --git a/analysis/tests/src/expected/Cross.res.txt b/analysis/tests/src/expected/Cross.res.txt index 63f400742..fbce959f9 100644 --- a/analysis/tests/src/expected/Cross.res.txt +++ b/analysis/tests/src/expected/Cross.res.txt @@ -3,6 +3,7 @@ References tests/src/Cross.res 0:17 {"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}}, {"uri": "Cross.res", "range": {"start": {"line": 3, "character": 16}, "end": {"line": 3, "character": 26}}}, {"uri": "Cross.res", "range": {"start": {"line": 6, "character": 13}, "end": {"line": 6, "character": 23}}}, -{"uri": "Cross.res", "range": {"start": {"line": 8, "character": 16}, "end": {"line": 8, "character": 26}}} +{"uri": "Cross.res", "range": {"start": {"line": 8, "character": 16}, "end": {"line": 8, "character": 26}}}, +{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}} ] diff --git a/server/src/server.ts b/server/src/server.ts index d6e72584b..81330412f 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -15,7 +15,7 @@ import * as utils from "./utils"; import * as c from "./constants"; import * as chokidar from "chokidar"; import { assert } from "console"; -import { fileURLToPath } from "url"; +import { fileURLToPath, pathToFileURL } from "url"; import { ChildProcess } from "child_process"; import { WorkspaceEdit } from "vscode-languageserver"; import { TextEdit } from "vscode-languageserver-types"; @@ -294,16 +294,34 @@ function rename(msg: p.RequestMessage) { if (locations === null) { result = null; } else { - let changes: { [uri: string]: TextEdit[] } = {}; + let textEdits: { [uri: string]: TextEdit[] } = {}; + let documentChanges: (p.RenameFile | p.TextDocumentEdit)[] = []; + locations.forEach(({ uri, range }) => { - let textEdit: TextEdit = { range, newText: params.newName }; - if (uri in changes) { - changes[uri].push(textEdit); + if (utils.isRangeTopOfFile(range)) { + let filePath = fileURLToPath(uri); + let newFilePath = `${path.dirname(filePath)}/${params.newName}${path.extname(filePath)}`; + let newUri = pathToFileURL(newFilePath).href; + let rename: p.RenameFile = { kind: "rename", oldUri: uri, newUri }; + documentChanges.push(rename); } else { - changes[uri] = [textEdit]; + let textEdit: TextEdit = { range, newText: params.newName }; + if (uri in textEdits) { + textEdits[uri].push(textEdit); + } else { + textEdits[uri] = [textEdit]; + } } }); - result = { changes }; + + Object.entries(textEdits) + .forEach(([uri, edits]) => { + let textDocumentEdit = { textDocument: { uri, version: null }, edits }; + documentChanges.push(textDocumentEdit); + }); + + + result = { documentChanges }; } let response: m.ResponseMessage = { jsonrpc: c.jsonrpcVersion, diff --git a/server/src/utils.ts b/server/src/utils.ts index 2c6d236f9..b32fd80a6 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -476,3 +476,11 @@ export let parseCompilerLogOutput = ( return { done, result }; }; + +export let isRangeTopOfFile = (range: p.Range) => + [ + range.start.character, + range.start.line, + range.end.character, + range.end.line + ].every(n => n === 0);