diff --git a/.gitignore b/.gitignore
index 77c07c0..fb7eabe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,5 @@ node_modules/
.idea/
build.vsix
Scratch.fsx
-Scratch.ts
\ No newline at end of file
+Scratch.ts
+fsharp-language-server.sln.DotSettings.user
diff --git a/client/extension.ts b/client/extension.ts
index 96837ab..27d5d05 100644
--- a/client/extension.ts
+++ b/client/extension.ts
@@ -26,8 +26,10 @@ export function activate(context: ExtensionContext) {
transport: TransportKind.stdio
}
if (debugMode) {
+ const commandName = process.platform === 'win32' ? 'dotnet.exe' : 'dotnet';
+
serverOptions = {
- command: findInPath('dotnet'),
+ command: findInPath(commandName),
args: ['run', '--project', 'src/FSharpLanguageServer'],
transport: TransportKind.stdio,
options: { cwd: context.extensionPath }
diff --git a/package-lock.json b/package-lock.json
index eb50789..2080995 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,353 +1,100 @@
{
"name": "fsharp-language-server",
"version": "0.1.27",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
- "dependencies": {
- "@tootallnate/once": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
- "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
- "dev": true
+ "packages": {
+ "": {
+ "version": "0.1.27",
+ "license": "MIT",
+ "dependencies": {
+ "@types/vscode": "^1.55.0",
+ "typescript": "^4.2.4",
+ "vscode-languageclient": "^4.2.1"
+ },
+ "devDependencies": {
+ "@types/node": "^14.14.37",
+ "@types/vscode": "^1.24.0",
+ "typescript": "^4.2.4"
+ },
+ "engines": {
+ "vscode": "^1.24.0"
+ }
},
- "@types/node": {
- "version": "10.17.59",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.59.tgz",
- "integrity": "sha512-7Uc8IRrL8yZz5ti45RaFxpbU8TxlzdC3HvxV+hOWo1EyLsuKv/w7y0n+TwZzwL3vdx3oZ2k3ubxPq131hNtXyg==",
+ "node_modules/@types/node": {
+ "version": "14.14.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
+ "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==",
"dev": true
},
- "@types/vscode": {
+ "node_modules/@types/vscode": {
"version": "1.55.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.55.0.tgz",
"integrity": "sha512-49hysH7jneTQoSC8TWbAi7nKK9Lc5osQNjmDHVosrcU8o3jecD9GrK0Qyul8q4aGPSXRfNGqIp9CBdb13akETg=="
},
- "agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "requires": {
- "debug": "4"
- }
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "big-integer": {
- "version": "1.6.48",
- "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
- "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==",
- "dev": true
- },
- "binary": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
- "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
- "dev": true,
- "requires": {
- "buffers": "~0.1.1",
- "chainsaw": "~0.1.0"
- }
- },
- "bluebird": {
- "version": "3.4.7",
- "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
- "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=",
- "dev": true
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "buffer-indexof-polyfill": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
- "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==",
- "dev": true
- },
- "buffers": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
- "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=",
- "dev": true
- },
- "chainsaw": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
- "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
- "dev": true,
- "requires": {
- "traverse": ">=0.3.0 <0.4"
- }
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
- },
- "debug": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
- "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- }
- },
- "duplexer2": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
- "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=",
- "dev": true,
- "requires": {
- "readable-stream": "^2.0.2"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "dev": true
- },
- "fstream": {
- "version": "1.0.12",
- "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
- "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.2",
- "inherits": "~2.0.0",
- "mkdirp": ">=0.5 0",
- "rimraf": "2"
+ "node_modules/typescript": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
+ "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
},
- "dependencies": {
- "rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- }
- }
+ "engines": {
+ "node": ">=4.2.0"
}
},
- "glob": {
- "version": "7.1.6",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
- "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.2.6",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
- "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
- "dev": true
- },
- "http-proxy-agent": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
- "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
- "dev": true,
- "requires": {
- "@tootallnate/once": "1",
- "agent-base": "6",
- "debug": "4"
- }
- },
- "https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "dev": true,
- "requires": {
- "agent-base": "6",
- "debug": "4"
- }
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dev": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
- "listenercount": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz",
- "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=",
- "dev": true
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minimist": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
- "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
- "dev": true
- },
- "mkdirp": {
- "version": "0.5.5",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
- "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
- "dev": true,
- "requires": {
- "minimist": "^1.2.5"
- }
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "requires": {
- "wrappy": "1"
+ "node_modules/vscode-jsonrpc": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz",
+ "integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==",
+ "engines": {
+ "node": ">=4.0.0 || >=6.0.0"
}
},
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true
- },
- "process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true
- },
- "readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
+ "node_modules/vscode-languageclient": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.2.1.tgz",
+ "integrity": "sha512-zeixIe2MiKPHiSNjEUmRhWFiNCGUwUNvefBiA9diZc7fXE8DX+AhfwpsOLYauO8Q8C6gW8f9OQvy3Vn2hBvY4g==",
+ "dependencies": {
+ "vscode-languageserver-protocol": "^3.8.1"
+ },
+ "engines": {
+ "vscode": "^1.23"
}
},
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
+ "node_modules/vscode-languageserver-protocol": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.8.1.tgz",
+ "integrity": "sha512-KdeetvQ2JavRiuE9afNrV5+xJZocj7NGPQwH4kpSFw5cp+0wijv87qgXfSEvmwPUaknhMBoSuSrSIG/LRrzWJQ==",
+ "dependencies": {
+ "vscode-jsonrpc": "^3.6.2",
+ "vscode-languageserver-types": "^3.8.1"
}
},
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "setimmediate": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "node_modules/vscode-languageserver-types": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.8.2.tgz",
+ "integrity": "sha512-2RMkyt1O1czGPCnkjKZWSio2D8oh3XlQ4zi4W2xL8q2Dvi4hB3/DEt+wYyzo4hmE2ZFP0RB8PXyzHyed7p1hbw=="
+ }
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "14.14.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
+ "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==",
"dev": true
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- }
- },
- "traverse": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
- "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=",
- "dev": true
+ "@types/vscode": {
+ "version": "1.55.0",
+ "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.55.0.tgz",
+ "integrity": "sha512-49hysH7jneTQoSC8TWbAi7nKK9Lc5osQNjmDHVosrcU8o3jecD9GrK0Qyul8q4aGPSXRfNGqIp9CBdb13akETg=="
},
"typescript": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg=="
},
- "unzipper": {
- "version": "0.10.11",
- "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz",
- "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==",
- "dev": true,
- "requires": {
- "big-integer": "^1.6.17",
- "binary": "~0.3.0",
- "bluebird": "~3.4.1",
- "buffer-indexof-polyfill": "~1.0.0",
- "duplexer2": "~0.1.4",
- "fstream": "^1.0.12",
- "graceful-fs": "^4.2.2",
- "listenercount": "~1.0.1",
- "readable-stream": "~2.3.6",
- "setimmediate": "~1.0.4"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
- },
"vscode-jsonrpc": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz",
@@ -374,24 +121,6 @@
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.8.2.tgz",
"integrity": "sha512-2RMkyt1O1czGPCnkjKZWSio2D8oh3XlQ4zi4W2xL8q2Dvi4hB3/DEt+wYyzo4hmE2ZFP0RB8PXyzHyed7p1hbw=="
- },
- "vscode-test": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-1.5.2.tgz",
- "integrity": "sha512-x9PVfKxF6EInH9iSFGQi0V8H5zIW1fC7RAer6yNQR6sy3WyOwlWkuT3I+wf75xW/cO53hxMi1aj/EvqQfDFOAg==",
- "dev": true,
- "requires": {
- "http-proxy-agent": "^4.0.1",
- "https-proxy-agent": "^5.0.0",
- "rimraf": "^3.0.2",
- "unzipper": "^0.10.11"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
}
}
}
diff --git a/package.json b/package.json
index 37ad996..1b43703 100644
--- a/package.json
+++ b/package.json
@@ -117,7 +117,8 @@
"vscode-languageclient": "^4.2.1"
},
"devDependencies": {
- "@types/node": "^10.17.59",
- "vscode-test": "^1.5.2"
+ "@types/node": "^14.14.37",
+ "@types/vscode": "^1.24.0",
+ "typescript": "^4.2.4"
}
}
diff --git a/sample/Net5Console/Net5Console.fsproj b/sample/Net5Console/Net5Console.fsproj
index 260a75b..1cd56e8 100644
--- a/sample/Net5Console/Net5Console.fsproj
+++ b/sample/Net5Console/Net5Console.fsproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ netcoreapp3.0
diff --git a/src/FSharpLanguageServer/Conversions.fs b/src/FSharpLanguageServer/Conversions.fs
index 0c480b3..c1635c7 100644
--- a/src/FSharpLanguageServer/Conversions.fs
+++ b/src/FSharpLanguageServer/Conversions.fs
@@ -1,4 +1,4 @@
-module FSharpLanguageServer.Conversions
+module FSharpLanguageServer.Conversions
open LSP.Log
open FSharp.Compiler
@@ -8,10 +8,8 @@ open System.IO
open LSP.Types
open FSharp.Data
-module Ast = FSharp.Compiler.Ast
-
/// Convert an F# Compiler Services 'FSharpErrorInfo' to an LSP 'Range'
-let private errorAsRange(err: FSharpErrorInfo): Range =
+let private errorAsRange(err: FSharpDiagnostic): Range =
{
// Got error "The field, constructor or member 'StartLine' is not defined"
start = {line=err.StartLineAlternate-1; character=err.StartColumn}
@@ -19,34 +17,35 @@ let private errorAsRange(err: FSharpErrorInfo): Range =
}
/// Convert an F# `Range.pos` to an LSP `Position`
-let private asPosition(p: Range.pos): Position =
+let private asPosition(p: Text.pos): Position =
{
line=p.Line-1
character=p.Column
}
/// Convert an F# `Range.range` to an LSP `Range`
-let asRange(r: Range.range): Range =
+let asRange(r: Text.range): Range =
{
start=asPosition r.Start
``end``=asPosition r.End
}
/// Convert an F# `Range.range` to an LSP `Location`
-let private asLocation(l: Range.range): Location =
- {
+let private asLocation(l: Text.range): Location =
+ {
uri=Uri("file://" + l.FileName)
- range = asRange l
+ range = asRange l
}
/// Convert an F# Compiler Services 'FSharpErrorSeverity' to an LSP 'DiagnosticSeverity'
-let private asDiagnosticSeverity(s: FSharpErrorSeverity): DiagnosticSeverity =
- match s with
- | FSharpErrorSeverity.Warning -> DiagnosticSeverity.Warning
- | FSharpErrorSeverity.Error -> DiagnosticSeverity.Error
+let private asDiagnosticSeverity(s: FSharpDiagnosticSeverity): DiagnosticSeverity =
+ match s with
+ | FSharpDiagnosticSeverity.Warning -> DiagnosticSeverity.Warning
+ | FSharpDiagnosticSeverity.Error -> DiagnosticSeverity.Error
+ | FSharpDiagnosticSeverity.Info -> DiagnosticSeverity.Information
/// Convert an F# Compiler Services 'FSharpErrorInfo' to an LSP 'Diagnostic'
-let asDiagnostic(err: FSharpErrorInfo): Diagnostic =
+let asDiagnostic(err: FSharpDiagnostic): Diagnostic =
{
range = errorAsRange(err)
severity = Some(asDiagnosticSeverity(err.Severity))
@@ -56,18 +55,18 @@ let asDiagnostic(err: FSharpErrorInfo): Diagnostic =
}
/// Create a Diagnostic
-let diagnostic(message: string, range: Range.range, severity: DiagnosticSeverity): Diagnostic =
+let diagnostic(message: string, range: Text.range, severity: DiagnosticSeverity): Diagnostic =
{
range = asRange(range)
severity = Some(severity)
- code = None
- source = None
+ code = None
+ source = None
message = message
}
-
+
/// Some compiler errors have no location in the file and should be displayed at the top of the file
-let private hasNoLocation(err: FSharpErrorInfo): bool =
- err.StartLineAlternate-1 = 0 &&
+let private hasNoLocation(err: FSharpDiagnostic): bool =
+ err.StartLineAlternate-1 = 0 &&
err.StartColumn = 0 &&
err.EndLineAlternate-1 = 0 &&
err.EndColumn = 0
@@ -76,60 +75,60 @@ let private hasNoLocation(err: FSharpErrorInfo): bool =
let errorAtTop(message: string): Diagnostic =
{
range = { start = {line=0; character=0}; ``end`` = {line=0; character=1} }
- severity = Some(DiagnosticSeverity.Error)
+ severity = Some(DiagnosticSeverity.Error)
code = None
- source = None
+ source = None
message = message
}
/// Convert a list of F# Compiler Services 'FSharpErrorInfo' to LSP 'Diagnostic'
-let asDiagnostics(errors: FSharpErrorInfo seq): Diagnostic list =
- [
- for err in errors do
- if hasNoLocation(err) then
+let asDiagnostics(errors: FSharpDiagnostic seq): Diagnostic list =
+ [
+ for err in errors do
+ if hasNoLocation(err) then
yield errorAtTop(sprintf "%s: %s" err.Subcategory err.Message)
else
- yield asDiagnostic(err)
+ yield asDiagnostic(err)
]
/// Convert an F# `FSharpToolTipElement` to an LSP `Hover`
-let asHover(FSharpToolTipText tips): Hover =
- let elements =
+let asHover(FSharpToolTipText tips): Hover =
+ let elements =
[ for t in tips do
- match t with
+ match t with
| FSharpToolTipElement.CompositionError(e) -> dprintfn "Error rendering tooltip: %s" e
- | FSharpToolTipElement.None -> ()
- | FSharpToolTipElement.Group(elements) ->
+ | FSharpToolTipElement.None -> ()
+ | FSharpToolTipElement.Group(elements) ->
yield! elements ]
- let contents =
- match elements with
+ let contents =
+ match elements with
| [] -> []
- | [one] ->
- [ yield HighlightedString(one.MainDescription, "fsharp")
- match TipFormatter.docComment(one.XmlDoc) with
+ | [one] ->
+ [ yield HighlightedString(one.MainDescription, "fsharp")
+ match TipFormatter.docComment(one.XmlDoc) with
| None -> ()
| Some(markdown) -> yield PlainString(markdown + "\n\n")
- match one.Remarks with
- | None | Some("") -> ()
- | Some(remarks) ->
+ match one.Remarks with
+ | None | Some("") -> ()
+ | Some(remarks) ->
yield PlainString("*" + remarks + "*\n\n") ]
- | many ->
+ | many ->
let last = List.last(many)
- [ for e in many do
+ [ for e in many do
yield HighlightedString(e.MainDescription, "fsharp")
- match TipFormatter.docSummaryOnly(last.XmlDoc) with
+ match TipFormatter.docSummaryOnly(last.XmlDoc) with
| None -> ()
| Some(markdown) -> yield PlainString(markdown)
- match last.Remarks with
- | None | Some("") -> ()
- | Some(remarks) ->
+ match last.Remarks with
+ | None | Some("") -> ()
+ | Some(remarks) ->
yield PlainString("*" + remarks + "*\n\n") ]
{contents=contents; range=None}
/// Convert an F# `FSharpGlyph` to an LSP `CompletionItemKind`
-let private asCompletionItemKind(k: FSharpGlyph): CompletionItemKind =
- match k with
+let private asCompletionItemKind(k: FSharpGlyph): CompletionItemKind =
+ match k with
| FSharpGlyph.Class -> CompletionItemKind.Class
| FSharpGlyph.Constant -> CompletionItemKind.Constant
| FSharpGlyph.Delegate -> CompletionItemKind.Property // ?
@@ -153,9 +152,9 @@ let private asCompletionItemKind(k: FSharpGlyph): CompletionItemKind =
| FSharpGlyph.Error -> CompletionItemKind.Class // ?
/// Convert an F# `FSharpDeclarationListItem` to an LSP `CompletionItem`
-let private asCompletionItem(i: FSharpDeclarationListItem): CompletionItem =
- { defaultCompletionItem with
- label = i.Name
+let private asCompletionItem(i: FSharpDeclarationListItem): CompletionItem =
+ { defaultCompletionItem with
+ label = i.Name
insertText = Some(i.NameInCode)
kind = Some(asCompletionItemKind(i.Glyph))
detail = Some(i.FullName)
@@ -165,12 +164,12 @@ let private asCompletionItem(i: FSharpDeclarationListItem): CompletionItem =
/// Convert an F# `FSharpDeclarationListInfo` to an LSP `CompletionList`
/// Used in rendering autocomplete lists
-let asCompletionList(ds: FSharpDeclarationListInfo): CompletionList =
+let asCompletionList(ds: FSharpDeclarationListInfo): CompletionList =
let items = [for i in ds.Items do yield asCompletionItem(i)]
{isIncomplete=List.isEmpty(items); items=items}
/// Convert an F# `FSharpMethodGroupItemParameter` to an LSP `ParameterInformation`
-let private asParameterInformation(p: FSharpMethodGroupItemParameter): ParameterInformation =
+let private asParameterInformation(p: FSharpMethodGroupItemParameter): ParameterInformation =
{
label = p.ParameterName
documentation = Some p.Display
@@ -178,37 +177,37 @@ let private asParameterInformation(p: FSharpMethodGroupItemParameter): Parameter
/// Convert an F# method name + `FSharpMethodGroupItem` to an LSP `SignatureInformation`
/// Used in providing signature help after autocompleting
-let asSignatureInformation(methodName: string, s: FSharpMethodGroupItem): SignatureInformation =
- let doc = match s.Description with
- | FSharpToolTipText [FSharpToolTipElement.Group [tip]] -> Some tip.MainDescription
- | _ ->
- dprintfn "Can't render documentation %A" s.Description
- None
+let asSignatureInformation(methodName: string, s: FSharpMethodGroupItem): SignatureInformation =
+ let doc = match s.Description with
+ | FSharpToolTipText [FSharpToolTipElement.Group [tip]] -> Some tip.MainDescription
+ | _ ->
+ dprintfn "Can't render documentation %A" s.Description
+ None
let parameterName(p: FSharpMethodGroupItemParameter) = p.ParameterName
let parameterNames = Array.map parameterName s.Parameters
{
- label = sprintf "%s(%s)" methodName (String.concat ", " parameterNames)
- documentation = doc
+ label = sprintf "%s(%s)" methodName (String.concat ", " parameterNames)
+ documentation = doc
parameters = Array.map asParameterInformation s.Parameters |> List.ofArray
}
/// Get the lcation where `s` was declared
-let declarationLocation(s: FSharpSymbol): Location option =
- match s.DeclarationLocation with
- | None ->
- dprintfn "Symbol %s has no declaration" s.FullName
- None
+let declarationLocation(s: FSharpSymbol): Location option =
+ match s.DeclarationLocation with
+ | None ->
+ dprintfn "Symbol %s has no declaration" s.FullName
+ None
| Some l ->
Some(asLocation(l))
/// Get the location where `s` was used
-let useLocation(s: FSharpSymbolUse): Location =
+let useLocation(s: FSharpSymbolUse): Location =
asLocation(s.RangeAlternate)
/// Convert an F# `FSharpNavigationDeclarationItemKind` to an LSP `SymbolKind`
/// `FSharpNavigationDeclarationItemKind` is the level of symbol-type information you get when parsing without typechecking
-let private asSymbolKind(k: FSharpNavigationDeclarationItemKind): SymbolKind =
- match k with
+let private asSymbolKind(k: FSharpNavigationDeclarationItemKind): SymbolKind =
+ match k with
| NamespaceDecl -> SymbolKind.Namespace
| ModuleFileDecl -> SymbolKind.Module
| ExnDecl -> SymbolKind.Class
@@ -222,17 +221,17 @@ let private asSymbolKind(k: FSharpNavigationDeclarationItemKind): SymbolKind =
/// Convert an F# `NavigationDeclarationItem` to an LSP `SymbolInformation`
/// `NavigationDeclarationItem` is the parsed AST representation of a symbol without typechecking
/// `container` is present when `d` is part of a module or type
-let asSymbolInformation(d: NavigationDeclarationItem, container: NavigationDeclarationItem option): SymbolInformation =
- let declarationName(d: NavigationDeclarationItem) = d.Name
+let asSymbolInformation(d: NavigationItem, container: NavigationItem option): SymbolInformation =
+ let declarationName(d: NavigationItem) = d.Name
{
- name=d.Name
- kind=asSymbolKind d.Kind
- location=asLocation d.Range
+ name=d.Name
+ kind=asSymbolKind d.Kind
+ location=asLocation d.Range
containerName=Option.map declarationName container
}
/// Convert symbols declared in an .fsi file to a CodeLens that helps the user navigate to the definition
-let asGoToImplementation(name: string list, file: FileInfo, range: Range.range): CodeLens =
+let asGoToImplementation(name: string list, file: FileInfo, range: Text.range): CodeLens =
let jsonFile = JsonValue.String(file.FullName)
let jsonName = JsonValue.Array([|for i in name do yield JsonValue.String(i)|])
{
@@ -241,13 +240,13 @@ let asGoToImplementation(name: string list, file: FileInfo, range: Range.range):
data=JsonValue.Array([|jsonFile; jsonName|])
}
-let goToImplementationData(goTo: CodeLens) =
- match goTo.data with
- | JsonValue.Array([|JsonValue.String(file); JsonValue.Array(jsonNames)|]) ->
+let goToImplementationData(goTo: CodeLens) =
+ match goTo.data with
+ | JsonValue.Array([|JsonValue.String(file); JsonValue.Array(jsonNames)|]) ->
FileInfo(file), [for JsonValue.String(j) in jsonNames do yield j ]
-let resolveGoToImplementation(unresolved: CodeLens, file: FileInfo, range: Range.range): CodeLens =
- let command =
+let resolveGoToImplementation(unresolved: CodeLens, file: FileInfo, range: Text.range): CodeLens =
+ let command =
{
title=sprintf "%s(%d)" file.Name range.StartLine
command="fsharp.command.goto"
@@ -261,8 +260,8 @@ let resolveGoToImplementation(unresolved: CodeLens, file: FileInfo, range: Range
}
{ unresolved with command = Some(command) }
-let resolveMissingGoToImplementation(unresolved: CodeLens, file: FileInfo): CodeLens =
- let command =
+let resolveMissingGoToImplementation(unresolved: CodeLens, file: FileInfo): CodeLens =
+ let command =
{
title="Not Found"
command="fsharp.command.goto"
@@ -276,7 +275,7 @@ let resolveMissingGoToImplementation(unresolved: CodeLens, file: FileInfo): Code
}
{ unresolved with command = Some(command) }
-let asRunTest(fsproj: FileInfo, fullyQualifiedName: string list, test: Ast.SynBinding): CodeLens =
+let asRunTest(fsproj: FileInfo, fullyQualifiedName: string list, test: SyntaxTree.SynBinding): CodeLens =
{
range=asRange(test.RangeOfBindingSansRhs)
command=Some({ title="Run Test"
@@ -285,11 +284,11 @@ let asRunTest(fsproj: FileInfo, fullyQualifiedName: string list, test: Ast.SynBi
data=JsonValue.Null
}
-let asDebugTest(fsproj: FileInfo, fullyQualifiedName: string list, test: Ast.SynBinding): CodeLens =
+let asDebugTest(fsproj: FileInfo, fullyQualifiedName: string list, test: SyntaxTree.SynBinding): CodeLens =
{
range=asRange(test.RangeOfBindingSansRhs)
command=Some({ title="Debug Test"
command="fsharp.command.test.debug"
arguments=[JsonValue.String(fsproj.FullName); JsonValue.String(String.concat "." fullyQualifiedName)] })
data=JsonValue.Null
- }
\ No newline at end of file
+ }
diff --git a/src/FSharpLanguageServer/FSharpLanguageServer.fsproj b/src/FSharpLanguageServer/FSharpLanguageServer.fsproj
index 926dfc5..aded62a 100644
--- a/src/FSharpLanguageServer/FSharpLanguageServer.fsproj
+++ b/src/FSharpLanguageServer/FSharpLanguageServer.fsproj
@@ -9,6 +9,7 @@
+
@@ -18,7 +19,7 @@
-
+
diff --git a/src/FSharpLanguageServer/Navigation.fs b/src/FSharpLanguageServer/Navigation.fs
index 0554b8b..9bbccb6 100644
--- a/src/FSharpLanguageServer/Navigation.fs
+++ b/src/FSharpLanguageServer/Navigation.fs
@@ -1,147 +1,152 @@
namespace FSharpLanguageServer
// Forked from https://github.com/fsharp/FSharp.Compiler.Service/blob/30127fa32cb0306cb81d95836cfee4ea5c116297/src/fsharp/service/ServiceNavigation.fs
+// 2021-04-13:
+// Small adaptation to the file based on https://github.com/dotnet/fsharp/blob/60a2fa663a3c4aed3f03c8bfc6f5e05b04284f23/src/fsharp/service/ServiceNavigation.fs
+// The changes are needed because SyntaxTreeOps functions were no longer exposed by FCS so we had to fork part of that file
// TODO this is a bit fuzzy with nesting---I think this would produce wrong answers with pathological examples like:
// module Outer1 =
-// module Inner =
-// val foo: string
-// module Outer2 =
-// module Inner =
+// module Inner =
+// val foo: string
+// module Outer2 =
+// module Inner =
// val foo: string
open FSharp.Compiler.SourceCodeServices
-open FSharp.Compiler.Range
-open FSharp.Compiler.Ast
+open FSharp.Compiler
+open FSharp.Compiler.Text.Range
+open FSharp.Compiler.SyntaxTree
+open FSharpLanguageServer.SyntaxTreeOps
/// Represents an item to be displayed in the navigation bar
[]
-type NavigationDeclarationItem(uniqueName: string, name: string, kind: FSharpNavigationDeclarationItemKind, glyph: FSharpGlyph, range: range,
- bodyRange: range, singleTopLevel: bool, enclosingEntityKind: FSharpEnclosingEntityKind, isAbstract: bool, access: SynAccess option) =
-
+type NavigationItem(uniqueName: string, name: string, kind: FSharpNavigationDeclarationItemKind, glyph: FSharpGlyph, range: Text.range,
+ bodyRange: Text.range, singleTopLevel: bool, enclosingEntityKind: FSharpEnclosingEntityKind, isAbstract: bool, access: SynAccess option) =
+
member x.bodyRange = bodyRange
member x.UniqueName = uniqueName
member x.Name = name
member x.Glyph = glyph
member x.Kind = kind
member x.Range = range
- member x.BodyRange = bodyRange
+ member x.BodyRange = bodyRange
member x.IsSingleTopLevel = singleTopLevel
member x.FSharpEnclosingEntityKind = enclosingEntityKind
member x.IsAbstract = isAbstract
-
+
member x.Access = access
-
+
member x.WithUniqueName(uniqueName: string) =
- NavigationDeclarationItem(uniqueName, name, kind, glyph, range, bodyRange, singleTopLevel, enclosingEntityKind, isAbstract, access)
- static member Create(name: string, kind, glyph: FSharpGlyph, range: range, bodyRange: range, singleTopLevel: bool, enclosingEntityKind, isAbstract, access: SynAccess option) =
- NavigationDeclarationItem("", name, kind, glyph, range, bodyRange, singleTopLevel, enclosingEntityKind, isAbstract, access)
+ NavigationItem(uniqueName, name, kind, glyph, range, bodyRange, singleTopLevel, enclosingEntityKind, isAbstract, access)
+ static member Create(name: string, kind, glyph: FSharpGlyph, range: Text.range, bodyRange: Text.range, singleTopLevel: bool, enclosingEntityKind, isAbstract, access: SynAccess option) =
+ NavigationItem("", name, kind, glyph, range, bodyRange, singleTopLevel, enclosingEntityKind, isAbstract, access)
/// Represents top-level declarations (that should be in the type drop-down)
/// with nested declarations (that can be shown in the member drop-down)
[]
-type FSharpNavigationTopLevelDeclaration =
- { Declaration: NavigationDeclarationItem
- Nested: NavigationDeclarationItem[] }
-
+type FSharpNavigationTopLevelDeclaration =
+ { Declaration: NavigationItem
+ Nested: NavigationItem[] }
+
/// Represents result of 'GetNavigationItems' operation - this contains
/// all the members and currently selected indices. First level correspond to
/// types & modules and second level are methods etc.
[]
type FSharpNavigationItems(declarations:FSharpNavigationTopLevelDeclaration[]) =
member x.Declarations = declarations
-
-module Navigation =
- let unionRangesChecked r1 r2 = if r1 = range.Zero then r2 elif r2 = range.Zero then r1 else unionRanges r1 r2
-
- let rangeOfDecls2 f decls =
- match (decls |> List.map (f >> (fun (d:NavigationDeclarationItem) -> d.bodyRange))) with
+
+module Navigation =
+ let unionRangesChecked r1 r2 = if r1 = Text.range.Zero then r2 elif r2 = Text.range.Zero then r1 else unionRanges r1 r2
+
+ let rangeOfDecls2 f decls =
+ match (decls |> List.map (f >> (fun (d: NavigationItem) -> d.bodyRange))) with
| hd::tl -> tl |> List.fold unionRangesChecked hd
- | [] -> range.Zero
-
+ | [] -> Text.range.Zero
+
let rangeOfDecls = rangeOfDecls2 fst
- let moduleRange (idm:range) others =
+ let moduleRange (idm:Text.range) others =
unionRangesChecked idm.EndRange (rangeOfDecls2 (fun (a, _, _) -> a) others)
-
+
let fldspecRange fldspec =
match fldspec with
- | UnionCaseFields(flds) -> flds |> List.fold (fun st (Field(_, _, _, _, _, _, _, m)) -> unionRangesChecked m st) range.Zero
+ | UnionCaseFields(flds) -> flds |> List.fold (fun st (Field(_, _, _, _, _, _, _, m)) -> unionRangesChecked m st) Text.range.Zero
| UnionCaseFullType(ty, _) -> ty.Range
-
+
let bodyRange mb decls =
unionRangesChecked (rangeOfDecls decls) mb
-
- /// Get information for implementation file
+
+ /// Get information for implementation file
let getNavigationFromImplFile (modules: SynModuleOrNamespace list) =
// Map for dealing with name conflicts
- let nameMap = ref Map.empty
+ let nameMap = ref Map.empty
- let addItemName name =
+ let addItemName name =
let count = defaultArg (!nameMap |> Map.tryFind name) 0
nameMap := (Map.add name (count + 1) (!nameMap))
(count + 1)
-
- let uniqueName name idx =
+
+ let uniqueName name idx =
let total = Map.find name (!nameMap)
sprintf "%s_%d_of_%d" name idx total
- // Create declaration (for the left dropdown)
+ // Create declaration (for the left dropdown)
let createDeclLid(baseName, lid, kind, baseGlyph, m, bodym, nested, enclosingEntityKind, isAbstract, access) =
let name = (if baseName <> "" then baseName + "." else "") + (textOfLid lid)
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(name, kind, baseGlyph, m, bodym, false, enclosingEntityKind, isAbstract, access), (addItemName name), nested
-
+
let createDecl(baseName, id:Ident, kind, baseGlyph, m, bodym, nested, enclosingEntityKind, isAbstract, access) =
let name = (if baseName <> "" then baseName + "." else "") + (id.idText)
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(name, kind, baseGlyph, m, bodym, false, enclosingEntityKind, isAbstract, access), (addItemName name), nested
-
+
// Create member-kind-of-thing for the right dropdown
let createMemberLid(lid, kind, baseGlyph, m, enclosingEntityKind, isAbstract, access) =
- NavigationDeclarationItem.Create(textOfLid lid, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(textOfLid lid))
+ NavigationItem.Create(textOfLid lid, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(textOfLid lid))
let createMember(id:Ident, kind, baseGlyph, m, enclosingEntityKind, isAbstract, access) =
- NavigationDeclarationItem.Create(id.idText, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(id.idText))
+ NavigationItem.Create(id.idText, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(id.idText))
// Process let-binding
let processBinding isMember enclosingEntityKind isAbstract (Binding(_, _, _, _, _, _, SynValData(memebrOpt, _, _), synPat, _, synExpr, _, _)) =
- let m =
- match synExpr with
+ let m =
+ match synExpr with
| SynExpr.Typed(e, _, _) -> e.Range // fix range for properties with type annotations
| _ -> synExpr.Range
match synPat, memebrOpt with
- | SynPat.LongIdent(longDotId=LongIdentWithDots(lid,_); accessibility=access), Some(flags) when isMember ->
+ | SynPat.LongIdent(longDotId=LongIdentWithDots(lid,_); accessibility=access), Some(flags) when isMember ->
let icon, kind =
match flags.MemberKind with
| MemberKind.ClassConstructor
| MemberKind.Constructor
- | MemberKind.Member ->
+ | MemberKind.Member ->
(if flags.IsOverrideOrExplicitImpl then FSharpGlyph.OverridenMethod else FSharpGlyph.Method), MethodDecl
| MemberKind.PropertyGetSet
| MemberKind.PropertySet
| MemberKind.PropertyGet -> FSharpGlyph.Property, PropertyDecl
- let lidShow, rangeMerge =
- match lid with
- | _thisVar::nm::_ -> (List.tail lid, nm.idRange)
- | hd::_ -> (lid, hd.idRange)
+ let lidShow, rangeMerge =
+ match lid with
+ | _thisVar::nm::_ -> (List.tail lid, nm.idRange)
+ | hd::_ -> (lid, hd.idRange)
| _ -> (lid, m)
[ createMemberLid(lidShow, kind, icon, unionRanges rangeMerge m, enclosingEntityKind, isAbstract, access) ]
- | SynPat.LongIdent(LongIdentWithDots(lid,_), _, _, _, access, _), _ ->
+ | SynPat.LongIdent(LongIdentWithDots(lid,_), _, _, _, access, _), _ ->
[ createMemberLid(lid, FieldDecl, FSharpGlyph.Field, unionRanges (List.head lid).idRange m, enclosingEntityKind, isAbstract, access) ]
- | SynPat.Named(_, id, _, access, _), _ ->
+ | SynPat.Named(_, id, _, access, _), _ ->
let glyph = if isMember then FSharpGlyph.Method else FSharpGlyph.Field
[ createMember(id, FieldDecl, glyph, unionRanges id.idRange m, enclosingEntityKind, isAbstract, access) ]
| _ -> []
-
+
// Process a class declaration or F# type declaration
let rec processExnDefnRepr baseName nested (SynExceptionDefnRepr(_, (UnionCase(_, id, fldspec, _, _, _)), _, _, access, m)) =
// Exception declaration
- [ createDecl(baseName, id, ExnDecl, FSharpGlyph.Exception, m, fldspecRange fldspec, nested, FSharpEnclosingEntityKind.Exception, false, access) ]
+ [ createDecl(baseName, id, ExnDecl, FSharpGlyph.Exception, m, fldspecRange fldspec, nested, FSharpEnclosingEntityKind.Exception, false, access) ]
// Process a class declaration or F# type declaration
- and processExnDefn baseName (SynExceptionDefn(repr, membDefns, _)) =
+ and processExnDefn baseName (SynExceptionDefn(repr, membDefns, _)) =
let nested = processMembers membDefns FSharpEnclosingEntityKind.Exception |> snd
processExnDefnRepr baseName nested repr
@@ -153,24 +158,24 @@ module Navigation =
// F# class declaration
let members = processMembers membDefns FSharpEnclosingEntityKind.Class |> snd
let nested = members@topMembers
- ([ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Class, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Class, false, access) ]: ((NavigationDeclarationItem * int * _) list))
+ ([ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Class, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Class, false, access) ]: ((NavigationItem * int * _) list))
| SynTypeDefnRepr.Simple(simple, _) ->
// F# type declaration
match simple with
| SynTypeDefnSimpleRepr.Union(_, cases, mb) ->
- let cases =
- [ for (UnionCase(_, id, fldspec, _, _, _)) in cases ->
+ let cases =
+ [ for (UnionCase(_, id, fldspec, _, _, _)) in cases ->
createMember(id, OtherDecl, FSharpGlyph.Struct, unionRanges (fldspecRange fldspec) id.idRange, FSharpEnclosingEntityKind.DU, false, access) ]
- let nested = cases@topMembers
+ let nested = cases@topMembers
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Union, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.DU, false, access) ]
- | SynTypeDefnSimpleRepr.Enum(cases, mb) ->
- let cases =
+ | SynTypeDefnSimpleRepr.Enum(cases, mb) ->
+ let cases =
[ for (EnumCase(_, id, _, _, m)) in cases ->
createMember(id, FieldDecl, FSharpGlyph.EnumMember, m, FSharpEnclosingEntityKind.Enum, false, access) ]
let nested = cases@topMembers
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Enum, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Enum, false, access) ]
| SynTypeDefnSimpleRepr.Record(_, fields, mb) ->
- let fields =
+ let fields =
[ for (Field(_, _, id, _, _, _, _, m)) in fields do
if (id.IsSome) then
yield createMember(id.Value, FieldDecl, FSharpGlyph.Field, m, FSharpEnclosingEntityKind.Record, false, access) ]
@@ -178,16 +183,16 @@ module Navigation =
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Type, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Record, false, access) ]
| SynTypeDefnSimpleRepr.TypeAbbrev(_, _, mb) ->
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Typedef, m, bodyRange mb topMembers, topMembers, FSharpEnclosingEntityKind.Class, false, access) ]
-
- //| SynTypeDefnSimpleRepr.General of TyconKind * (SynType * range * ident option) list * (valSpfn * MemberFlags) list * fieldDecls * bool * bool * range
+
+ //| SynTypeDefnSimpleRepr.General of TyconKind * (SynType * range * ident option) list * (valSpfn * MemberFlags) list * fieldDecls * bool * bool * range
//| SynTypeDefnSimpleRepr.LibraryOnlyILAssembly of ILType * range
//| TyconCore_repr_hidden of range
- | _ -> []
-
- // Returns class-members for the right dropdown
- and processMembers members enclosingEntityKind : (range * list) =
- let members =
- members
+ | _ -> []
+
+ // Returns class-members for the right dropdown
+ and processMembers members enclosingEntityKind : (Text.range * list) =
+ let members =
+ members
|> List.groupBy (fun x -> x.Range)
|> List.map (fun (range, members) ->
range,
@@ -198,14 +203,14 @@ module Navigation =
| SynMemberDefn.Member(bind, _) -> processBinding true enclosingEntityKind false bind
| SynMemberDefn.ValField(Field(_, _, Some(rcid), ty, _, _, access, _), _) ->
[ createMember(rcid, FieldDecl, FSharpGlyph.Field, ty.Range, enclosingEntityKind, false, access) ]
- | SynMemberDefn.AutoProperty(_attribs,_isStatic,id,_tyOpt,_propKind,_,_xmlDoc, access,_synExpr, _, _) ->
+ | SynMemberDefn.AutoProperty(_attribs,_isStatic,id,_tyOpt,_propKind,_,_xmlDoc, access,_synExpr, _, _) ->
[ createMember(id, FieldDecl, FSharpGlyph.Field, id.idRange, enclosingEntityKind, false, access) ]
| SynMemberDefn.AbstractSlot(ValSpfn(_, id, _, ty, _, _, _, _, access, _, _), _, _) ->
[ createMember(id, MethodDecl, FSharpGlyph.OverridenMethod, ty.Range, enclosingEntityKind, true, access) ]
- | SynMemberDefn.NestedType _ -> failwith "tycon as member????" //processTycon tycon
+ | SynMemberDefn.NestedType _ -> failwith "tycon as member????" //processTycon tycon
| SynMemberDefn.Interface(_, Some(membs), _) ->
processMembers membs enclosingEntityKind |> snd
- | _ -> []
+ | _ -> []
// can happen if one is a getter and one is a setter
| [SynMemberDefn.Member(memberDefn=Binding(headPat=SynPat.LongIdent(lid1, Some(info1),_,_,_,_)) as binding1)
SynMemberDefn.Member(memberDefn=Binding(headPat=SynPat.LongIdent(lid2, Some(info2),_,_,_,_)) as binding2)] ->
@@ -218,42 +223,42 @@ module Navigation =
match processBinding true enclosingEntityKind false binding1 with
| [] -> processBinding true enclosingEntityKind false binding2
| x -> x
- | _ -> []))
-
- (members |> Seq.map fst |> Seq.fold unionRangesChecked range.Zero),
+ | _ -> []))
+
+ (members |> Seq.map fst |> Seq.fold unionRangesChecked Text.range.Zero),
(members |> List.map snd |> List.concat)
// Process declarations in a module that belong to the right drop-down (let bindings)
let processNestedDeclarations decls = decls |> List.collect (function
| SynModuleDecl.Let(_, binds, _) -> List.collect (processBinding false FSharpEnclosingEntityKind.Module false) binds
- | _ -> [])
+ | _ -> [])
// Process declarations nested in a module that should be displayed in the left dropdown
- // (such as type declarations, nested modules etc.)
+ // (such as type declarations, nested modules etc.)
let rec processFSharpNavigationTopLevelDeclarations(baseName, decls) = decls |> List.collect (function
| SynModuleDecl.ModuleAbbrev(id, lid, m) ->
[ createDecl(baseName, id, ModuleDecl, FSharpGlyph.Module, m, rangeOfLid lid, [], FSharpEnclosingEntityKind.Namespace, false, None) ]
-
+
| SynModuleDecl.NestedModule(ComponentInfo(_, _, _, lid, _, _, access, _), _isRec, decls, _, m) ->
// Find let bindings (for the right dropdown)
let nested = processNestedDeclarations(decls)
let newBaseName = (if (baseName = "") then "" else baseName+".") + (textOfLid lid)
-
+
// Get nested modules and types (for the left dropdown)
let other = processFSharpNavigationTopLevelDeclarations(newBaseName, decls)
createDeclLid(baseName, lid, ModuleDecl, FSharpGlyph.Module, m, unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid lid) other), nested, FSharpEnclosingEntityKind.Module, false, access) :: other
-
- | SynModuleDecl.Types(tydefs, _) -> tydefs |> List.collect (processTycon baseName)
+
+ | SynModuleDecl.Types(tydefs, _) -> tydefs |> List.collect (processTycon baseName)
| SynModuleDecl.Exception (defn,_) -> processExnDefn baseName defn
| _ -> [])
- // Collect all the items
- let items =
+ // Collect all the items
+ let items =
// Show base name for this module only if it's not the root one
let singleTopLevel = (modules.Length = 1)
modules |> List.collect (fun (SynModuleOrNamespace(id, _isRec, kind, decls, _, _, access, m)) ->
- let isModule =
- match kind with
+ let isModule =
+ match kind with
| SynModuleOrNamespaceKind.AnonModule | SynModuleOrNamespaceKind.NamedModule -> true
| _ -> false
let baseName = if (not singleTopLevel) then textOfLid id else ""
@@ -267,54 +272,54 @@ module Navigation =
| [] -> other
| _ ->
let decl =
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(textOfLid id, (if isModule then ModuleFileDecl else NamespaceDecl),
- FSharpGlyph.Module, m,
- unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid id) other),
+ FSharpGlyph.Module, m,
+ unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid id) other),
singleTopLevel, FSharpEnclosingEntityKind.Module, false, access), (addItemName(textOfLid id)), nested
decl::other)
-
- let items =
- items
- |> Array.ofList
- |> Array.map (fun (d, idx, nest) ->
+
+ let items =
+ items
+ |> Array.ofList
+ |> Array.map (fun (d, idx, nest) ->
let nest = nest |> Array.ofList |> Array.map (fun (decl, idx) -> decl.WithUniqueName(uniqueName d.Name idx))
nest |> Array.sortInPlaceWith (fun a b -> compare a.Name b.Name)
- { Declaration = d.WithUniqueName(uniqueName d.Name idx); Nested = nest } )
+ { Declaration = d.WithUniqueName(uniqueName d.Name idx); Nested = nest } )
items |> Array.sortInPlaceWith (fun a b -> compare a.Declaration.Name b.Declaration.Name)
new FSharpNavigationItems(items)
- /// Get information for signature file
+ /// Get information for signature file
let getNavigationFromSigFile (modules: SynModuleOrNamespaceSig list) =
// Map for dealing with name conflicts
- let nameMap = ref Map.empty
- let addItemName name =
+ let nameMap = ref Map.empty
+ let addItemName name =
let count = defaultArg (!nameMap |> Map.tryFind name) 0
nameMap := (Map.add name (count + 1) (!nameMap))
(count + 1)
- let uniqueName name idx =
+ let uniqueName name idx =
let total = Map.find name (!nameMap)
sprintf "%s_%d_of_%d" name idx total
- // Create declaration (for the left dropdown)
+ // Create declaration (for the left dropdown)
let createDeclLid(baseName, lid, kind, baseGlyph, m, bodym, nested, enclosingEntityKind, isAbstract, access) =
let name = (if baseName <> "" then baseName + "." else "") + (textOfLid lid)
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(name, kind, baseGlyph, m, bodym, false, enclosingEntityKind, isAbstract, access), (addItemName name), nested
-
+
let createDecl(baseName, id:Ident, kind, baseGlyph, m, bodym, nested, enclosingEntityKind, isAbstract, access) =
let name = (if baseName <> "" then baseName + "." else "") + (id.idText)
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(name, kind, baseGlyph, m, bodym, false, enclosingEntityKind, isAbstract, access), (addItemName name), nested
-
+
let createMember(id:Ident, kind, baseGlyph, m, enclosingEntityKind, isAbstract, access) =
- NavigationDeclarationItem.Create(id.idText, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(id.idText))
+ NavigationItem.Create(id.idText, kind, baseGlyph, m, m, false, enclosingEntityKind, isAbstract, access), (addItemName(id.idText))
let rec processExnRepr baseName nested (SynExceptionDefnRepr(_, (UnionCase(_, id, fldspec, _, _, _)), _, _, access, m)) =
// Exception declaration
- [ createDecl(baseName, id, ExnDecl, FSharpGlyph.Exception, m, fldspecRange fldspec, nested, FSharpEnclosingEntityKind.Exception, false, access) ]
-
- and processExnSig baseName (SynExceptionSig(repr, memberSigs, _)) =
+ [ createDecl(baseName, id, ExnDecl, FSharpGlyph.Exception, m, fldspecRange fldspec, nested, FSharpEnclosingEntityKind.Exception, false, access) ]
+
+ and processExnSig baseName (SynExceptionSig(repr, memberSigs, _)) =
let nested = processSigMembers memberSigs
processExnRepr baseName nested repr
@@ -326,24 +331,24 @@ module Navigation =
// F# class declaration
let members = processSigMembers membDefns
let nested = members @ topMembers
- ([ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Class, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Class, false, access) ]: ((NavigationDeclarationItem * int * _) list))
+ ([ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Class, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Class, false, access) ]: ((NavigationItem * int * _) list))
| SynTypeDefnSigRepr.Simple(simple, _) ->
// F# type declaration
match simple with
| SynTypeDefnSimpleRepr.Union(_, cases, mb) ->
- let cases =
- [ for (UnionCase(_, id, fldspec, _, _, _)) in cases ->
+ let cases =
+ [ for (UnionCase(_, id, fldspec, _, _, _)) in cases ->
createMember(id, OtherDecl, FSharpGlyph.Struct, unionRanges (fldspecRange fldspec) id.idRange, FSharpEnclosingEntityKind.DU, false, access) ]
- let nested = cases@topMembers
+ let nested = cases@topMembers
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Union, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.DU, false, access) ]
- | SynTypeDefnSimpleRepr.Enum(cases, mb) ->
- let cases =
+ | SynTypeDefnSimpleRepr.Enum(cases, mb) ->
+ let cases =
[ for (EnumCase(_, id, _, _, m)) in cases ->
createMember(id, FieldDecl, FSharpGlyph.EnumMember, m, FSharpEnclosingEntityKind.Enum, false, access) ]
let nested = cases@topMembers
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Enum, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Enum, false, access) ]
| SynTypeDefnSimpleRepr.Record(_, fields, mb) ->
- let fields =
+ let fields =
[ for (Field(_, _, id, _, _, _, _, m)) in fields do
if (id.IsSome) then
yield createMember(id.Value, FieldDecl, FSharpGlyph.Field, m, FSharpEnclosingEntityKind.Record, false, access) ]
@@ -351,13 +356,13 @@ module Navigation =
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Type, m, bodyRange mb nested, nested, FSharpEnclosingEntityKind.Record, false, access) ]
| SynTypeDefnSimpleRepr.TypeAbbrev(_, _, mb) ->
[ createDeclLid(baseName, lid, TypeDecl, FSharpGlyph.Typedef, m, bodyRange mb topMembers, topMembers, FSharpEnclosingEntityKind.Class, false, access) ]
-
- //| SynTypeDefnSimpleRepr.General of TyconKind * (SynType * range * ident option) list * (valSpfn * MemberFlags) list * fieldDecls * bool * bool * range
+
+ //| SynTypeDefnSimpleRepr.General of TyconKind * (SynType * range * ident option) list * (valSpfn * MemberFlags) list * fieldDecls * bool * bool * range
//| SynTypeDefnSimpleRepr.LibraryOnlyILAssembly of ILType * range
//| TyconCore_repr_hidden of range
- | _ -> []
-
- and processSigMembers (members: SynMemberSig list): list =
+ | _ -> []
+
+ and processSigMembers (members: SynMemberSig list): list =
[ for memb in members do
match memb with
| SynMemberSig.Member(SynValSig.ValSpfn(_, id, _, _, _, _, _, _, access, _, m), _, _) ->
@@ -370,34 +375,34 @@ module Navigation =
let processNestedSigDeclarations decls = decls |> List.collect (function
| SynModuleSigDecl.Val(SynValSig.ValSpfn(_, id, _, _, _, _, _, _, access, _, m), _) ->
[ createMember(id, MethodDecl, FSharpGlyph.Method, m, FSharpEnclosingEntityKind.Module, false, access) ]
- | _ -> [] )
+ | _ -> [] )
// Process declarations nested in a module that should be displayed in the left dropdown
- // (such as type declarations, nested modules etc.)
+ // (such as type declarations, nested modules etc.)
let rec processFSharpNavigationTopLevelSigDeclarations(baseName, decls) = decls |> List.collect (function
| SynModuleSigDecl.ModuleAbbrev(id, lid, m) ->
[ createDecl(baseName, id, ModuleDecl, FSharpGlyph.Module, m, rangeOfLid lid, [], FSharpEnclosingEntityKind.Module, false, None) ]
-
- | SynModuleSigDecl.NestedModule(ComponentInfo(_, _, _, lid, _, _, access, _), _, decls, m) ->
+
+ | SynModuleSigDecl.NestedModule(ComponentInfo(_, _, _, lid, _, _, access, _), _, decls, m) ->
// Find let bindings (for the right dropdown)
let nested = processNestedSigDeclarations(decls)
let newBaseName = (if (baseName = "") then "" else baseName+".") + (textOfLid lid)
-
+
// Get nested modules and types (for the left dropdown)
let other = processFSharpNavigationTopLevelSigDeclarations(newBaseName, decls)
createDeclLid(baseName, lid, ModuleDecl, FSharpGlyph.Module, m, unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid lid) other), nested, FSharpEnclosingEntityKind.Module, false, access)::other
-
- | SynModuleSigDecl.Types(tydefs, _) -> tydefs |> List.collect (processTycon baseName)
+
+ | SynModuleSigDecl.Types(tydefs, _) -> tydefs |> List.collect (processTycon baseName)
| SynModuleSigDecl.Exception (defn,_) -> processExnSig baseName defn
| _ -> [])
-
- // Collect all the items
- let items =
+
+ // Collect all the items
+ let items =
// Show base name for this module only if it's not the root one
let singleTopLevel = (modules.Length = 1)
modules |> List.collect (fun (SynModuleOrNamespaceSig(id, _isRec, kind, decls, _, _, access, m)) ->
- let isModule =
- match kind with
+ let isModule =
+ match kind with
| SynModuleOrNamespaceKind.AnonModule | SynModuleOrNamespaceKind.NamedModule -> true
| _ -> false
let baseName = if (not singleTopLevel) then textOfLid id else ""
@@ -405,24 +410,24 @@ module Navigation =
let nested = processNestedSigDeclarations(decls)
// Get nested modules and types (for the left dropdown)
let other = processFSharpNavigationTopLevelSigDeclarations(baseName, decls)
-
+
// Create explicitly - it can be 'single top level' thing that is hidden
let decl =
- NavigationDeclarationItem.Create
+ NavigationItem.Create
(textOfLid id, (if isModule then ModuleFileDecl else NamespaceDecl),
- FSharpGlyph.Module, m,
- unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid id) other),
+ FSharpGlyph.Module, m,
+ unionRangesChecked (rangeOfDecls nested) (moduleRange (rangeOfLid id) other),
singleTopLevel, FSharpEnclosingEntityKind.Module, false, access), (addItemName(textOfLid id)), nested
decl::other)
-
- let items =
- items
- |> Array.ofList
- |> Array.map (fun (d, idx, nest) ->
+
+ let items =
+ items
+ |> Array.ofList
+ |> Array.map (fun (d, idx, nest) ->
let nest = nest |> Array.ofList |> Array.map (fun (decl, idx) -> decl.WithUniqueName(uniqueName d.Name idx))
nest |> Array.sortInPlaceWith (fun a b -> compare a.Name b.Name)
- let nest = nest |> Array.distinctBy (fun x -> x.Range, x.BodyRange, x.Name, x.Kind)
-
- { Declaration = d.WithUniqueName(uniqueName d.Name idx); Nested = nest } )
+ let nest = nest |> Array.distinctBy (fun x -> x.Range, x.BodyRange, x.Name, x.Kind)
+
+ { Declaration = d.WithUniqueName(uniqueName d.Name idx); Nested = nest } )
items |> Array.sortInPlaceWith (fun a b -> compare a.Declaration.Name b.Declaration.Name)
- new FSharpNavigationItems(items)
\ No newline at end of file
+ new FSharpNavigationItems(items)
diff --git a/src/FSharpLanguageServer/Program.fs b/src/FSharpLanguageServer/Program.fs
index 90a2a76..e95e349 100644
--- a/src/FSharpLanguageServer/Program.fs
+++ b/src/FSharpLanguageServer/Program.fs
@@ -14,37 +14,35 @@ open FSharp.Data
open FSharp.Data.JsonExtensions
open Conversions
-module Ast = FSharp.Compiler.Ast
-
let private TODO() = raise (Exception "TODO")
/// Look for a method call like foo.MyMethod() before the cursor
/// (exposed for testing)
-let findMethodCallBeforeCursor(lineContent: string, cursor: int): int option =
+let findMethodCallBeforeCursor(lineContent: string, cursor: int): int option =
let mutable found = -1
let mutable parenDepth = 0
- for i in (min (cursor-1) lineContent.Length) .. -1 .. 0 do
- match lineContent.[i] with
+ for i in (min (cursor-1) lineContent.Length) .. -1 .. 0 do
+ match lineContent.[i] with
| ')' -> parenDepth <- parenDepth + 1
| '(' when parenDepth > 0 -> parenDepth <- parenDepth - 1
| '(' when found = -1 -> found <- i
| _ -> ()
- if found = -1 then None
- else
+ if found = -1 then None
+ else
let prefix = lineContent.Substring(0, found).TrimEnd()
- if Regex(@"let[ \w]+$").IsMatch(prefix) then
- dprintfn "No signature help in let expression %s" lineContent
- None
- else if Regex(@"member[ \w\.]+$").IsMatch(prefix) then
- dprintfn "No signature help in member expression %s" lineContent
- None
+ if Regex(@"let[ \w]+$").IsMatch(prefix) then
+ dprintfn "No signature help in let expression %s" lineContent
+ None
+ else if Regex(@"member[ \w\.]+$").IsMatch(prefix) then
+ dprintfn "No signature help in member expression %s" lineContent
+ None
else Some prefix.Length
/// Figure out the active parameter by counting ',' characters
-let private countCommas(lineContent: string, endOfMethodName: int, cursor: int): int =
+let private countCommas(lineContent: string, endOfMethodName: int, cursor: int): int =
let mutable count = 0
- for i in endOfMethodName .. (min (cursor-1) lineContent.Length) do
- if lineContent.[i] = ',' then
+ for i in endOfMethodName .. (min (cursor-1) lineContent.Length) do
+ if lineContent.[i] = ',' then
count <- count + 1
count
@@ -52,74 +50,74 @@ let private countCommas(lineContent: string, endOfMethodName: int, cursor: int):
/// Matches can be discontinuous if the letters of `find` match the first letters of words in `candidate`
/// For example, fb matches FooBar, but it doesn't match Foobar
/// (exposed for testing)
-let matchesTitleCase(find: string, candidate: string): bool =
+let matchesTitleCase(find: string, candidate: string): bool =
let mutable i = 0
- let lowerEquals(x, y) =
+ let lowerEquals(x, y) =
Char.ToLower(x) = Char.ToLower(y)
- let matchNextChar(f) =
- if i < candidate.Length && lowerEquals(candidate.[i], f) then
- i <- i + 1
- true
- else false
- let isStartOfWord(i) =
+ let matchNextChar(f) =
+ if i < candidate.Length && lowerEquals(candidate.[i], f) then
+ i <- i + 1
+ true
+ else false
+ let isStartOfWord(i) =
0 <= i && i < candidate.Length && Char.IsUpper(candidate.[i])
- let matchStartOfNextWord(f) =
+ let matchStartOfNextWord(f) =
let test(i) = isStartOfWord(i) && lowerEquals(candidate.[i], f)
while i < candidate.Length && not(test(i)) do
- i <- i + 1
+ i <- i + 1
test(i)
let mutable matched = true
- for f in find do
+ for f in find do
matched <- matched && (matchNextChar(f) || matchStartOfNextWord(f))
matched
/// Check if an F# symbol matches a query typed by the user
-let private matchesQuery(query: string, candidate: string): bool =
+let private matchesQuery(query: string, candidate: string): bool =
matchesTitleCase(query, candidate)
/// Find the first overload in `method` that is compatible with `activeParameter`
// TODO actually consider types
-let private findCompatibleOverload(activeParameter: int, methods: FSharpMethodGroupItem[]): int option =
- let mutable result = -1
- for i in 0 .. methods.Length - 1 do
- if result = -1 && (activeParameter = 0 || activeParameter < methods.[i].Parameters.Length) then
- result <- i
+let private findCompatibleOverload(activeParameter: int, methods: FSharpMethodGroupItem[]): int option =
+ let mutable result = -1
+ for i in 0 .. methods.Length - 1 do
+ if result = -1 && (activeParameter = 0 || activeParameter < methods.[i].Parameters.Length) then
+ result <- i
if result = -1 then None else Some result
/// Find searchable declarations
-let private findDeclarations(parse: FSharpParseFileResults) =
- let items =
- match parse.ParseTree with
- | Some(Ast.ParsedInput.SigFile(Ast.ParsedSigFileInput(_, _, _, _, modules))) ->
+let private findDeclarations(parse: FSharpParseFileResults) =
+ let items =
+ match parse.ParseTree with
+ | Some(SyntaxTree.ParsedInput.SigFile(SyntaxTree.ParsedSigFileInput(_, _, _, _, modules))) ->
Navigation.getNavigationFromSigFile(modules).Declarations
- | Some(Ast.ParsedInput.ImplFile(Ast.ParsedImplFileInput(_, _, _, _, _, modules, _))) ->
+ | Some(SyntaxTree.ParsedInput.ImplFile(SyntaxTree.ParsedImplFileInput(_, _, _, _, _, modules, _))) ->
Navigation.getNavigationFromImplFile(modules).Declarations
| _ -> [||]
- [ for i in items do
+ [ for i in items do
yield i.Declaration, None
- for n in i.Nested do
+ for n in i.Nested do
yield n, Some(i.Declaration) ]
-let private findSignatureDeclarations(parse: FSharpParseFileResults) =
- match parse.ParseTree with
- | Some(Ast.ParsedInput.SigFile(Ast.ParsedSigFileInput(_, _, _, _, modules))) ->
+let private findSignatureDeclarations(parse: FSharpParseFileResults) =
+ match parse.ParseTree with
+ | Some(SyntaxTree.ParsedInput.SigFile(SyntaxTree.ParsedSigFileInput(_, _, _, _, modules))) ->
let items = Navigation.getNavigationFromSigFile(modules)
- [ for i in items.Declarations do
- for n in i.Nested do
+ [ for i in items.Declarations do
+ for n in i.Nested do
yield [i.Declaration.Name; n.Name], n.Range ]
| _ -> []
-let private findSignatureImplementation(parse: FSharpParseFileResults, name: string list) =
- match parse.ParseTree with
- | Some(Ast.ParsedInput.ImplFile(Ast.ParsedImplFileInput(_, _, _, _, _, modules, _))) ->
+let private findSignatureImplementation(parse: FSharpParseFileResults, name: string list) =
+ match parse.ParseTree with
+ | Some(SyntaxTree.ParsedInput.ImplFile(SyntaxTree.ParsedImplFileInput(_, _, _, _, _, modules, _))) ->
let items = Navigation.getNavigationFromImplFile(modules)
- [ for i in items.Declarations do
- for n in i.Nested do
+ [ for i in items.Declarations do
+ for n in i.Nested do
if [i.Declaration.Name; n.Name] = name then yield n.Range ]
| _ -> []
/// Find functions annotated with []
-let private testFunctions(parse: FSharpParseFileResults): (string list * Ast.SynBinding) list =
+let private testFunctions(parse: FSharpParseFileResults): (string list * SyntaxTree.SynBinding) list =
let (|XunitTest|_|) str =
match str with
| "Fact" | "Xunit.FactAttribute"
@@ -129,108 +127,108 @@ let private testFunctions(parse: FSharpParseFileResults): (string list * Ast.Syn
match str with
| "Test" | "NUnit.Framework.Test" -> Some true
| _ -> None
- let isTestAttribute(a: Ast.SynAttribute): bool =
+ let isTestAttribute(a: SyntaxTree.SynAttribute): bool =
let ids = a.TypeName.Lid
let string = String.concat "." [for i in ids do yield i.idText]
- match string with
+ match string with
// TODO check for open NUnit.Framework before accepting plain "Test"
| NUnitTest _ | XunitTest _ -> true
| _ -> false
- let isTestFunction(binding: Ast.SynBinding): bool =
- let attrs = match binding with Ast.Binding(_, _, _, _, attrs, _, _, _, _, _, _, _) -> attrs
+ let isTestFunction(binding: SyntaxTree.SynBinding): bool =
+ let attrs = match binding with SyntaxTree.Binding(_, _, _, _, attrs, _, _, _, _, _, _, _) -> attrs
let mutable found = false
- for list in attrs do
- for a in list.Attributes do
- if isTestAttribute(a) then
+ for list in attrs do
+ for a in list.Attributes do
+ if isTestAttribute(a) then
found <- true
found
- let name(binding: Ast.SynBinding): string list =
- match binding with
- | Ast.Binding(_, _, _, _, _, _, _, Ast.SynPat.LongIdent(Ast.LongIdentWithDots(ids, _), _, _, _, _, _), _, _, _, _) ->
+ let name(binding: SyntaxTree.SynBinding): string list =
+ match binding with
+ | SyntaxTree.Binding(_, _, _, _, _, _, _, SyntaxTree.SynPat.LongIdent(SyntaxTree.LongIdentWithDots(ids, _), _, _, _, _, _), _, _, _, _) ->
[for i in ids do yield i.idText]
| _ -> []
- let rec bindings(ctx: string list, m: Ast.SynModuleDecl): (string list * Ast.SynBinding) seq =
+ let rec bindings(ctx: string list, m: SyntaxTree.SynModuleDecl): (string list * SyntaxTree.SynBinding) seq =
seq {
- match m with
- | Ast.SynModuleDecl.NestedModule(outer, _, decls, _, _) ->
- let ids = match outer with Ast.ComponentInfo(_, _, _, ids, _, _, _, _) -> ids
+ match m with
+ | SyntaxTree.SynModuleDecl.NestedModule(outer, _, decls, _, _) ->
+ let ids = match outer with SyntaxTree.ComponentInfo(_, _, _, ids, _, _, _, _) -> ids
let ctx = ctx@[for i in ids do yield i.idText]
- for d in decls do
+ for d in decls do
yield! bindings(ctx, d)
- | Ast.SynModuleDecl.Let(_, bindings, _) ->
- for b in bindings do
+ | SyntaxTree.SynModuleDecl.Let(_, bindings, _) ->
+ for b in bindings do
yield ctx@name(b), b
- | Ast.SynModuleDecl.Types(defs, _) ->
- for d in defs do
- match d with
- | Ast.TypeDefn(Ast.ComponentInfo(_, _, _, ids, _, _, _, _), _, members, _) ->
+ | SyntaxTree.SynModuleDecl.Types(defs, _) ->
+ for d in defs do
+ match d with
+ | SyntaxTree.TypeDefn(SyntaxTree.ComponentInfo(_, _, _, ids, _, _, _, _), _, members, _) ->
let ctx = ctx@[for i in ids do yield i.idText]
- for m in members do
- match m with
- | Ast.SynMemberDefn.Member(b, _) ->
+ for m in members do
+ match m with
+ | SyntaxTree.SynMemberDefn.Member(b, _) ->
yield ctx@name(b), b
- | Ast.SynMemberDefn.LetBindings(bindings, _, _, _) ->
- for b in bindings do
+ | SyntaxTree.SynMemberDefn.LetBindings(bindings, _, _, _) ->
+ for b in bindings do
yield ctx@name(b), b
| _ -> ()
| _ -> ()
}
- let modules =
- match parse.ParseTree with
- | Some(Ast.ParsedInput.ImplFile(Ast.ParsedImplFileInput(_, _, _, _, _, modules, _))) -> modules
+ let modules =
+ match parse.ParseTree with
+ | Some(SyntaxTree.ParsedInput.ImplFile(SyntaxTree.ParsedImplFileInput(_, _, _, _, _, modules, _))) -> modules
| _ -> []
- [ for m in modules do
- let ids, decls = match m with Ast.SynModuleOrNamespace(ids, _, _, decls, _, _, _, _) -> ids, decls
+ [ for m in modules do
+ let ids, decls = match m with SyntaxTree.SynModuleOrNamespace(ids, _, _, decls, _, _, _, _) -> ids, decls
let name = [for i in ids do yield i.idText]
- for d in decls do
- for ctx, b in bindings(name, d) do
- if isTestFunction(b) then
+ for d in decls do
+ for ctx, b in bindings(name, d) do
+ if isTestFunction(b) then
yield ctx, b ]
-type Server(client: ILanguageClient) =
+type Server(client: ILanguageClient) =
let docs = DocumentStore()
let checker = FSharpChecker.Create()
let projects = ProjectManager(checker)
/// Get a file from docs, or read it from disk
- let getOrRead(file: FileInfo): string option =
- match docs.GetText(file) with
+ let getOrRead(file: FileInfo): string option =
+ match docs.GetText(file) with
| Some(text) -> Some(text)
| None when file.Exists -> Some(File.ReadAllText(file.FullName))
| None -> None
/// Read a specific line from a file
- let lineContent(file: FileInfo, targetLine: int): string =
+ let lineContent(file: FileInfo, targetLine: int): string =
let text = getOrRead(file) |> Option.defaultValue ""
let reader = new StringReader(text)
let mutable line = 0
- while line < targetLine && reader.Peek() <> -1 do
+ while line < targetLine && reader.Peek() <> -1 do
reader.ReadLine() |> ignore
line <- line + 1
- if reader.Peek() = -1 then
+ if reader.Peek() = -1 then
dprintfn "Reached EOF before line %d in file %O" targetLine file.Name
- ""
- else
+ ""
+ else
reader.ReadLine()
- /// Parse a file
- let parseFile(file: FileInfo): Async> =
+ /// Parse a file
+ let parseFile(file: FileInfo): Async> =
async {
- match projects.FindProjectOptions(file), getOrRead(file) with
+ match projects.FindProjectOptions(file), getOrRead(file) with
| Error(_), _ ->
return Error(sprintf "Can't find symbols in %s because of error in project options" file.Name)
- | _, None ->
+ | _, None ->
return Error(sprintf "%s was closed" file.FullName)
- | Ok(projectOptions), Some(sourceText) ->
- match checker.TryGetRecentCheckResultsForFile(file.FullName, projectOptions) with
- | Some(parse, _, _) ->
+ | Ok(projectOptions), Some(sourceText) ->
+ match checker.TryGetRecentCheckResultsForFile(file.FullName, projectOptions) with
+ | Some(parse, _, _) ->
return Ok parse
| None ->
try
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions)
let! parse = checker.ParseFile(file.FullName, SourceText.ofString(sourceText), parsingOptions)
return Ok(parse)
- with e ->
+ with e ->
return Error(e.Message)
}
@@ -239,63 +237,63 @@ type Server(client: ILanguageClient) =
/// `allowCached` is a bad idea if upstream dependencies of `file` have been edited
/// If `allowStale`, will re-use stale results where the source doesn't match
/// `allowStale` only really works for simple identifier expressions like x.y
- let checkOpenFile(file: FileInfo, allowCached: bool, allowStale: bool): Async> =
+ let checkOpenFile(file: FileInfo, allowCached: bool, allowStale: bool): Async> =
async {
- match projects.FindProjectOptions(file), docs.Get(file) with
- | _, None ->
+ match projects.FindProjectOptions(file), docs.Get(file) with
+ | _, None ->
// If file doesn't exist, there's nothing to report
dprintfn "%s was closed" file.FullName
return Error []
- | Error(errs), _ ->
+ | Error(errs), _ ->
return Error(errs)
- | Ok(projectOptions), Some(sourceText, sourceVersion) ->
+ | Ok(projectOptions), Some(sourceText, sourceVersion) ->
let recompile = async {
let timeCheck = Stopwatch.StartNew()
let! force = checker.ParseAndCheckFileInProject(file.FullName, sourceVersion, SourceText.ofString(sourceText), projectOptions)
dprintfn "Checked %s in %dms" file.Name timeCheck.ElapsedMilliseconds
- match force with
+ match force with
| parseResult, FSharpCheckFileAnswer.Aborted -> return Error(asDiagnostics parseResult.Errors)
| parseResult, FSharpCheckFileAnswer.Succeeded(checkResult) -> return Ok(parseResult, checkResult)
}
- match checker.TryGetRecentCheckResultsForFile(file.FullName, projectOptions) with
- | Some(parseResult, checkResult, version) ->
- if allowCached && version = sourceVersion then
+ match checker.TryGetRecentCheckResultsForFile(file.FullName, projectOptions) with
+ | Some(parseResult, checkResult, version) ->
+ if allowCached && version = sourceVersion then
return Ok(parseResult, checkResult)
- else if allowCached && allowStale then
- try
+ else if allowCached && allowStale then
+ try
dprintfn "Trying to recompile %s with timeout" file.Name
let! worker = Async.StartChild(recompile, millisecondsTimeout=200)
return! worker
with :? TimeoutException ->
dprintfn "Re-compile timed out, using stale results"
return Ok(parseResult, checkResult)
- else
+ else
return! recompile
- | _ ->
+ | _ ->
return! recompile
}
/// When did we last check each file on disk?
let lastCheckedOnDisk = new System.Collections.Generic.Dictionary()
// TODO there might be a thread safety issue here---is this getting called from a separate thread?
- do checker.BeforeBackgroundFileCheck.Add(fun(fileName, _) ->
+ do checker.BeforeBackgroundFileCheck.Add(fun(fileName, _) ->
let file = FileInfo(fileName)
lastCheckedOnDisk.[file.FullName] <- file.LastWriteTime)
/// Figure out what files will be implicitly recompiled if we recompile `goal`
- let needsRecompile(goal: FileInfo): List =
- match projects.FindProjectOptions(goal) with
- | Ok(projectOptions) ->
+ let needsRecompile(goal: FileInfo): List =
+ match projects.FindProjectOptions(goal) with
+ | Ok(projectOptions) ->
// Find all projects that goal depends on, including its own project
let projects = projects.TransitiveDeps(FileInfo(projectOptions.ProjectFileName))
// Take all files that lead up to goal, not including itself
- let files = [for p in projects do
+ let files = [for p in projects do
let sourceFiles = Array.map FileInfo p.SourceFiles
let notGoal(f: FileInfo) = f.FullName <> goal.FullName
yield! Array.takeWhile notGoal sourceFiles]
// Skip files until we find a modified file, then take all remaining files
- let notModified(f: FileInfo) =
- match lastCheckedOnDisk.TryGetValue(f.FullName) with
+ let notModified(f: FileInfo) =
+ match lastCheckedOnDisk.TryGetValue(f.FullName) with
| true, lastChecked -> f.LastWriteTime <= lastChecked
| _, _ -> false
let modified = List.skipWhile notModified files
@@ -303,15 +301,15 @@ type Server(client: ILanguageClient) =
modified@[goal]
| Error(_) -> []
/// Send diagnostics to the client
- let publishErrors(file: FileInfo, errors: Diagnostic list) =
+ let publishErrors(file: FileInfo, errors: Diagnostic list) =
client.PublishDiagnostics({uri=Uri("file://" + file.FullName); diagnostics=errors})
/// Check a file
- let getErrors(file: FileInfo, check: Result): Async =
+ let getErrors(file: FileInfo, check: Result): Async =
async {
match check with
| Error(errors) ->
return errors
- | Ok(parseResult, checkResult) ->
+ | Ok(parseResult, checkResult) ->
let parseErrors = asDiagnostics(parseResult.Errors)
let typeErrors = asDiagnostics(checkResult.Errors)
// This is just too slow. Also, it's sometimes wrong.
@@ -322,15 +320,15 @@ type Server(client: ILanguageClient) =
// dprintfn "Found %d unused opens in %dms" unusedOpenErrors.Length timeUnusedOpens.ElapsedMilliseconds
// Find unused declarations
let timeUnusedDeclarations = Stopwatch.StartNew()
- let! uses = checkResult.GetAllUsesOfAllSymbolsInFile()
- let unusedDeclarationRanges = UnusedDeclarations.getUnusedDeclarationRanges(uses, file.Name.EndsWith(".fsx"))
+ let uses = checkResult.GetAllUsesOfAllSymbolsInFile()
+ let unusedDeclarationRanges = UnusedDeclarations.getUnusedDeclarationRanges(Seq.toArray uses, file.Name.EndsWith(".fsx"))
let unusedDeclarationErrors = [for r in unusedDeclarationRanges do yield diagnostic("Unused declaration", r, DiagnosticSeverity.Hint)]
dprintfn "Found %d unused declarations in %dms" unusedDeclarationErrors.Length timeUnusedDeclarations.ElapsedMilliseconds
// Combine
// return parseErrors@typeErrors@unusedOpenErrors@unusedDeclarationErrors
return parseErrors@typeErrors@unusedDeclarationErrors
}
- let doCheck(file: FileInfo): Async =
+ let doCheck(file: FileInfo): Async =
async {
let! check = checkOpenFile(file, true, false)
let! errors = getErrors(file, check)
@@ -339,23 +337,23 @@ type Server(client: ILanguageClient) =
/// Request that `uri` be checked when the user stops doing things for 1 second
let backgroundCheck = DebounceCheck(doCheck, 1000)
/// Find the symbol at a position
- let symbolAt(textDocument: TextDocumentIdentifier, position: Position): Async =
+ let symbolAt(textDocument: TextDocumentIdentifier, position: Position): Async =
async {
let file = FileInfo(textDocument.uri.LocalPath)
let! c = checkOpenFile(file, true, false)
let line = lineContent(file, position.line)
let maybeId = QuickParse.GetCompleteIdentifierIsland false line (position.character)
- match c, maybeId with
- | Error(errors), _ ->
+ match c, maybeId with
+ | Error(errors), _ ->
dprintfn "Check failed, ignored %d errors" (List.length errors)
return None
- | _, None ->
- dprintfn "No identifier at %d in line '%s'" position.character line
+ | _, None ->
+ dprintfn "No identifier at %d in line '%s'" position.character line
return None
- | Ok(_, checkResult), Some(id, endOfIdentifier, _) ->
+ | Ok(_, checkResult), Some(id, endOfIdentifier, _) ->
dprintfn "Looking at symbol %s" id
let names = List.ofArray(id.Split('.'))
- let! maybeSymbol = checkResult.GetSymbolUseAtLocation(position.line+1, endOfIdentifier, line, names)
+ let maybeSymbol = checkResult.GetSymbolUseAtLocation(position.line+1, endOfIdentifier, line, names)
if maybeSymbol.IsNone then
dprintfn "%s in line '%s' is not a symbol use" id line
return maybeSymbol
@@ -363,117 +361,117 @@ type Server(client: ILanguageClient) =
/// Find the exact location of a symbol within a fully-qualified name.
/// For example, if we have `let b = Foo.bar`, and we want to find the symbol `bar` in the range `let b = [Foo.bar]`.
- let refineRenameRange(s: FSharpSymbol, file: FileInfo, range: Range.range): Range =
+ let refineRenameRange(s: FSharpSymbol, file: FileInfo, range: Text.range): Range =
let line = range.End.Line - 1
let startColumn = if range.Start.Line - 1 < line then 0 else range.Start.Column
let endColumn = range.End.Column
let lineText = lineContent(file, line )
let find = lineText.LastIndexOf(s.DisplayName, endColumn, endColumn - startColumn)
if find = -1 then
- dprintfn "Couldn't find '%s' in line '%s'" s.DisplayName lineText
+ dprintfn "Couldn't find '%s' in line '%s'" s.DisplayName lineText
asRange range
- else
+ else
{
start={line=line; character=find}
``end``={line=line; character=find + s.DisplayName.Length}
}
/// Rename one usage of a symbol
- let renameTo(newName: string, file: FileInfo, usages: FSharpSymbolUse seq): TextDocumentEdit =
+ let renameTo(newName: string, file: FileInfo, usages: FSharpSymbolUse seq): TextDocumentEdit =
let uri = Uri("file://" + file.FullName)
let version = docs.GetVersion(file) |> Option.defaultValue 0
let edits = [
- for u in usages do
+ for u in usages do
let range = refineRenameRange(u.Symbol, FileInfo(u.FileName), u.RangeAlternate)
yield {range=range; newText=newName} ]
{textDocument={uri=uri; version=version}; edits=edits}
let symbolPattern = Regex(@"\w+")
/// Quickly check if a file *might* contain a symbol matching query
- let maybeMatchesQuery(query: string, file: FileInfo): string option =
- match getOrRead(file) with
- | None -> None
- | Some(text) ->
+ let maybeMatchesQuery(query: string, file: FileInfo): string option =
+ match getOrRead(file) with
+ | None -> None
+ | Some(text) ->
let matches = symbolPattern.Matches(text)
let test(m: Match) = matchesQuery(query, m.Value)
- if Seq.exists test matches then
- Some(text)
- else
+ if Seq.exists test matches then
+ Some(text)
+ else
None
let exactlyMatches(findSymbol: string, file: FileInfo): string option =
- match getOrRead(file) with
- | None -> None
- | Some(text) ->
+ match getOrRead(file) with
+ | None -> None
+ | Some(text) ->
let matches = symbolPattern.Matches(text)
let test(m: Match) = m.Value = findSymbol
- if Seq.exists test matches then
- Some(text)
- else
+ if Seq.exists test matches then
+ Some(text)
+ else
None
/// Find all uses of a symbol, across all open projects
- let findAllSymbolUses(symbol: FSharpSymbol): Async> =
+ let findAllSymbolUses(symbol: FSharpSymbol): Async> =
async {
// If the symbol is private or internal, we only need to scan 1 file or project
// TODO this only detects symbols *declared* private, many symbols are implicitly private
- let isPrivate, isInternal =
- match FSharpSymbol.GetAccessibility(symbol) with
+ let isPrivate, isInternal =
+ match FSharpSymbol.GetAccessibility(symbol) with
| Some(a) when a.IsPrivate -> true, true
| Some(a) when a.IsInternal -> false, true
| _ -> false, false
// Figure out what project and file the symbol is declared in
// This might be nothing if the symbol is declared outside the workspace
- let symbolDeclarationProject, symbolDeclarationFile =
- match symbol.DeclarationLocation with
+ let symbolDeclarationProject, symbolDeclarationFile =
+ match symbol.DeclarationLocation with
| None -> None, None
- | Some(range) ->
+ | Some(range) ->
let f = FileInfo(range.FileName)
- match projects.FindProjectOptions(f) with
+ match projects.FindProjectOptions(f) with
| Error(_) -> None, Some(f)
| Ok(projectOptions) -> Some(projectOptions), Some(f)
- if isPrivate then
+ if isPrivate then
dprintfn "Symbol %s is private so we will only check declaration file %A" symbol.FullName symbolDeclarationFile
- elif isInternal then
+ elif isInternal then
dprintfn "Symbol %s is internal so we will onlcy check declaration project %A" symbol.FullName symbolDeclarationProject
// Is fileName the same file symbol was declared in?
let isSymbolFile(fileName: string) =
match symbolDeclarationFile with None -> false | Some(f) -> f.FullName = fileName
// Is candidate the same project that symbol was declared in?
- let isSymbolProject(candidate: FSharpProjectOptions) =
+ let isSymbolProject(candidate: FSharpProjectOptions) =
match symbolDeclarationProject with None -> false | Some(p) -> candidate.ProjectFileName = p.ProjectFileName
// Does fileName come after symbol in dependency order, meaning it can see symbol?
- let isVisibleFromFile(fromFile: FileInfo) =
- match symbolDeclarationFile with
+ let isVisibleFromFile(fromFile: FileInfo) =
+ match symbolDeclarationFile with
| Some(symbolFile) -> projects.IsVisible(symbolFile, fromFile)
| _ -> true
// Is symbol visible from file?
- let isVisibleFrom(project: FSharpProjectOptions, file: string) =
- if isPrivate then
+ let isVisibleFrom(project: FSharpProjectOptions, file: string) =
+ if isPrivate then
isSymbolFile(file)
- elif isInternal then
+ elif isInternal then
isSymbolProject(project) && isVisibleFromFile(FileInfo(file))
- else
+ else
isVisibleFromFile(FileInfo(file))
// Find all source files that can see symbol
let visible = [
- for projectOptions in projects.OpenProjects do
- for fileName in projectOptions.SourceFiles do
- if isVisibleFrom(projectOptions, fileName) then
+ for projectOptions in projects.OpenProjects do
+ for fileName in projectOptions.SourceFiles do
+ if isVisibleFrom(projectOptions, fileName) then
yield projectOptions, FileInfo(fileName)
]
let visibleNames = String.concat ", " [for _, f in visible do yield f.Name]
dprintfn "Symbol %s is visible from %s" symbol.FullName visibleNames
// Check source files for possible symbol references using string matching
- let searchFor =
+ let searchFor =
// Attributes are referenced without the `Attribute` suffix
// searchFor is just used to cut down the number of files we need to check,
// so it's OK to be a little fast-and-sloppy
- if symbol.DisplayName.EndsWith("Attribute") then
+ if symbol.DisplayName.EndsWith("Attribute") then
symbol.DisplayName.Substring(0, symbol.DisplayName.Length - "Attribute".Length)
- else
+ else
symbol.DisplayName
let candidates = [
- for projectOptions, sourceFile in visible do
- match exactlyMatches(searchFor, sourceFile) with
+ for projectOptions, sourceFile in visible do
+ match exactlyMatches(searchFor, sourceFile) with
| None -> ()
| Some(sourceText) -> yield projectOptions, sourceFile, sourceText
]
@@ -482,52 +480,52 @@ type Server(client: ILanguageClient) =
// Check each candidate file
use progress = new ProgressBar(candidates.Length, sprintf "Search %d files" candidates.Length, client)
let all = System.Collections.Generic.List()
- for projectOptions, sourceFile, sourceText in candidates do
+ for projectOptions, sourceFile, sourceText in candidates do
try
// Send a notification to the client updating the progress indicator
progress.Increment(sourceFile)
-
+
// Check file
let sourceVersion = docs.GetVersion(sourceFile) |> Option.defaultValue 0
let timeCheck = Stopwatch.StartNew()
let! _, maybeCheck = checker.ParseAndCheckFileInProject(sourceFile.FullName, sourceVersion, SourceText.ofString(sourceText), projectOptions)
dprintfn "Checked %s in %dms" sourceFile.Name timeCheck.ElapsedMilliseconds
- match maybeCheck with
+ match maybeCheck with
| FSharpCheckFileAnswer.Aborted -> dprintfn "Aborted checking %s" sourceFile.Name
- | FSharpCheckFileAnswer.Succeeded(check) ->
- let! uses = check.GetUsesOfSymbolInFile(symbol)
- for u in uses do
+ | FSharpCheckFileAnswer.Succeeded(check) ->
+ let uses = check.GetUsesOfSymbolInFile(symbol)
+ for u in uses do
all.Add(u)
- with e ->
+ with e ->
dprintfn "Error checking %s: %s" sourceFile.Name e.Message
return List.ofSeq(all)
}
/// Tell the user if we run out of memory
/// TODO add a setting to increase max memory
- let maxMemoryWarning() =
+ let maxMemoryWarning() =
let message = sprintf "Reached max memory %d MB" checker.MaxMemory
client.ShowMessage({``type``=MessageType.Warning; message=message})
let _ = checker.MaxMemoryReached.Add(maxMemoryWarning)
/// Remember the last completion list for ResolveCompletionItem
- let mutable lastCompletion: FSharpDeclarationListInfo option = None
+ let mutable lastCompletion: FSharpDeclarationListInfo option = None
/// Defer initialization operations until Initialized() is called,
/// so that the client-side code int client/extension.ts starts running immediately
let mutable deferredInitialize = async { () }
- interface ILanguageServer with
+ interface ILanguageServer with
member this.Initialize(p: InitializeParams) =
async {
- match p.rootUri with
- | Some root ->
+ match p.rootUri with
+ | Some root ->
dprintfn "Add workspace root %s" root.LocalPath
- deferredInitialize <- projects.AddWorkspaceRoot(DirectoryInfo(root.LocalPath))
+ deferredInitialize <- projects.AddWorkspaceRoot(DirectoryInfo(root.LocalPath))
| _ -> dprintfn "No root URI in initialization message %A" p
- return {
- capabilities =
- { defaultServerCapabilities with
+ return {
+ capabilities =
+ { defaultServerCapabilities with
hoverProvider = true
completionProvider = Some({resolveProvider=true; triggerCharacters=['.']})
signatureHelpProvider = Some({triggerCharacters=['('; ',']})
@@ -537,24 +535,24 @@ type Server(client: ILanguageClient) =
definitionProvider = true
referencesProvider = true
renameProvider = true
- textDocumentSync =
- { defaultTextDocumentSyncOptions with
- openClose = true
+ textDocumentSync =
+ { defaultTextDocumentSyncOptions with
+ openClose = true
save = Some({ includeText = false })
- change = TextDocumentSyncKind.Incremental
- }
+ change = TextDocumentSyncKind.Incremental
+ }
}
}
}
- member this.Initialized(): Async =
+ member this.Initialized(): Async =
deferredInitialize
- member this.Shutdown(): Async =
+ member this.Shutdown(): Async =
async { () }
member this.DidChangeConfiguration(p: DidChangeConfigurationParams): Async =
async {
dprintfn "New configuration %s" (p.ToString())
}
- member this.DidOpenTextDocument(p: DidOpenTextDocumentParams): Async =
+ member this.DidOpenTextDocument(p: DidOpenTextDocumentParams): Async =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
// Store text in docs
@@ -565,7 +563,7 @@ type Server(client: ILanguageClient) =
use increment = checker.BeforeBackgroundFileCheck.Subscribe(fun (fileName, _) -> progress.Increment(FileInfo(fileName)))
do! doCheck(file)
}
- member this.DidChangeTextDocument(p: DidChangeTextDocumentParams): Async =
+ member this.DidChangeTextDocument(p: DidChangeTextDocumentParams): Async =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
docs.Change(p)
@@ -573,53 +571,53 @@ type Server(client: ILanguageClient) =
}
member this.WillSaveTextDocument(p: WillSaveTextDocumentParams): Async = TODO()
member this.WillSaveWaitUntilTextDocument(p: WillSaveTextDocumentParams): Async = TODO()
- member this.DidSaveTextDocument(p: DidSaveTextDocumentParams): Async =
+ member this.DidSaveTextDocument(p: DidSaveTextDocumentParams): Async =
async {
let targetFile = FileInfo(p.textDocument.uri.LocalPath)
- let todo = [ for fromFile in docs.OpenFiles() do
- if projects.IsVisible(targetFile, fromFile) then
+ let todo = [ for fromFile in docs.OpenFiles() do
+ if projects.IsVisible(targetFile, fromFile) then
yield fromFile ]
use progress = new ProgressBar(todo.Length, sprintf "Check %d files" todo.Length, client, todo.Length <= 1)
- for file in todo do
+ for file in todo do
progress.Increment(file)
let! check = checkOpenFile(file, false, false)
let! errors = getErrors(file, check)
publishErrors(file, errors)
}
- member this.DidCloseTextDocument(p: DidCloseTextDocumentParams): Async =
+ member this.DidCloseTextDocument(p: DidCloseTextDocumentParams): Async =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
docs.Close(p)
// Only show errors for open files
publishErrors(file, [])
}
- member this.DidChangeWatchedFiles(p: DidChangeWatchedFilesParams): Async =
+ member this.DidChangeWatchedFiles(p: DidChangeWatchedFilesParams): Async =
async {
- for change in p.changes do
+ for change in p.changes do
let file = FileInfo(change.uri.LocalPath)
dprintfn "Watched file %s %O" file.FullName change.``type``
- if file.Name.EndsWith(".fsproj") || file.Name.EndsWith(".fsx") then
- match change.``type`` with
+ if file.Name.EndsWith(".fsproj") || file.Name.EndsWith(".fsx") then
+ match change.``type`` with
| FileChangeType.Created ->
projects.NewProjectFile(file)
- | FileChangeType.Changed ->
+ | FileChangeType.Changed ->
projects.UpdateProjectFile(file)
| FileChangeType.Deleted ->
projects.DeleteProjectFile(file)
elif file.Name.EndsWith(".sln") then
- match change.``type`` with
+ match change.``type`` with
| FileChangeType.Created ->
projects.UpdateSlnFile(file)
- | FileChangeType.Changed ->
+ | FileChangeType.Changed ->
projects.UpdateSlnFile(file)
| FileChangeType.Deleted ->
projects.DeleteSlnFile(file)
- elif file.Name = "project.assets.json" then
+ elif file.Name = "project.assets.json" then
projects.UpdateAssetsJson(file)
// Re-check all open files
- // In theory we could optimize this by only re-checking descendents of changed projects,
+ // In theory we could optimize this by only re-checking descendents of changed projects,
// but in practice that will make little difference
- for f in docs.OpenFiles() do
+ for f in docs.OpenFiles() do
backgroundCheck.CheckLater(f)
}
member this.Completion(p: TextDocumentPositionParams): Async =
@@ -635,71 +633,71 @@ type Server(client: ILanguageClient) =
// TODO when this is the only edited line, and the line looks like x.y.z.?, then stale completions are quite accurate
let! c = checkOpenFile(file, true, not(noPartialName))
dprintfn "Finished typecheck, looking for completions..."
- match c with
- | Error errors ->
+ match c with
+ | Error errors ->
dprintfn "Check failed, ignored %d errors" (List.length(errors))
return None
- | Ok(parseResult, checkResult) ->
- let! declarations = checkResult.GetDeclarationListInfo(Some parseResult, p.position.line+1, line, partialName)
- lastCompletion <- Some declarations
+ | Ok(parseResult, checkResult) ->
+ let declarations = checkResult.GetDeclarationListInfo(Some parseResult, p.position.line+1, line, partialName)
+ lastCompletion <- Some declarations
dprintfn "Found %d completions" declarations.Items.Length
return Some(asCompletionList(declarations))
}
- member this.Hover(p: TextDocumentPositionParams): Async =
+ member this.Hover(p: TextDocumentPositionParams): Async =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
let! c = checkOpenFile(file, true, false)
let line = lineContent(file, p.position.line)
let maybeId = QuickParse.GetCompleteIdentifierIsland false line (p.position.character)
- match c, maybeId with
- | Error(errors), _ ->
+ match c, maybeId with
+ | Error(errors), _ ->
dprintfn "Check failed, ignored %d errors" (List.length(errors))
return None
- | _, None ->
+ | _, None ->
dprintfn "No identifier at %s(%d, %d)" file.FullName p.position.line p.position.character
return None
- | Ok(parseResult, checkResult), Some(id, _, _) ->
+ | Ok(parseResult, checkResult), Some(id, _, _) ->
dprintfn "Hover over %s" id
let ids = List.ofArray(id.Split('.'))
- let! tips = checkResult.GetToolTipText(p.position.line+1, p.position.character+1, line, ids, FSharpTokenTag.Identifier)
+ let tips = checkResult.GetToolTipText(p.position.line+1, p.position.character+1, line, ids, FSharpTokenTag.Identifier)
return Some(asHover(tips))
}
// Add documentation to a completion item
// Generating documentation is an expensive step, so we want to defer it until the user is actually looking at it
- member this.ResolveCompletionItem(p: CompletionItem): Async =
+ member this.ResolveCompletionItem(p: CompletionItem): Async =
async {
let mutable result = p
- if lastCompletion.IsSome then
- for candidate in lastCompletion.Value.Items do
- if candidate.FullName = p.data?FullName.AsString() then
+ if lastCompletion.IsSome then
+ for candidate in lastCompletion.Value.Items do
+ if candidate.FullName = p.data?FullName.AsString() then
dprintfn "Resolve description for %s" candidate.FullName
let! resolved = TipFormatter.resolveDocs(p, candidate)
result <- resolved
return result
}
- member this.SignatureHelp(p: TextDocumentPositionParams): Async =
+ member this.SignatureHelp(p: TextDocumentPositionParams): Async =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
let! c = checkOpenFile(file, true, true)
- match c with
- | Error errors ->
+ match c with
+ | Error errors ->
dprintfn "Check failed, ignored %d errors" (List.length(errors))
return None
- | Ok(parseResult, checkResult) ->
+ | Ok(parseResult, checkResult) ->
let line = lineContent(file, p.position.line)
- match findMethodCallBeforeCursor(line, p.position.character) with
- | None ->
- dprintfn "No method call in line %s" line
+ match findMethodCallBeforeCursor(line, p.position.character) with
+ | None ->
+ dprintfn "No method call in line %s" line
return None
- | Some endOfMethodName ->
- match QuickParse.GetCompleteIdentifierIsland false line (endOfMethodName - 1) with
- | None ->
+ | Some endOfMethodName ->
+ match QuickParse.GetCompleteIdentifierIsland false line (endOfMethodName - 1) with
+ | None ->
dprintfn "No identifier before column %d in %s" (endOfMethodName - 1) line
- return None
+ return None
| Some(id, _, _) ->
dprintfn "Looking for overloads of %s" id
let names = List.ofArray(id.Split('.'))
- let! overloads = checkResult.GetMethods(p.position.line+1, endOfMethodName, line, Some names)
+ let overloads = checkResult.GetMethods(p.position.line+1, endOfMethodName, line, Some names)
let signature(i: FSharpMethodGroupItem) = asSignatureInformation(overloads.MethodName, i)
let sigs = Array.map signature overloads.Methods |> List.ofArray
let activeParameter = countCommas(line, endOfMethodName, p.position.character)
@@ -707,19 +705,19 @@ type Server(client: ILanguageClient) =
dprintfn "Found %d overloads" overloads.Methods.Length
return Some({signatures=sigs; activeSignature=activeDeclaration; activeParameter=Some activeParameter})
}
- member this.GotoDefinition(p: TextDocumentPositionParams): Async =
+ member this.GotoDefinition(p: TextDocumentPositionParams): Async =
async {
let! maybeSymbol = symbolAt(p.textDocument, p.position)
- match maybeSymbol with
+ match maybeSymbol with
| None -> return []
| Some s -> return declarationLocation s.Symbol |> Option.toList
}
- member this.FindReferences(p: ReferenceParams): Async =
+ member this.FindReferences(p: ReferenceParams): Async =
async {
let! maybeSymbol = symbolAt(p.textDocument, p.position)
- match maybeSymbol with
- | None -> return []
- | Some s ->
+ match maybeSymbol with
+ | None -> return []
+ | Some s ->
let! uses = findAllSymbolUses(s.Symbol)
return List.map useLocation uses
}
@@ -728,84 +726,84 @@ type Server(client: ILanguageClient) =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
let! maybeParse = parseFile(file)
- match maybeParse with
- | Error e ->
- dprintfn "%s" e
+ match maybeParse with
+ | Error e ->
+ dprintfn "%s" e
return []
| Ok parse ->
let flat = findDeclarations(parse)
return List.map asSymbolInformation flat
}
- member this.WorkspaceSymbols(p: WorkspaceSymbolParams): Async =
+ member this.WorkspaceSymbols(p: WorkspaceSymbolParams): Async =
async {
dprintfn "Looking for symbols matching `%s`" p.query
// Read open projects until we find at least 50 symbols that match query
let all = System.Collections.Generic.List()
// TODO instead of checking open projects, check all .fs files, using default parsing options
- for projectOptions in projects.OpenProjects do
+ for projectOptions in projects.OpenProjects do
dprintfn "...check project %s" projectOptions.ProjectFileName
- for sourceFileName in projectOptions.SourceFiles do
+ for sourceFileName in projectOptions.SourceFiles do
let sourceFile = FileInfo(sourceFileName)
- if all.Count < 50 then
+ if all.Count < 50 then
dprintfn "...scan %s" sourceFile.Name
- match maybeMatchesQuery(p.query, sourceFile) with
- | None -> ()
+ match maybeMatchesQuery(p.query, sourceFile) with
+ | None -> ()
| Some(sourceText) ->
try
dprintfn "...parse %s" sourceFile.Name
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions)
let! parse = checker.ParseFile(sourceFile.FullName, SourceText.ofString(sourceText), parsingOptions)
- for declaration, container in findDeclarations(parse) do
- if matchesQuery(p.query, declaration.Name) then
+ for declaration, container in findDeclarations(parse) do
+ if matchesQuery(p.query, declaration.Name) then
all.Add(asSymbolInformation(declaration, container))
- with e ->
+ with e ->
dprintfn "Error parsing %s: %s" sourceFile.Name e.Message
return List.ofSeq(all)
}
member this.CodeActions(p: CodeActionParams): Async = TODO()
- member this.CodeLens(p: CodeLensParams): Async> =
+ member this.CodeLens(p: CodeLensParams): Async> =
async {
let file = FileInfo(p.textDocument.uri.LocalPath)
- match projects.FindProjectOptions(file), getOrRead(file) with
- | Ok(projectOptions), Some(sourceText) ->
+ match projects.FindProjectOptions(file), getOrRead(file) with
+ | Ok(projectOptions), Some(sourceText) ->
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions)
let! parse = checker.ParseFile(file.FullName, SourceText.ofString(sourceText), parsingOptions)
- if file.Name.EndsWith(".fs") then
+ if file.Name.EndsWith(".fs") then
let fns = testFunctions(parse)
let fsproj = FileInfo(projectOptions.ProjectFileName)
- return [ for id, bindings in fns do
+ return [ for id, bindings in fns do
yield asRunTest(fsproj, id, bindings)
yield asDebugTest(fsproj, id, bindings) ]
- else if file.Name.EndsWith(".fsi") then
- return
- [ for name, range in findSignatureDeclarations(parse) do
+ else if file.Name.EndsWith(".fsi") then
+ return
+ [ for name, range in findSignatureDeclarations(parse) do
yield asGoToImplementation(name, file, range) ]
- else
+ else
dprintfn "Don't know how to compute code lenses on extension %s" file.Extension
return []
- | Error(e), _ ->
+ | Error(e), _ ->
dprintfn "Failed to create code lens because project options failed to load: %A" e
return []
- | _, None ->
+ | _, None ->
dprintfn "Failed to create code lens because file %s does not exist" file.FullName
return []
}
- member this.ResolveCodeLens(p: CodeLens): Async =
+ member this.ResolveCodeLens(p: CodeLens): Async =
async {
- if p.data <> JsonValue.Null then
+ if p.data <> JsonValue.Null then
dprintfn "Resolving %A" p.data
let fsi, name = goToImplementationData(p)
- if not(fsi.Extension = ".fsi") then
+ if not(fsi.Extension = ".fsi") then
raise(Exception(sprintf "Signature file %s should end with .fsi" fsi.Name))
let file = FileInfo(fsi.FullName.Substring(0, fsi.FullName.Length - 1))
- match projects.FindProjectOptions(file), getOrRead(file) with
- | Ok(projectOptions), Some(sourceText) ->
+ match projects.FindProjectOptions(file), getOrRead(file) with
+ | Ok(projectOptions), Some(sourceText) ->
let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions(projectOptions)
let! parse = checker.ParseFile(file.FullName, SourceText.ofString(sourceText), parsingOptions)
- match findSignatureImplementation(parse, name) with
- | [range] ->
+ match findSignatureImplementation(parse, name) with
+ | [range] ->
return resolveGoToImplementation(p, file, range)
- | [] ->
+ | [] ->
dprintfn "Signature %A has no implementation in %s" name file.Name
return resolveMissingGoToImplementation(p, fsi)
| many ->
@@ -814,10 +812,10 @@ type Server(client: ILanguageClient) =
// This is wrong but still useful
let range = many.Head
return resolveGoToImplementation(p, file, range)
- | Error(e), _ ->
+ | Error(e), _ ->
dprintfn "Failed to resolve code lens because project options failed to load: %A" e
return p
- | _, None ->
+ | _, None ->
dprintfn "Failed to resolve code lens because file %s does not exist" file.FullName
return p
else return p
@@ -830,9 +828,9 @@ type Server(client: ILanguageClient) =
member this.Rename(p: RenameParams): Async =
async {
let! maybeSymbol = symbolAt(p.textDocument, p.position)
- match maybeSymbol with
+ match maybeSymbol with
| None -> return {documentChanges=[]}
- | Some s ->
+ | Some s ->
let! uses = findAllSymbolUses(s.Symbol)
let byFile = List.groupBy (fun (usage:FSharpSymbolUse) -> usage.FileName) uses
let fileNames = List.map fst byFile
@@ -841,9 +839,9 @@ type Server(client: ILanguageClient) =
return {documentChanges=List.ofSeq(renames)}
}
member this.ExecuteCommand(p: ExecuteCommandParams): Async = TODO()
- member this.DidChangeWorkspaceFolders(p: DidChangeWorkspaceFoldersParams): Async =
+ member this.DidChangeWorkspaceFolders(p: DidChangeWorkspaceFoldersParams): Async =
async {
- for root in p.event.added do
+ for root in p.event.added do
let file = FileInfo(root.uri.LocalPath)
do! projects.AddWorkspaceRoot(file.Directory)
// TODO removed
@@ -855,9 +853,9 @@ let main(argv: array): int =
let write = new BinaryWriter(Console.OpenStandardOutput())
let serverFactory(client) = Server(client) :> ILanguageServer
dprintfn "Listening on stdin"
- try
+ try
LanguageServer.connect(serverFactory, read, write)
0 // return an integer exit code
- with e ->
+ with e ->
dprintfn "Exception in language server %O" e
1
diff --git a/src/FSharpLanguageServer/ProjectManager.fs b/src/FSharpLanguageServer/ProjectManager.fs
index 11106ed..3f0d3ae 100644
--- a/src/FSharpLanguageServer/ProjectManager.fs
+++ b/src/FSharpLanguageServer/ProjectManager.fs
@@ -1,4 +1,4 @@
-namespace FSharpLanguageServer
+namespace FSharpLanguageServer
open LSP.Log
open System
@@ -18,36 +18,36 @@ type private ResolvedProject = {
sources: FileInfo list
options: FSharpProjectOptions
target: FileInfo
- errors: Diagnostic list
+ errors: Diagnostic list
}
type private LazyProject = {
- file: FileInfo
+ file: FileInfo
resolved: Lazy
}
-type private ProjectCache() =
+type private ProjectCache() =
let knownProjects = new Dictionary()
- member this.Invalidate(fsprojOrFsx: FileInfo) =
+ member this.Invalidate(fsprojOrFsx: FileInfo) =
knownProjects.Remove(fsprojOrFsx.FullName) |> ignore
- member this.Get(fsprojOrFsx: FileInfo, analyzeLater: FileInfo -> LazyProject): LazyProject =
- if not(knownProjects.ContainsKey(fsprojOrFsx.FullName)) then
+ member this.Get(fsprojOrFsx: FileInfo, analyzeLater: FileInfo -> LazyProject): LazyProject =
+ if not(knownProjects.ContainsKey(fsprojOrFsx.FullName)) then
knownProjects.Add(fsprojOrFsx.FullName, analyzeLater(fsprojOrFsx))
knownProjects.[fsprojOrFsx.FullName]
/// Maintains caches of parsed versions of .fsprojOrFsx files
-type ProjectManager(checker: FSharpChecker) =
+type ProjectManager(checker: FSharpChecker) =
/// Remember what .fsproj files are referenced by .sln files
/// Keys are full paths to .sln files
/// Values are lists of .fsproj files referenced by the .sln file
let knownSolutions = new Dictionary>()
- /// Remember what .fsproj and .fsx files are present
+ /// Remember what .fsproj and .fsx files are present
let knownProjects = new HashSet()
//// Cache expensive analyze operations
let cache = ProjectCache()
- let printOptions(options: FSharpProjectOptions) =
+ let printOptions(options: FSharpProjectOptions) =
// This is long but it's useful
dprintfn "%s: " options.ProjectFileName
dprintfn " ProjectFileName: %A" options.ProjectFileName
@@ -65,21 +65,21 @@ type ProjectManager(checker: FSharpChecker) =
/// When was this .fsx, .fsproj or corresponding project.assets.json file modified?
// TODO use checksum instead of time
- let lastModified(fsprojOrFsx: FileInfo) =
+ let lastModified(fsprojOrFsx: FileInfo) =
let assets = FileInfo(Path.Combine [| fsprojOrFsx.Directory.FullName; "obj"; "project.assets.json" |])
- if assets.Exists then
+ if assets.Exists then
max fsprojOrFsx.LastWriteTime assets.LastWriteTime
- else
+ else
fsprojOrFsx.LastWriteTime
/// Find any .fsproj files associated with a project.assets.json
- let projectFileForAssets(assetsJson: FileInfo) =
+ let projectFileForAssets(assetsJson: FileInfo) =
let dir = assetsJson.Directory.Parent
dir.GetFiles("*.fsproj")
/// Find base dlls
/// Workaround of https://github.com/fsharp/FSharp.Compiler.Service/issues/847
- let dotNetFramework =
+ let dotNetFramework =
let dir = Path.GetDirectoryName(typeof.Assembly.Location)
let relative = [
"FSharp.Core.dll"
@@ -226,30 +226,30 @@ type ProjectManager(checker: FSharpChecker) =
"mscorlib.dll"
"netstandard.dll"
]
- [ for d in relative do
+ [ for d in relative do
let f = FileInfo(Path.Combine(dir, d))
- if f.Exists then
+ if f.Exists then
yield f
- else
- dprintfn "Couldn't find %s in %s" d dir
+ else
+ dprintfn "Couldn't find %s in %s" d dir
]
/// Analyze a .fsx or .fsproj file
- let rec analyzeLater(fsprojOrFsx: FileInfo): LazyProject =
+ let rec analyzeLater(fsprojOrFsx: FileInfo): LazyProject =
/// Analyze a script file
- let analyzeFsx(fsx: FileInfo) =
+ let analyzeFsx(fsx: FileInfo) =
dprintfn "Creating project options for script %s" fsx.Name
let source = SourceText.ofString(File.ReadAllText(fsx.FullName))
let inferred, errors = checker.GetProjectOptionsFromScript(fsx.FullName, source, loadedTimeStamp=fsx.LastWriteTime, assumeDotNetFramework=false) |> Async.RunSynchronously
let combinedOtherOptions = [|
- for p in dotNetFramework do
+ for p in dotNetFramework do
yield "-r:" + p.FullName
- for o in inferred.OtherOptions do
+ for o in inferred.OtherOptions do
// If a dll is included by default, skip it
let matchesName(f: FileInfo) = o.EndsWith(f.Name)
let alreadyIncluded = List.exists matchesName dotNetFramework
if not(alreadyIncluded) then
- yield o
+ yield o
|]
let options = {inferred with OtherOptions = combinedOtherOptions}
printOptions(options)
@@ -260,85 +260,85 @@ type ProjectManager(checker: FSharpChecker) =
errors=Conversions.asDiagnostics(errors)
}
/// Analyze a project
- let analyzeFsproj(fsproj: FileInfo) =
+ let analyzeFsproj(fsproj: FileInfo) =
dprintfn "Analyzing %s" fsproj.Name
let cracked = ProjectCracker.crack(fsproj)
// Convert to FSharpProjectOptions
let options = {
- ExtraProjectInfo = None
- IsIncompleteTypeCheckEnvironment = false
+ ExtraProjectInfo = None
+ IsIncompleteTypeCheckEnvironment = false
LoadTime = lastModified(fsproj)
OriginalLoadReferences = []
- OtherOptions =
+ OtherOptions =
[|
// Dotnet framework should be specified explicitly
yield "--noframework"
// Reference output of other projects
- for r in cracked.projectReferences do
+ for r in cracked.projectReferences do
let options = cache.Get(r, analyzeLater)
yield "-r:" + options.resolved.Value.target.FullName
// Reference target .dll for .csproj proejcts
- for r in cracked.otherProjectReferences do
+ for r in cracked.otherProjectReferences do
yield "-r:" + r.FullName
// Reference packages
- for r in cracked.packageReferences do
+ for r in cracked.packageReferences do
yield "-r:" + r.FullName
// Direct dll references
- for r in cracked.directReferences do
+ for r in cracked.directReferences do
yield "-r:" + r.FullName
|]
- ProjectFileName = fsproj.FullName
+ ProjectFileName = fsproj.FullName
ProjectId = None // This is apparently relevant to multi-targeting builds https://github.com/Microsoft/visualfsharp/pull/4918
- ReferencedProjects =
+ ReferencedProjects =
[|
- for r in cracked.projectReferences do
+ for r in cracked.projectReferences do
let options = cache.Get(r, analyzeLater)
yield options.resolved.Value.target.FullName, options.resolved.Value.options
|]
- SourceFiles =
+ SourceFiles =
[|
- for f in cracked.sources do
+ for f in cracked.sources do
yield f.FullName
|]
- Stamp = None
- UnresolvedReferences = None
+ Stamp = None
+ UnresolvedReferences = None
UseScriptResolutionRules = false
}
// Log what we inferred
printOptions(options)
{
sources=cracked.sources
- options=options
+ options=options
target=cracked.target
errors=match cracked.error with None -> [] | Some(e) -> [Conversions.errorAtTop(e)]
}
// Direct to analyzeFsx or analyzeFsproj, depending on type
- if fsprojOrFsx.Name.EndsWith(".fsx") then
+ if fsprojOrFsx.Name.EndsWith(".fsx") then
{file=fsprojOrFsx; resolved=lazy(analyzeFsx(fsprojOrFsx))}
- elif fsprojOrFsx.Name.EndsWith(".fsproj") then
+ elif fsprojOrFsx.Name.EndsWith(".fsproj") then
{file=fsprojOrFsx; resolved=lazy(analyzeFsproj(fsprojOrFsx))}
- else
+ else
raise(Exception(sprintf "Don't know how to analyze project %s" fsprojOrFsx.Name))
/// Invalidate all descendents of a modified .fsproj or .fsx file
- let invalidateDescendents(fsprojOrFsx: FileInfo) =
+ let invalidateDescendents(fsprojOrFsx: FileInfo) =
cache.Invalidate(fsprojOrFsx)
- for fileName in knownProjects do
+ for fileName in knownProjects do
let file = FileInfo(fileName)
let project = cache.Get(file, analyzeLater)
- if project.resolved.IsValueCreated then
- for _, ancestor in project.resolved.Value.options.ReferencedProjects do
- if ancestor.ProjectFileName = fsprojOrFsx.FullName then
+ if project.resolved.IsValueCreated then
+ for _, ancestor in project.resolved.Value.options.ReferencedProjects do
+ if ancestor.ProjectFileName = fsprojOrFsx.FullName then
dprintfn "%s has been invalidated by changes to %s" ancestor.ProjectFileName fsprojOrFsx.Name
cache.Invalidate(FileInfo(ancestor.ProjectFileName))
/// All transitive deps of anproject, including itself
- let transitiveDeps(fsprojOrFsx: FileInfo) =
+ let transitiveDeps(fsprojOrFsx: FileInfo) =
let touched = new HashSet()
let result = new List()
- let rec walk(options: FSharpProjectOptions) =
- if touched.Add(options.ProjectFileName) then
- for _, parent in options.ReferencedProjects do
+ let rec walk(options: FSharpProjectOptions) =
+ if touched.Add(options.ProjectFileName) then
+ for _, parent in options.ReferencedProjects do
walk(parent)
result.Add(options)
let root = cache.Get(fsprojOrFsx, analyzeLater)
@@ -348,17 +348,19 @@ type ProjectManager(checker: FSharpChecker) =
/// Find all .fsproj files referenced by a .sln file
let slnProjectReferences (sln: FileInfo): list =
// From https://github.com/OmniSharp/omnisharp-roslyn/blob/master/src/OmniSharp.MSBuild/SolutionParsing/ProjectBlock.cs
- let projectHeader = Regex(
- "^" // Beginning of line
- + "Project\\(\"(?.*)\"\\)"
- + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace
- + "\"(?.*)\""
- + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
- + "\"(?.*)\""
- + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
- + "\"(?.*)\""
- + "$", // End-of-line
- RegexOptions.Compiled)
+ let projectHeader =
+ Regex(
+ "^" // Beginning of line
+ + "Project\\(\"(?.*)\"\\)"
+ + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace
+ + "\"(?.*)\""
+ + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
+ + "\"(?.*)\""
+ + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace
+ + "\"(?.*)\""
+ + "$", // End-of-line
+ RegexOptions.Compiled
+ )
[ for line in File.ReadLines sln.FullName do
let m = projectHeader.Match(line)
if m.Success then
@@ -367,35 +369,35 @@ type ProjectManager(checker: FSharpChecker) =
let path = Path.Combine(sln.Directory.FullName, relativePath)
let normalize = Path.GetFullPath(path)
yield FileInfo(normalize) ]
- member this.AddWorkspaceRoot(root: DirectoryInfo): Async =
+ member this.AddWorkspaceRoot(root: DirectoryInfo): Async =
async {
- for f in root.EnumerateFiles("*.*", SearchOption.AllDirectories) do
+ for f in root.EnumerateFiles("*.*", SearchOption.AllDirectories) do
if f.Name.EndsWith(".fsx") || f.Name.EndsWith(".fsproj") then
knownProjects.Add(f.FullName) |> ignore
else if f.Name.EndsWith(".sln") then
knownSolutions.[f.FullName] <- slnProjectReferences(f)
}
- member this.DeleteProjectFile(fsprojOrFsx: FileInfo) =
+ member this.DeleteProjectFile(fsprojOrFsx: FileInfo) =
knownProjects.Remove(fsprojOrFsx.FullName) |> ignore
cache.Invalidate(fsprojOrFsx) |> ignore
invalidateDescendents(fsprojOrFsx)
- member this.NewProjectFile(fsprojOrFsx: FileInfo) =
+ member this.NewProjectFile(fsprojOrFsx: FileInfo) =
knownProjects.Add(fsprojOrFsx.FullName) |> ignore
invalidateDescendents(fsprojOrFsx)
- member this.UpdateProjectFile(fsprojOrFsx: FileInfo) =
+ member this.UpdateProjectFile(fsprojOrFsx: FileInfo) =
invalidateDescendents(fsprojOrFsx)
- member this.DeleteSlnFile(sln: FileInfo) =
+ member this.DeleteSlnFile(sln: FileInfo) =
knownSolutions.Remove(sln.FullName) |> ignore
- member this.UpdateSlnFile(sln: FileInfo) =
+ member this.UpdateSlnFile(sln: FileInfo) =
knownSolutions.[sln.FullName] <- slnProjectReferences(sln)
- member this.UpdateAssetsJson(assets: FileInfo) =
+ member this.UpdateAssetsJson(assets: FileInfo) =
for fsproj in projectFileForAssets(assets) do invalidateDescendents(fsproj)
- member this.FindProjectOptions(sourceFile: FileInfo): Result =
+ member this.FindProjectOptions(sourceFile: FileInfo): Result =
let isSourceFile(f: FileInfo) = f.FullName = sourceFile.FullName
// Does `p` contain a reference to `sourceFile`?
let isMatch(p: ResolvedProject) = List.exists isSourceFile p.sources
// Check if the text of `p` contains the name of `sourceFile` without cracking it
- let isPotentialMatch(p: LazyProject) =
+ let isPotentialMatch(p: LazyProject) =
let containsFileName(line: string) = line.Contains(sourceFile.Name)
let lines = File.ReadAllLines(p.file.FullName)
Array.exists containsFileName lines
@@ -404,50 +406,50 @@ type ProjectManager(checker: FSharpChecker) =
let alreadyCracked, notYetCracked = List.partition isCracked knownProjectsList
let isReferencedBySln(fsproj: LazyProject) =
seq {
- for KeyValue(sln, fsprojs) in knownSolutions do
- for f in fsprojs do
- if fsproj.file.FullName = f.FullName then
+ for KeyValue(sln, fsprojs) in knownSolutions do
+ for f in fsprojs do
+ if fsproj.file.FullName = f.FullName then
dprintfn "%s is referenced by %s" f.Name sln
yield sln
- } |> Seq.isEmpty |> not
+ } |> Seq.isEmpty |> not
let referencedProjects, orphanProjects = List.partition isReferencedBySln notYetCracked
let crackLazily = seq {
- // If file is an .fsx, return itself
- if sourceFile.Name.EndsWith(".fsx") then
+ // If file is an .fsx, return itself
+ if sourceFile.Name.EndsWith(".fsx") then
yield cache.Get(sourceFile, analyzeLater)
// First, look at all projects that have *already* been cracked
- for options in alreadyCracked do
- if isMatch(options.resolved.Value) then
+ for options in alreadyCracked do
+ if isMatch(options.resolved.Value) then
yield options
// If that doesn't work, check other .fsproj files
dprintfn "No cracked project references %s, looking at uncracked projects..." sourceFile.Name
// Prioritize .fsproj files that are referenced by .sln files
- for options in referencedProjects@orphanProjects do
+ for options in referencedProjects@orphanProjects do
// Only parse projects that contain the simple name of `sourceFile`
- if isPotentialMatch(options) then
+ if isPotentialMatch(options) then
dprintfn "The text of %s contains the string '%s', cracking" options.file.Name sourceFile.Name
if isMatch(options.resolved.Value) then
yield options
}
- match Seq.tryHead crackLazily with
+ match Seq.tryHead crackLazily with
| None -> Error([Conversions.errorAtTop(sprintf "No .fsproj or .fsx file references %s" sourceFile.FullName)])
- | Some(options) ->
+ | Some(options) ->
let cracked = options.resolved.Value
- if cracked.errors.IsEmpty then
+ if cracked.errors.IsEmpty then
Ok(cracked.options)
- else
+ else
Error(cracked.errors)
/// All open projects, in dependency order.
/// Ancestor projects come before projects that depend on them.
- member this.OpenProjects: FSharpProjectOptions list =
+ member this.OpenProjects: FSharpProjectOptions list =
let touched = new HashSet()
let result = new List()
- let rec walk(options: FSharpProjectOptions) =
- if touched.Add(options.ProjectFileName) then
- for _, parent in options.ReferencedProjects do
+ let rec walk(options: FSharpProjectOptions) =
+ if touched.Add(options.ProjectFileName) then
+ for _, parent in options.ReferencedProjects do
walk(parent)
result.Add(options)
- for f in knownProjects do
+ for f in knownProjects do
let project = cache.Get(FileInfo(f), analyzeLater)
if project.resolved.IsValueCreated then
walk(project.resolved.Value.options)
@@ -456,12 +458,12 @@ type ProjectManager(checker: FSharpChecker) =
member this.TransitiveDeps(projectFile: FileInfo): FSharpProjectOptions list =
transitiveDeps(projectFile)
/// Is `targetSourceFile` visible from `fromSourceFile`?
- member this.IsVisible(targetSourceFile: FileInfo, fromSourceFile: FileInfo) =
- match this.FindProjectOptions(fromSourceFile) with
- | Error(_) -> false
+ member this.IsVisible(targetSourceFile: FileInfo, fromSourceFile: FileInfo) =
+ match this.FindProjectOptions(fromSourceFile) with
+ | Error(_) -> false
| Ok(fromProjectOptions) ->
// If fromSourceFile is in the same project as targetSourceFile, check if iFrom comes after iTarget in the source file order
- if Array.contains targetSourceFile.FullName fromProjectOptions.SourceFiles then
+ if Array.contains targetSourceFile.FullName fromProjectOptions.SourceFiles then
let iTarget = Array.IndexOf(fromProjectOptions.SourceFiles, targetSourceFile.FullName)
let iFrom = Array.IndexOf(fromProjectOptions.SourceFiles, fromSourceFile.FullName)
iFrom >= iTarget
diff --git a/src/FSharpLanguageServer/SyntaxTreeOps.fs b/src/FSharpLanguageServer/SyntaxTreeOps.fs
new file mode 100644
index 0000000..4f0e7b3
--- /dev/null
+++ b/src/FSharpLanguageServer/SyntaxTreeOps.fs
@@ -0,0 +1,24 @@
+module FSharpLanguageServer.SyntaxTreeOps
+
+// Forked from https://github.com/dotnet/fsharp/blob/60a2fa663a3c4aed3f03c8bfc6f5e05b04284f23/src/fsharp/range.fs
+
+open FSharp.Compiler.SyntaxTree
+open FSharp.Compiler.Text.Range
+
+let ident (s, r) = Ident(s, r)
+
+let textOfId (id: Ident) = id.idText
+
+let pathOfLid lid = List.map textOfId lid
+
+let arrPathOfLid lid = Array.ofList (pathOfLid lid)
+
+let textOfPath path = String.concat "." path
+
+let textOfLid lid = textOfPath (pathOfLid lid)
+
+let rangeOfLid (lid: Ident list) =
+ match lid with
+ | [] -> failwith "rangeOfLid"
+ | [id] -> id.idRange
+ | h :: t -> unionRanges h.idRange (List.last t).idRange
diff --git a/src/FSharpLanguageServer/TipFormatter.fs b/src/FSharpLanguageServer/TipFormatter.fs
index 2e3d2cf..b83c37c 100644
--- a/src/FSharpLanguageServer/TipFormatter.fs
+++ b/src/FSharpLanguageServer/TipFormatter.fs
@@ -19,7 +19,7 @@ type private CachedMember = {
parameters: (string * string) list
returns: string option
// For example, ("T:System.ArgumentNullException", "The property is being set to null.")
- exceptions: (string * string) list
+ exceptions: (string * string) list
}
type private CachedFile = {
@@ -29,37 +29,37 @@ type private CachedFile = {
let private cache = Dictionary()
-let private convertTag(node: HtmlNode, tag: string, mapper: HtmlNode -> HtmlNode) =
- let nodes = List.ofSeq(node.Descendants(tag))
- for n in nodes do
+let private convertTag(node: HtmlNode, tag: string, mapper: HtmlNode -> HtmlNode) =
+ let nodes = List.ofSeq(node.Descendants(tag))
+ for n in nodes do
let parent = n.ParentNode
let r = mapper(n)
- parent.ReplaceChild(r, n) |> ignore
+ parent.ReplaceChild(r, n) |> ignore
-let private lastWord(text: string): string =
+let private lastWord(text: string): string =
let wordPattern = Regex(@"[a-zA-Z]\w*")
let words = [for m in wordPattern.Matches(text) do yield m.Value]
let maybeLast = List.tryLast(words)
Option.defaultValue "" maybeLast
-let private convertSee(n: HtmlNode): HtmlNode =
+let private convertSee(n: HtmlNode): HtmlNode =
let ref = n.GetAttributeValue("cref", n.InnerText)
let name = lastWord(ref)
HtmlNode.CreateNode("`" + name + "`")
-let private convertNameToCode(n: HtmlNode): HtmlNode =
+let private convertNameToCode(n: HtmlNode): HtmlNode =
let name = n.GetAttributeValue("name", n.InnerText)
HtmlNode.CreateNode("`" + name + "`")
-let private convertInnerTextToCode(n: HtmlNode): HtmlNode =
+let private convertInnerTextToCode(n: HtmlNode): HtmlNode =
let text = n.InnerText
HtmlNode.CreateNode("`" + text + "`")
-let private convertPara(n: HtmlNode): HtmlNode =
+let private convertPara(n: HtmlNode): HtmlNode =
HtmlNode.CreateNode("\n\n")
/// Convert special tags listed in https://docs.microsoft.com/en-us/dotnet/csharp/codedoc to markdown
-let private convertSpecialTagsToMarkdown(node: HtmlNode) =
+let private convertSpecialTagsToMarkdown(node: HtmlNode) =
convertTag(node, "see", convertSee)
convertTag(node, "paramref", convertNameToCode)
convertTag(node, "typeparamref", convertNameToCode)
@@ -72,10 +72,10 @@ let private cref(node: HtmlNode) =
let parts = attr.Split(':')
Array.last(parts)
-let private ensure(xmlFile: FileInfo) =
- if xmlFile.Exists then
- let needsUpdate =
- match cache.TryGetValue(xmlFile.FullName) with
+let private ensure(xmlFile: FileInfo) =
+ if xmlFile.Exists then
+ let needsUpdate =
+ match cache.TryGetValue(xmlFile.FullName) with
| false, _ -> true
| _, existing -> existing.loadTime < xmlFile.LastWriteTime
if needsUpdate then
@@ -86,7 +86,7 @@ let private ensure(xmlFile: FileInfo) =
let html = new HtmlDocument()
html.Load(xmlFile.FullName)
// Find all members
- for m in html.DocumentNode.Descendants("member") do
+ for m in html.DocumentNode.Descendants("member") do
convertSpecialTagsToMarkdown(m)
let name = m.GetAttributeValue("name", "")
let summary = [for e in m.Descendants("summary") do yield e.InnerHtml]
@@ -95,105 +95,116 @@ let private ensure(xmlFile: FileInfo) =
let exceptions = [for e in m.Descendants("exception") do yield cref(e), e.InnerHtml]
parsed.TryAdd(name, {
summary = List.tryHead(summary)
- parameters = parameters
- returns = List.tryHead(returns)
+ parameters = parameters
+ returns = List.tryHead(returns)
exceptions = exceptions
}) |> ignore
cache.TryAdd(xmlFile.FullName, {
- members = parsed
+ members = parsed
loadTime = xmlFile.LastWriteTime
}) |> ignore
/// Find the documentation for `memberName` inside of `xmlFile`
-let private find(xmlFile: FileInfo, memberName: string): CachedMember option =
+let private find(xmlFile: FileInfo, memberName: string): CachedMember option =
ensure(xmlFile)
- match cache.TryGetValue(xmlFile.FullName) with
- | false, _ -> None
- | _, cached ->
- match cached.members.TryGetValue(memberName) with
- | false, _ -> None
+ match cache.TryGetValue(xmlFile.FullName) with
+ | false, _ -> None
+ | _, cached ->
+ match cached.members.TryGetValue(memberName) with
+ | false, _ -> None
| _, m -> Some(m)
/// Render complete documentation, including parameter and return information, for a member that has no overloads
let docComment(doc: FSharpXmlDoc): string option =
match doc with
| FSharpXmlDoc.None -> None
- | FSharpXmlDoc.Text(s) -> Some(s)
+ | FSharpXmlDoc.Text(unprocessedLines, elaboratedXmlLines) ->
+ unprocessedLines
+ |> String.concat "\n"
+ |> Some
| FSharpXmlDoc.XmlDocFileSignature(dllPath, memberName) ->
let xmlFile = FileInfo(Path.ChangeExtension(dllPath, ".xml"))
- match find(xmlFile, memberName) with
- | None -> None
- | Some(m) ->
+ match find(xmlFile, memberName) with
+ | None -> None
+ | Some(m) ->
let lines = [
- if m.summary.IsSome && m.summary.Value.Length > 0 then
+ if m.summary.IsSome && m.summary.Value.Length > 0 then
yield m.summary.Value.Trim()
- for name, desc in m.parameters do
- yield sprintf "**%s** %s" name desc
- if m.returns.IsSome && m.returns.Value.Length > 0 then
+ for name, desc in m.parameters do
+ yield sprintf "**%s** %s" name desc
+ if m.returns.IsSome && m.returns.Value.Length > 0 then
yield sprintf "**returns** %s" m.returns.Value
- for name, desc in m.exceptions do
+ for name, desc in m.exceptions do
yield sprintf "**exception** `%s` %s" name desc ]
let comment = String.concat "\n\n" lines
Some(comment)
/// Render just the summary documentation
-let docSummaryOnly(doc: FSharpXmlDoc): string option =
+let docSummaryOnly(doc: FSharpXmlDoc): string option =
match doc with
| FSharpXmlDoc.None -> None
- | FSharpXmlDoc.Text(s) -> Some(s)
+ | FSharpXmlDoc.Text(unprocessedLines, elaboratedXmlLines) ->
+ unprocessedLines
+ |> String.concat "\n"
+ |> Some
| FSharpXmlDoc.XmlDocFileSignature(dllPath, memberName) ->
let xmlFile = FileInfo(Path.ChangeExtension(dllPath, ".xml"))
- match find(xmlFile, memberName) with
- | None -> None
+ match find(xmlFile, memberName) with
+ | None -> None
| Some(m) -> m.summary |> Option.map (fun(s) -> s.Trim())
/// Render documentation for an overloaded member
-let private overloadComment(docs: FSharpXmlDoc list): string option =
+let private overloadComment(docs: FSharpXmlDoc list): string option =
let summaries = [
- for doc in docs do
- match doc with
+ for doc in docs do
+ match doc with
| FSharpXmlDoc.None -> ()
- | FSharpXmlDoc.Text(s) -> yield s
- | FSharpXmlDoc.XmlDocFileSignature(dllPath, memberName) ->
+// | FSharpXmlDoc.Text(s) -> yield s
+ | FSharpXmlDoc.Text(unprocessedLines, elaboratedXmlLines) ->
+ yield unprocessedLines
+ |> String.concat "\n"
+ | FSharpXmlDoc.XmlDocFileSignature(dllPath, memberName) ->
let xmlFile = FileInfo(Path.ChangeExtension(dllPath, ".xml"))
- match find(xmlFile, memberName) with
- | None -> ()
- | Some(m) ->
- match m.summary with
- | None -> ()
+ match find(xmlFile, memberName) with
+ | None -> ()
+ | Some(m) ->
+ match m.summary with
+ | None -> ()
| Some(message) -> yield message.Trim() ]
- let first = List.tryHead summaries
- match first with
+ let first = List.tryHead summaries
+ match first with
| None -> None
| Some(summary) -> Some(sprintf "%s\n\n*(%d overloads)*" summary docs.Length)
-let private markup(s: string): MarkupContent =
+let private markup(s: string): MarkupContent =
{
kind=MarkupKind.Markdown
value=s
}
/// Add documentation information to the inline help of autocomplete
-let resolveDocs(item: CompletionItem, candidate: FSharpDeclarationListItem): Async =
+let resolveDocs(item: CompletionItem, candidate: FSharpDeclarationListItem): Async =
async {
- let! FSharpToolTipText(xs) = candidate.DescriptionTextAsync
+// let! FSharpToolTipText(xs) = candidate.DescriptionTextAsync
+ // TODO: fix this code
+ let xs = []
// FSharpToolTipText is weirdly nested, unwrap the parts that point to documentation
- let docs = [
- for x in xs do
- match x with
- | FSharpToolTipElement.Group(ys) ->
- for y in ys do
+ let docs = [
+ for x in xs do
+ match x with
+ | FSharpToolTipElement.Group(ys) ->
+ for y in ys do
yield y.XmlDoc
| _ -> () ]
// Render docs differently depending on how many overloads we find
- match docs with
- | [] -> return item
- | [one] ->
+ match docs with
+ | [] -> return item
+ | [one] ->
let value = docComment(one)
let doc = Option.map markup value
return {item with documentation=doc}
- | many ->
+ | many ->
let value = overloadComment(many)
let doc = Option.map markup value
return {item with documentation=doc}
- }
\ No newline at end of file
+ }
diff --git a/src/LSP/LSP.fsproj b/src/LSP/LSP.fsproj
index 6ddff7d..2bf8c77 100644
--- a/src/LSP/LSP.fsproj
+++ b/src/LSP/LSP.fsproj
@@ -14,6 +14,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/src/LSP/Ser.fs b/src/LSP/Ser.fs
index 60288a5..206a7a5 100644
--- a/src/LSP/Ser.fs
+++ b/src/LSP/Ser.fs
@@ -8,32 +8,32 @@ open System.Text.RegularExpressions
open FSharp.Data
let private escapeChars = Regex("[\t\n\r\"\\\\]", RegexOptions.Compiled)
-let private replaceChars =
- MatchEvaluator(fun m ->
- match m.Value with
+let private replaceChars =
+ MatchEvaluator(fun m ->
+ match m.Value with
| "\\" -> "\\\\"
| "\t" -> "\\t"
- | "\n" -> "\\n"
- | "\r" -> "\\r"
- | "\"" -> "\\\""
+ | "\n" -> "\\n"
+ | "\r" -> "\\r"
+ | "\"" -> "\\\""
| v -> v)
let private escapeStr(text:string) =
let escaped = escapeChars.Replace(text, replaceChars)
sprintf "\"%s\"" escaped
-let private isOption(t: Type) =
+let private isOption(t: Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option>
-let private isSeq(t: Type) =
+let private isSeq(t: Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof>
-let private implementsSeq(t: Type) =
+let private implementsSeq(t: Type) =
let is = t.GetInterfaces()
Seq.exists isSeq is
-let private isList(t: Type) =
+let private isList(t: Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof>
-let private isMap(t: Type) =
+let private isMap(t: Type) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof